require 'spec_helper' describe Sinatra::Cookies do def cookie_route(*cookies, &block) result = nil set_cookie(cookies) @cookie_app.get('/') do result = instance_eval(&block) "ok" end get '/', {}, @headers || {} expect(last_response).to be_ok expect(body).to eq("ok") result end def cookies(*set_cookies) cookie_route(*set_cookies) { cookies } end before do app = nil mock_app do helpers Sinatra::Cookies app = self end @cookie_app = app clear_cookies end describe :cookie_route do it 'runs the block' do ran = false cookie_route { ran = true } expect(ran).to be true end it 'returns the block result' do expect(cookie_route { 42 }).to eq(42) end end describe :== do it 'is comparable to hashes' do expect(cookies).to eq({}) end it 'is comparable to anything that responds to to_hash' do other = Struct.new(:to_hash).new({}) expect(cookies).to eq(other) end end describe :[] do it 'allows access to request cookies' do expect(cookies("foo=bar")["foo"]).to eq("bar") end it 'takes symbols as keys' do expect(cookies("foo=bar")[:foo]).to eq("bar") end it 'returns nil for missing keys' do expect(cookies("foo=bar")['bar']).to be_nil end it 'allows access to response cookies' do expect(cookie_route do response.set_cookie 'foo', 'bar' cookies['foo'] end).to eq('bar') end it 'favors response cookies over request cookies' do expect(cookie_route('foo=bar') do response.set_cookie 'foo', 'baz' cookies['foo'] end).to eq('baz') end it 'takes the last value for response cookies' do expect(cookie_route do response.set_cookie 'foo', 'bar' response.set_cookie 'foo', 'baz' cookies['foo'] end).to eq('baz') end end describe :[]= do it 'sets cookies to httponly' do expect(cookie_route do cookies['foo'] = 'bar' response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('HttpOnly') end it 'sets domain to nil if localhost' do @headers = {'HTTP_HOST' => 'localhost'} expect(cookie_route do cookies['foo'] = 'bar' response['Set-Cookie'] end).not_to include("domain") end it 'sets the domain' do expect(cookie_route do cookies['foo'] = 'bar' response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('domain=example.org') end it 'sets path to / by default' do expect(cookie_route do cookies['foo'] = 'bar' response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('path=/') end it 'sets path to the script_name if app is nested' do expect(cookie_route do request.script_name = '/foo' cookies['foo'] = 'bar' response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('path=/foo') end it 'sets a cookie' do cookie_route { cookies['foo'] = 'bar' } expect(cookie_jar['foo']).to eq('bar') end it 'adds a value to the cookies hash' do expect(cookie_route do cookies['foo'] = 'bar' cookies['foo'] end).to eq('bar') end end describe :assoc do it 'behaves like Hash#assoc' do cookies('foo=bar').assoc('foo') == ['foo', 'bar'] end end if Hash.method_defined? :assoc describe :clear do it 'removes request cookies from cookies hash' do jar = cookies('foo=bar') expect(jar['foo']).to eq('bar') jar.clear expect(jar['foo']).to be_nil end it 'removes response cookies from cookies hash' do expect(cookie_route do cookies['foo'] = 'bar' cookies.clear cookies['foo'] end).to be_nil end it 'expires existing cookies' do expect(cookie_route("foo=bar") do cookies.clear response['Set-Cookie'] end).to include("foo=;", "expires=", "1970 00:00:00") end end describe :compare_by_identity? do it { expect(cookies).not_to be_compare_by_identity } end describe :default do it { expect(cookies.default).to be_nil } end describe :default_proc do it { expect(cookies.default_proc).to be_nil } end describe :delete do it 'removes request cookies from cookies hash' do jar = cookies('foo=bar') expect(jar['foo']).to eq('bar') jar.delete 'foo' expect(jar['foo']).to be_nil end it 'removes response cookies from cookies hash' do expect(cookie_route do cookies['foo'] = 'bar' cookies.delete 'foo' cookies['foo'] end).to be_nil end it 'expires existing cookies' do expect(cookie_route("foo=bar") do cookies.delete 'foo' response['Set-Cookie'] end).to include("foo=;", "expires=", "1970 00:00:00") end it 'honours the app cookie_options' do @cookie_app.class_eval do set :cookie_options, { :path => '/foo', :domain => 'bar.com', :secure => true, :httponly => true } end cookie_header = cookie_route("foo=bar") do cookies.delete 'foo' response['Set-Cookie'] end expect(cookie_header).to include("path=/foo;", "domain=bar.com;", "secure;", "HttpOnly") end it 'does not touch other cookies' do expect(cookie_route("foo=bar", "bar=baz") do cookies.delete 'foo' cookies['bar'] end).to eq('baz') end it 'returns the previous value for request cookies' do expect(cookie_route("foo=bar") do cookies.delete "foo" end).to eq("bar") end it 'returns the previous value for response cookies' do expect(cookie_route do cookies['foo'] = 'bar' cookies.delete "foo" end).to eq("bar") end it 'returns nil for non-existing cookies' do expect(cookie_route { cookies.delete("foo") }).to be_nil end end describe :delete_if do it 'deletes cookies that match the block' do expect(cookie_route('foo=bar') do cookies['bar'] = 'baz' cookies['baz'] = 'foo' cookies.delete_if { |*a| a.include? 'bar' } cookies.values_at 'foo', 'bar', 'baz' end).to eq([nil, nil, 'foo']) end end describe :each do it 'loops through cookies' do keys = [] foo = nil bar = nil cookie_route('foo=bar', 'bar=baz') do cookies.each do |key, value| foo = value if key == 'foo' bar = value if key == 'bar' keys << key end end expect(keys.sort).to eq(['bar', 'foo']) expect(foo).to eq('bar') expect(bar).to eq('baz') end it 'favors response over request cookies' do seen = false key = nil value = nil cookie_route('foo=bar') do cookies[:foo] = 'baz' cookies.each do |k,v| key = k value = v end end expect(key).to eq('foo') expect(value).to eq('baz') expect(seen).to eq(false) end it 'does not loop through deleted cookies' do cookie_route('foo=bar') do cookies.delete :foo cookies.each { fail } end end it 'returns an enumerator' do keys = [] cookie_route('foo=bar') do enum = cookies.each enum.each { |key, value| keys << key } end keys.each{ |key| expect(key).to eq('foo')} end end describe :each_key do it 'loops through cookies' do keys = [] cookie_route('foo=bar', 'bar=baz') do cookies.each_key do |key| keys << key end end expect(keys.sort).to eq(['bar', 'foo']) end it 'only yields keys once' do seen = false cookie_route('foo=bar') do cookies[:foo] = 'baz' end expect(seen).to eq(false) end it 'does not loop through deleted cookies' do cookie_route('foo=bar') do cookies.delete :foo cookies.each_key { fail } end end it 'returns an enumerator' do keys = [] cookie_route('foo=bar') do enum = cookies.each_key enum.each { |key| keys << key } end keys.each{ |key| expect(key).to eq('foo')} end end describe :each_pair do it 'loops through cookies' do keys = [] foo = nil bar = nil cookie_route('foo=bar', 'bar=baz') do cookies.each_pair do |key, value| foo = value if key == 'foo' bar = value if key == 'bar' keys << key end end expect(keys.sort).to eq(['bar', 'foo']) expect(foo).to eq('bar') expect(bar).to eq('baz') end it 'favors response over request cookies' do seen = false key = nil value = nil cookie_route('foo=bar') do cookies[:foo] = 'baz' cookies.each_pair do |k, v| key = k value = v end end expect(key).to eq('foo') expect(value).to eq('baz') expect(seen).to eq(false) end it 'does not loop through deleted cookies' do cookie_route('foo=bar') do cookies.delete :foo cookies.each_pair { fail } end end it 'returns an enumerator' do keys = [] cookie_route('foo=bar') do enum = cookies.each_pair enum.each { |key, value| keys << key } end keys.each{ |key| expect(key).to eq('foo')} end end describe :each_value do it 'loops through cookies' do values = [] cookie_route('foo=bar', 'bar=baz') do cookies.each_value do |value| values << value end end expect(values.sort).to eq(['bar', 'baz']) end it 'favors response over request cookies' do value = nil cookie_route('foo=bar') do cookies[:foo] = 'baz' cookies.each_value do |v| value = v end end expect(value).to eq('baz') end it 'does not loop through deleted cookies' do cookie_route('foo=bar') do cookies.delete :foo cookies.each_value { fail } end end it 'returns an enumerator' do enum = nil cookie_route('foo=bar') do enum = cookies.each_value end enum.each { |value| expect(value).to eq('bar') } end end describe :empty? do it 'returns true if there are no cookies' do expect(cookies).to be_empty end it 'returns false if there are request cookies' do expect(cookies('foo=bar')).not_to be_empty end it 'returns false if there are response cookies' do expect(cookie_route do cookies['foo'] = 'bar' cookies.empty? end).to be false end it 'becomes true if response cookies are removed' do expect(cookie_route do cookies['foo'] = 'bar' cookies.delete :foo cookies.empty? end).to be true end it 'becomes true if request cookies are removed' do expect(cookie_route('foo=bar') do cookies.delete :foo cookies.empty? end).to be_truthy end it 'becomes true after clear' do expect(cookie_route('foo=bar', 'bar=baz') do cookies['foo'] = 'bar' cookies.clear cookies.empty? end).to be_truthy end end describe :fetch do it 'returns values from request cookies' do expect(cookies('foo=bar').fetch('foo')).to eq('bar') end it 'returns values from response cookies' do expect(cookie_route do cookies['foo'] = 'bar' cookies.fetch('foo') end).to eq('bar') end it 'favors response over request cookies' do expect(cookie_route('foo=baz') do cookies['foo'] = 'bar' cookies.fetch('foo') end).to eq('bar') end it 'raises an exception if key does not exist' do error = if defined? JRUBY_VERSION IndexError else KeyError end expect { cookies.fetch('foo') }.to raise_exception(error) end it 'returns the block result if missing' do expect(cookies.fetch('foo') { 'bar' }).to eq('bar') end end describe :flatten do it { expect(cookies('foo=bar').flatten).to eq({'foo' => 'bar'}.flatten) } end if Hash.method_defined? :flatten describe :has_key? do it 'checks request cookies' do expect(cookies('foo=bar')).to have_key('foo') end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar).to have_key(:foo) end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar).not_to have_key('foo') end end describe :has_value? do it 'checks request cookies' do expect(cookies('foo=bar')).to have_value('bar') end it 'checks response cookies' do jar = cookies jar[:foo] = 'bar' expect(jar).to have_value('bar') end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar).not_to have_value('bar') end end describe :include? do it 'checks request cookies' do expect(cookies('foo=bar')).to include('foo') end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar).to include(:foo) end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar).not_to include('foo') end end describe :keep_if do it 'removes entries' do jar = cookies('foo=bar', 'bar=baz') jar.keep_if { |*args| args == ['bar', 'baz'] } expect(jar).to eq({'bar' => 'baz'}) end end describe :key do it 'checks request cookies' do expect(cookies('foo=bar').key('bar')).to eq('foo') end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar.key('bar')).to eq('foo') end it 'returns nil when missing' do expect(cookies('foo=bar').key('baz')).to be_nil end end describe :key? do it 'checks request cookies' do expect(cookies('foo=bar').key?('foo')).to be true end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar.key?(:foo)).to be true end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar.key?('foo')).to be false end end describe :keys do it { expect(cookies('foo=bar').keys).to eq(['foo']) } end describe :length do it { expect(cookies.length).to eq(0) } it { expect(cookies('foo=bar').length).to eq(1) } end describe :member? do it 'checks request cookies' do expect(cookies('foo=bar').member?('foo')).to be true end it 'checks response cookies' do jar = cookies jar['foo'] = 'bar' expect(jar.member?(:foo)).to be true end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar.member?('foo')).to be false end end describe :merge do it 'is mergable with a hash' do expect(cookies('foo=bar').merge(:bar => :baz)).to eq({"foo" => "bar", :bar => :baz}) end it 'does not create cookies' do jar = cookies('foo=bar') jar.merge(:bar => 'baz') expect(jar).not_to include(:bar) end it 'takes a block for conflict resolution' do update = {'foo' => 'baz', 'bar' => 'baz'} merged = cookies('foo=bar').merge(update) do |key, old, other| expect(key).to eq('foo') expect(old).to eq('bar') expect(other).to eq('baz') 'foo' end expect(merged['foo']).to eq('foo') end end describe :merge! do it 'creates cookies' do jar = cookies('foo=bar') jar.merge! :bar => 'baz' expect(jar).to include('bar') end it 'overrides existing values' do jar = cookies('foo=bar') jar.merge! :foo => "baz" expect(jar["foo"]).to eq("baz") end it 'takes a block for conflict resolution' do update = {'foo' => 'baz', 'bar' => 'baz'} jar = cookies('foo=bar') jar.merge!(update) do |key, old, other| expect(key).to eq('foo') expect(old).to eq('bar') expect(other).to eq('baz') 'foo' end expect(jar['foo']).to eq('foo') end end describe :rassoc do it 'behaves like Hash#assoc' do cookies('foo=bar').rassoc('bar') == ['foo', 'bar'] end end if Hash.method_defined? :rassoc describe :reject do it 'removes entries from new hash' do jar = cookies('foo=bar', 'bar=baz') sub = jar.reject { |*args| args == ['bar', 'baz'] } expect(sub).to eq({'foo' => 'bar'}) expect(jar['bar']).to eq('baz') end end describe :reject! do it 'removes entries' do jar = cookies('foo=bar', 'bar=baz') jar.reject! { |*args| args == ['bar', 'baz'] } expect(jar).to eq({'foo' => 'bar'}) end end describe :replace do it 'replaces entries' do jar = cookies('foo=bar', 'bar=baz') jar.replace 'foo' => 'baz', 'baz' => 'bar' expect(jar).to eq({'foo' => 'baz', 'baz' => 'bar'}) end end describe :set do it 'sets a cookie' do cookie_route { cookies.set('foo', value: 'bar') } expect(cookie_jar['foo']).to eq('bar') end it 'sets a cookie with HttpOnly' do expect(cookie_route do request.script_name = '/foo' cookies.set('foo', value: 'bar', httponly: true) response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).to include('HttpOnly') end it 'sets a cookie without HttpOnly' do expect(cookie_route do request.script_name = '/foo' cookies.set('foo', value: 'bar', httponly: false) response['Set-Cookie'].lines.detect { |l| l.start_with? 'foo=' } end).not_to include('HttpOnly') end end describe :select do it 'removes entries from new hash' do jar = cookies('foo=bar', 'bar=baz') sub = jar.select { |*args| args != ['bar', 'baz'] } expect(sub).to eq({'foo' => 'bar'}.select { true }) expect(jar['bar']).to eq('baz') end end describe :select! do it 'removes entries' do jar = cookies('foo=bar', 'bar=baz') jar.select! { |*args| args != ['bar', 'baz'] } expect(jar).to eq({'foo' => 'bar'}) end end if Hash.method_defined? :select! describe :shift do it 'removes from the hash' do jar = cookies('foo=bar') expect(jar.shift).to eq(['foo', 'bar']) expect(jar).not_to include('bar') end end describe :size do it { expect(cookies.size).to eq(0) } it { expect(cookies('foo=bar').size).to eq(1) } end describe :update do it 'creates cookies' do jar = cookies('foo=bar') jar.update :bar => 'baz' expect(jar).to include('bar') end it 'overrides existing values' do jar = cookies('foo=bar') jar.update :foo => "baz" expect(jar["foo"]).to eq("baz") end it 'takes a block for conflict resolution' do merge = {'foo' => 'baz', 'bar' => 'baz'} jar = cookies('foo=bar') jar.update(merge) do |key, old, other| expect(key).to eq('foo') expect(old).to eq('bar') expect(other).to eq('baz') 'foo' end expect(jar['foo']).to eq('foo') end end describe :value? do it 'checks request cookies' do expect(cookies('foo=bar').value?('bar')).to be true end it 'checks response cookies' do jar = cookies jar[:foo] = 'bar' expect(jar.value?('bar')).to be true end it 'does not use deleted cookies' do jar = cookies('foo=bar') jar.delete :foo expect(jar.value?('bar')).to be false end end describe :values do it { expect(cookies('foo=bar', 'bar=baz').values.sort).to eq(['bar', 'baz']) } end describe :values_at do it { expect(cookies('foo=bar', 'bar=baz').values_at('foo')).to eq(['bar']) } end end