require "spec" require "http/cookie" module HTTP describe Cookies do describe ".from_client_headers" do it "parses Cookie header" do cookies = Cookies.from_client_headers Headers{"Cookie" => "a=b"} cookies.should eq HTTP::Cookies{Cookie.new("a", "b")} end it "does not accept Set-Cookie header" do cookies = Cookies.from_client_headers Headers{"Cookie" => "a=b", "Set-Cookie" => "x=y"} cookies.should eq HTTP::Cookies{Cookie.new("a", "b")} end it "chops value at the first invalid byte" do HTTP::Cookies.from_client_headers( HTTP::Headers{"Cookie" => "ginger=snap; cookie=hm🍪delicious; snicker=doodle"} ).should eq HTTP::Cookies{ HTTP::Cookie.new("ginger", "snap"), HTTP::Cookie.new("cookie", "hm"), HTTP::Cookie.new("snicker", "doodle"), } end # FIXME: Should handle both: https://github.com/crystal-lang/crystal/issues/16069 it "takes last one on duplicate keys" do HTTP::Cookies.from_client_headers( HTTP::Headers{"Cookie" => "foo=bar; foo=baz"} ).should eq HTTP::Cookies{ HTTP::Cookie.new("foo", "baz"), } HTTP::Cookies.from_client_headers( HTTP::Headers{"Cookie" => "foo=baz; foo=bar"} ).should eq HTTP::Cookies{ HTTP::Cookie.new("foo", "bar"), } end end describe ".from_server_headers" do it "parses Set-Cookie header" do cookies = Cookies.from_server_headers Headers{"Set-Cookie" => "a=b; path=/foo"} cookies.should eq HTTP::Cookies{Cookie.new("a", "b", path: "/foo")} end it "does not accept Cookie header" do cookies = Cookies.from_server_headers Headers{"Set-Cookie" => "a=b", "Cookie" => "x=y"} cookies.should eq HTTP::Cookies{Cookie.new("a", "b")} end it "drops cookies with invalid byte in value" do HTTP::Cookies.from_server_headers( HTTP::Headers{"Set-Cookie" => ["ginger=snap", "cookie=hm🍪delicious", "snicker=doodle"]} ).should eq HTTP::Cookies{ HTTP::Cookie.new("ginger", "snap"), HTTP::Cookie.new("snicker", "doodle"), } end # FIXME: Should handle both: https://github.com/crystal-lang/crystal/issues/16069 it "takes last one on duplicate keys" do HTTP::Cookies.from_server_headers( HTTP::Headers{"Set-Cookie" => ["foo=bar", "foo=baz; path=/baz"]} ).should eq HTTP::Cookies{ HTTP::Cookie.new("foo", "baz", path: "/baz"), } HTTP::Cookies.from_server_headers( HTTP::Headers{"Set-Cookie" => ["foo=baz; path=/baz", "foo=bar"]} ).should eq HTTP::Cookies{ HTTP::Cookie.new("foo", "bar"), } end end describe "mutating" do it "allows adding cookies and retrieving" do cookies = Cookies.new cookies << Cookie.new("a", "b") cookies["c"] = Cookie.new("c", "d") cookies["d"] = "e" cookies["a"].value.should eq "b" cookies["c"].value.should eq "d" cookies["d"].value.should eq "e" cookies["a"]?.should_not be_nil cookies["e"]?.should be_nil cookies.has_key?("a").should be_true end describe "#<<" do it "overwrites existing key" do cookies = Cookies{"a" => "b"} cookies << Cookie.new("a", "c") cookies.should eq Cookies{"a" => "c"} end it "overwrites existing key with same value" do cookies = Cookies{"a" => "b"} new_cookie = Cookie.new("a", "b", path: "/foo") cookies << new_cookie cookies.should eq Cookies{new_cookie} end end describe "#[]=" do it "disallows adding inconsistent state" do cookies = Cookies.new expect_raises ArgumentError do cookies["a"] = Cookie.new("b", "c") end end it "overwrites existing key" do cookies = Cookies{"a" => "b"} cookies["a"] = "c" cookies.should eq Cookies{"a" => "c"} end it "overwrites existing key with same value" do cookies = Cookies{Cookie.new("a", "b", path: "/foo")} cookies["a"] = "b" cookies.should eq Cookies{"a" => "b"} end end end it "#size" do cookies = Cookies.new cookies.size.should eq 0 cookies << Cookie.new("1", "2") cookies.size.should eq 1 cookies << Cookie.new("3", "4") cookies.size.should eq 2 end it "#clear" do cookies = Cookies.new cookies << Cookie.new("test_key", "test_value") cookies << Cookie.new("a", "b") cookies << Cookie.new("c", "d") cookies.clear cookies.should be_empty end it "#delete" do cookies = Cookies.new cookies << Cookie.new("the_key", "the_value") cookies << Cookie.new("not_the_key", "not_the_value") cookies << Cookie.new("a", "b") cookies.has_key?("the_key").should be_true cookies.delete("the_key").not_nil!.value.should eq "the_value" cookies.has_key?("the_key").should be_false cookies.size.should eq 2 end describe "#add_request_headers" do it "overwrites a pre-existing Cookie header" do headers = Headers.new headers["Cookie"] = "some_key=some_value" cookies = Cookies.new cookies << Cookie.new("a", "b") headers["Cookie"].should eq "some_key=some_value" cookies.add_request_headers(headers) headers["Cookie"].should eq "a=b" end it "use encode_www_form to write the cookie's value" do headers = Headers.new cookies = Cookies.new cookies << Cookie.new("a", "b+c") cookies.add_request_headers(headers) headers["Cookie"].should eq "a=b+c" end it "merges multiple cookies into one Cookie header" do headers = Headers.new cookies = Cookies.new cookies << Cookie.new("a", "b") cookies << Cookie.new("c", "d") cookies.add_request_headers(headers) headers["Cookie"].should eq "a=b; c=d" end describe "when no cookies are set" do it "does not set a Cookie header" do headers = Headers.new headers["Cookie"] = "a=b" cookies = Cookies.new headers["Cookie"]?.should_not be_nil cookies.add_request_headers(headers) headers["Cookie"]?.should be_nil end end end describe "#add_response_headers" do it "overwrites all pre-existing Set-Cookie headers" do headers = Headers.new headers.add("Set-Cookie", "a=b") headers.add("Set-Cookie", "c=d") cookies = Cookies.new cookies << Cookie.new("x", "y") headers.get("Set-Cookie").size.should eq 2 headers.get("Set-Cookie").should contain("a=b") headers.get("Set-Cookie").should contain("c=d") cookies.add_response_headers(headers) headers.get("Set-Cookie").size.should eq 1 headers.get("Set-Cookie")[0].should eq "x=y" end it "sets one Set-Cookie header per cookie" do headers = Headers.new cookies = Cookies.new cookies << Cookie.new("a", "b") cookies << Cookie.new("c", "d") headers.get?("Set-Cookie").should be_nil cookies.add_response_headers(headers) headers.get?("Set-Cookie").should_not be_nil headers.get("Set-Cookie").should contain("a=b") headers.get("Set-Cookie").should contain("c=d") end it "uses encode_www_form on Set-Cookie value" do headers = Headers.new cookies = Cookies.new cookies << Cookie.new("a", "b+c") cookies.add_response_headers(headers) headers.get("Set-Cookie").should contain("a=b+c") end describe "when no cookies are set" do it "does not set a Set-Cookie header" do headers = Headers.new headers.add("Set-Cookie", "a=b") cookies = Cookies.new headers.get?("Set-Cookie").should_not be_nil cookies.add_response_headers(headers) headers.get?("Set-Cookie").should be_nil end end end it "#each" do cookies = Cookies.new cookies["a"] = "b" cookies.each do |cookie| cookie.name.should eq "a" cookie.value.should eq "b" end cookie = cookies.each.next cookie.should eq Cookie.new("a", "b") end it "#to_h" do cookies = Cookies.new cookies << Cookie.new("a", "b") cookies["c"] = Cookie.new("c", "d") cookies["d"] = "e" cookies_hash = cookies.to_h compare_hash = {"a" => Cookie.new("a", "b"), "c" => Cookie.new("c", "d"), "d" => Cookie.new("d", "e")} cookies_hash.should eq(compare_hash) cookies["x"] = "y" cookies.to_h.should_not eq(cookies_hash) end describe "#==" do it "equal" do Cookies.new.should eq Cookies.new Cookies{Cookie.new("foo", "bar")}.should eq Cookies{Cookie.new("foo", "bar")} cookies = Cookies{Cookie.new("foo", "bar"), Cookie.new("baz", "qux")} cookies.should eq cookies.dup end it "order doesn't matter" do Cookies{Cookie.new("foo", "bar"), Cookie.new("baz", "qux")}.should eq Cookies{Cookie.new("baz", "qux"), Cookie.new("foo", "bar")} end it "unequal" do Cookies.new.should_not eq Cookies{Cookie.new("foo", "bar")} Cookies{Cookie.new("foo", "bar")}.should_not eq Cookies.new Cookies{Cookie.new("foo", "bar")}.should_not eq Cookies{Cookie.new("foo", "baz")} end end describe "#to_s" do it "stringifies" do cookies = HTTP::Cookies{ HTTP::Cookie.new("foo", "bar"), HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax), } cookies.to_s.should eq %(HTTP::Cookies{"foo=bar", "x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"}) end end describe "#inspect" do it "stringifies" do cookies = HTTP::Cookies{ HTTP::Cookie.new("foo", "bar"), HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax), } cookies.inspect.should eq %(HTTP::Cookies{"foo=bar", "x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"}) end end describe "#pretty_print" do it "stringifies" do cookies = HTTP::Cookies{ HTTP::Cookie.new("foo", "bar"), HTTP::Cookie.new("x", "y", domain: "example.com", path: "/foo", expires: Time.unix(1257894000), samesite: :lax), } cookies.pretty_inspect.should eq <<-CRYSTAL HTTP::Cookies{"foo=bar", "x=y; domain=example.com; path=/foo; expires=Tue, 10 Nov 2009 23:00:00 GMT; SameSite=Lax"} CRYSTAL end end end end