nono-redis-store 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. data/.gitignore +11 -0
  2. data/CHANGELOG +262 -0
  3. data/Gemfile +33 -0
  4. data/Gemfile.lock +194 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +191 -0
  7. data/Rakefile +60 -0
  8. data/VERSION +1 -0
  9. data/lib/action_controller/session/redis_session_store.rb +74 -0
  10. data/lib/active_support/cache/redis_store.rb +228 -0
  11. data/lib/cache/merb/redis_store.rb +75 -0
  12. data/lib/cache/sinatra/redis_store.rb +126 -0
  13. data/lib/i18n/backend/redis.rb +67 -0
  14. data/lib/rack/cache/redis_entitystore.rb +51 -0
  15. data/lib/rack/cache/redis_metastore.rb +42 -0
  16. data/lib/rack/session/merb.rb +32 -0
  17. data/lib/rack/session/redis.rb +81 -0
  18. data/lib/redis-store.rb +44 -0
  19. data/lib/redis/distributed_store.rb +35 -0
  20. data/lib/redis/factory.rb +46 -0
  21. data/lib/redis/store.rb +30 -0
  22. data/lib/redis/store/interface.rb +17 -0
  23. data/lib/redis/store/marshalling.rb +41 -0
  24. data/lib/redis/store/namespace.rb +54 -0
  25. data/lib/redis/store/ttl.rb +37 -0
  26. data/lib/redis/store/version.rb +11 -0
  27. data/redis-store.gemspec +103 -0
  28. data/spec/action_controller/session/redis_session_store_spec.rb +121 -0
  29. data/spec/active_support/cache/redis_store_spec.rb +405 -0
  30. data/spec/cache/merb/redis_store_spec.rb +143 -0
  31. data/spec/cache/sinatra/redis_store_spec.rb +192 -0
  32. data/spec/config/master.conf +312 -0
  33. data/spec/config/single.conf +312 -0
  34. data/spec/config/slave.conf +312 -0
  35. data/spec/i18n/backend/redis_spec.rb +56 -0
  36. data/spec/rack/cache/entitystore/pony.jpg +0 -0
  37. data/spec/rack/cache/entitystore/redis_spec.rb +120 -0
  38. data/spec/rack/cache/metastore/redis_spec.rb +255 -0
  39. data/spec/rack/session/redis_spec.rb +234 -0
  40. data/spec/redis/distributed_store_spec.rb +47 -0
  41. data/spec/redis/factory_spec.rb +110 -0
  42. data/spec/redis/store/interface_spec.rb +23 -0
  43. data/spec/redis/store/marshalling_spec.rb +83 -0
  44. data/spec/redis/store/namespace_spec.rb +76 -0
  45. data/spec/redis/store/version_spec.rb +7 -0
  46. data/spec/spec_helper.rb +43 -0
  47. data/tasks/redis.tasks.rb +220 -0
  48. metadata +141 -0
@@ -0,0 +1,56 @@
1
+ require 'spec_helper'
2
+
3
+ describe "I18n::Backend::Redis" do
4
+ before :each do
5
+ @backend = I18n::Backend::Redis.new
6
+ @store = @backend.store
7
+ I18n.backend = @backend
8
+ end
9
+
10
+ it "should instantiate a store" do
11
+ @store.should be_kind_of(Redis::Store)
12
+ end
13
+
14
+ it "should instantiate a distributed store" do
15
+ store = I18n::Backend::Redis.new([ "redis://127.0.0.1:6379", "redis://127.0.0.1:6380" ]).store
16
+ store.should be_kind_of(Redis::DistributedStore)
17
+ end
18
+
19
+ it "should accept string uri" do
20
+ store = I18n::Backend::Redis.new("redis://127.0.0.1:6380/13/theplaylist").store
21
+ store.to_s.should == "Redis Client connected to 127.0.0.1:6380 against DB 13 with namespace theplaylist"
22
+ end
23
+
24
+ it "should accept hash params" do
25
+ store = I18n::Backend::Redis.new(:host => "127.0.0.1", :port => "6380", :db =>"13", :namespace => "theplaylist").store
26
+ store.to_s.should == "Redis Client connected to 127.0.0.1:6380 against DB 13 with namespace theplaylist"
27
+ end
28
+
29
+ it "should store translations" do
30
+ I18n.backend.store_translations :en, :foo => { :bar => :baz }
31
+ I18n.t(:"foo.bar").should == :baz
32
+
33
+ I18n.backend.store_translations :en, "foo" => { "bar" => "baz" }
34
+ I18n.t(:"foo.bar").should == "baz"
35
+ end
36
+
37
+ it "should get translations" do
38
+ I18n.backend.store_translations :en, :foo => { :bar => { :baz => :bang } }
39
+ I18n.t(:"foo.bar.baz").should == :bang
40
+ I18n.t(:"baz", :scope => :"foo.bar").should == :bang
41
+ end
42
+
43
+ it "should not store proc translations" do
44
+ lambda { I18n.backend.store_translations :en, :foo => lambda {|| } }.should raise_error("Key-value stores cannot handle procs")
45
+ end
46
+
47
+ it "should list available locales" do
48
+ locales = [ :en, :it, :es, :fr, :de ]
49
+ locales.each { |locale| I18n.backend.store_translations locale, :foo => "bar" }
50
+ available_locales = I18n.backend.available_locales
51
+
52
+ locales.each do |locale|
53
+ available_locales.should include(locale)
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,120 @@
1
+ require 'spec_helper'
2
+
3
+ class Object
4
+ def sha_like?
5
+ length == 40 && self =~ /^[0-9a-z]+$/
6
+ end
7
+ end
8
+
9
+ module Rack
10
+ module Cache
11
+ class EntityStore
12
+ # courtesy of http://github.com/rtomayko/rack-cache team
13
+ describe "Rack::Cache::EntityStore::Redis" do
14
+ before(:each) do
15
+ @store = Rack::Cache::EntityStore::Redis.new :host => "localhost"
16
+ end
17
+
18
+ # Redis store specific examples ===========================================
19
+
20
+ it "should have the class referenced by homonym constant" do
21
+ Rack::Cache::EntityStore::REDIS.should be(Rack::Cache::EntityStore::Redis)
22
+ end
23
+
24
+ it "should resolve the connection uri" do
25
+ cache = Rack::Cache::EntityStore::Redis.resolve(uri("redis://127.0.0.1")).cache
26
+ cache.should be_kind_of(::Redis)
27
+ cache.id.should == "redis://127.0.0.1:6379/0"
28
+
29
+ cache = Rack::Cache::EntityStore::Redis.resolve(uri("redis://127.0.0.1:6380")).cache
30
+ cache.id.should == "redis://127.0.0.1:6380/0"
31
+
32
+ cache = Rack::Cache::EntityStore::Redis.resolve(uri("redis://127.0.0.1/13")).cache
33
+ cache.id.should == "redis://127.0.0.1:6379/13"
34
+ end
35
+
36
+ # Entity store shared examples ===========================================
37
+
38
+ it 'responds to all required messages' do
39
+ %w[read open write exist?].each do |message|
40
+ @store.should respond_to(message)
41
+ end
42
+ end
43
+
44
+ it 'stores bodies with #write' do
45
+ key, size = @store.write(['My wild love went riding,'])
46
+ key.should_not be_nil
47
+ key.should be_sha_like
48
+
49
+ data = @store.read(key)
50
+ data.should == 'My wild love went riding,'
51
+ end
52
+
53
+ it 'correctly determines whether cached body exists for key with #exist?' do
54
+ key, size = @store.write(['She rode to the devil,'])
55
+ @store.should be_exist(key)
56
+ @store.should_not be_exist('938jasddj83jasdh4438021ksdfjsdfjsdsf')
57
+ end
58
+
59
+ it 'can read data written with #write' do
60
+ key, size = @store.write(['And asked him to pay.'])
61
+ data = @store.read(key)
62
+ data.should == 'And asked him to pay.'
63
+ end
64
+
65
+ it 'gives a 40 character SHA1 hex digest from #write' do
66
+ key, size = @store.write(['she rode to the sea;'])
67
+ key.should_not be_nil
68
+ key.length.should == 40
69
+ key.should =~ /^[0-9a-z]+$/
70
+ key.should == '90a4c84d51a277f3dafc34693ca264531b9f51b6'
71
+ end
72
+
73
+ it 'returns the entire body as a String from #read' do
74
+ key, size = @store.write(['She gathered together'])
75
+ @store.read(key).should == 'She gathered together'
76
+ end
77
+
78
+ it 'returns nil from #read when key does not exist' do
79
+ @store.read('87fe0a1ae82a518592f6b12b0183e950b4541c62').should be_nil
80
+ end
81
+
82
+ it 'returns a Rack compatible body from #open' do
83
+ key, size = @store.write(['Some shells for her hair.'])
84
+ body = @store.open(key)
85
+ body.should respond_to(:each)
86
+ buf = ''
87
+ body.each { |part| buf << part }
88
+ buf.should == 'Some shells for her hair.'
89
+ end
90
+
91
+ it 'returns nil from #open when key does not exist' do
92
+ @store.open('87fe0a1ae82a518592f6b12b0183e950b4541c62').should be_nil
93
+ end
94
+
95
+ if RUBY_VERSION < '1.9'
96
+ it 'can store largish bodies with binary data' do
97
+ pony = ::File.open(::File.dirname(__FILE__) + '/pony.jpg', 'rb') { |f| f.read }
98
+ key, size = @store.write([pony])
99
+ key.should == 'd0f30d8659b4d268c5c64385d9790024c2d78deb'
100
+ data = @store.read(key)
101
+ data.length.should == pony.length
102
+ data.hash.should == pony.hash
103
+ end
104
+ end
105
+
106
+ it 'deletes stored entries with #purge' do
107
+ key, size = @store.write(['My wild love went riding,'])
108
+ @store.purge(key).should be_nil
109
+ @store.read(key).should be_nil
110
+ end
111
+
112
+ # Helper Methods =============================================================
113
+
114
+ define_method :uri do |uri|
115
+ URI.parse uri
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,255 @@
1
+ require 'spec_helper'
2
+
3
+ module Rack
4
+ module Cache
5
+ class MetaStore
6
+ # courtesy of http://github.com/rtomayko/rack-cache team
7
+ describe "Rack::Cache::MetaStore::Redis" do
8
+ before :each do
9
+ @store = Rack::Cache::MetaStore::Redis.resolve uri("redis://127.0.0.1")
10
+ @entity_store = Rack::Cache::EntityStore::Redis.resolve uri("redis://127.0.0.1:6380")
11
+ @request = mock_request('/', {})
12
+ @response = mock_response(200, {}, ['hello world'])
13
+ end
14
+
15
+ after :each do
16
+ @store.cache.flushall
17
+ @entity_store.cache.flushall
18
+ end
19
+
20
+ it "should have the class referenced by homonym constant" do
21
+ Rack::Cache::MetaStore::REDIS.should be(Rack::Cache::MetaStore::Redis)
22
+ end
23
+
24
+ it "should resolve the connection uri" do
25
+ cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1")).cache
26
+ cache.should be_kind_of(::Redis::Store)
27
+ cache.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 0"
28
+
29
+ cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1:6380")).cache
30
+ cache.to_s.should == "Redis Client connected to 127.0.0.1:6380 against DB 0"
31
+
32
+ cache = Rack::Cache::MetaStore::Redis.resolve(uri("redis://127.0.0.1/13")).cache
33
+ cache.to_s.should == "Redis Client connected to 127.0.0.1:6379 against DB 13"
34
+ end
35
+
36
+ # Low-level implementation methods ===========================================
37
+
38
+ it 'writes a list of negotation tuples with #write' do
39
+ lambda { @store.write('/test', [[{}, {}]]) }.should_not raise_error
40
+ end
41
+
42
+ it 'reads a list of negotation tuples with #read' do
43
+ @store.write('/test', [[{},{}],[{},{}]])
44
+ tuples = @store.read('/test')
45
+ tuples.should == [ [{},{}], [{},{}] ]
46
+ end
47
+
48
+ it 'reads an empty list with #read when nothing cached at key' do
49
+ @store.read('/nothing').should be_empty
50
+ end
51
+
52
+ it 'removes entries for key with #purge' do
53
+ @store.write('/test', [[{},{}]])
54
+ @store.read('/test').should_not be_empty
55
+
56
+ @store.purge('/test')
57
+ @store.read('/test').should be_empty
58
+ end
59
+
60
+ it 'succeeds when purging non-existing entries' do
61
+ @store.read('/test').should be_empty
62
+ @store.purge('/test')
63
+ end
64
+
65
+ it 'returns nil from #purge' do
66
+ @store.write('/test', [[{},{}]])
67
+ @store.purge('/test').should be_nil
68
+ @store.read('/test').should == []
69
+ end
70
+
71
+ %w[/test http://example.com:8080/ /test?x=y /test?x=y&p=q].each do |key|
72
+ it "can read and write key: '#{key}'" do
73
+ lambda { @store.write(key, [[{},{}]]) }.should_not raise_error
74
+ @store.read(key).should == [[{},{}]]
75
+ end
76
+ end
77
+
78
+ it "can read and write fairly large keys" do
79
+ key = "b" * 4096
80
+ lambda { @store.write(key, [[{},{}]]) }.should_not raise_error
81
+ @store.read(key).should == [[{},{}]]
82
+ end
83
+
84
+ it "allows custom cache keys from block" do
85
+ request = mock_request('/test', {})
86
+ request.env['rack-cache.cache_key'] =
87
+ lambda { |request| request.path_info.reverse }
88
+ @store.cache_key(request).should == 'tset/'
89
+ end
90
+
91
+ it "allows custom cache keys from class" do
92
+ request = mock_request('/test', {})
93
+ request.env['rack-cache.cache_key'] = Class.new do
94
+ def self.call(request); request.path_info.reverse end
95
+ end
96
+ @store.cache_key(request).should == 'tset/'
97
+ end
98
+
99
+ # Abstract methods ===========================================================
100
+
101
+ # Stores an entry for the given request args, returns a url encoded cache key
102
+ # for the request.
103
+ define_method :store_simple_entry do |*request_args|
104
+ path, headers = request_args
105
+ @request = mock_request(path || '/test', headers || {})
106
+ @response = mock_response(200, {'Cache-Control' => 'max-age=420'}, ['test'])
107
+ body = @response.body
108
+ cache_key = @store.store(@request, @response, @entity_store)
109
+ @response.body.should_not equal(body)
110
+ cache_key
111
+ end
112
+
113
+ it 'stores a cache entry' do
114
+ cache_key = store_simple_entry
115
+ @store.read(cache_key).should_not be_empty
116
+ end
117
+
118
+ it 'sets the X-Content-Digest response header before storing' do
119
+ cache_key = store_simple_entry
120
+ req, res = @store.read(cache_key).first
121
+ res['X-Content-Digest'].should == 'a94a8fe5ccb19ba61c4c0873d391e987982fbbd3'
122
+ end
123
+
124
+ it 'finds a stored entry with #lookup' do
125
+ store_simple_entry
126
+ response = @store.lookup(@request, @entity_store)
127
+ response.should_not be_nil
128
+ response.should be_kind_of(Rack::Cache::Response)
129
+ end
130
+
131
+ it 'does not find an entry with #lookup when none exists' do
132
+ req = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
133
+ @store.lookup(req, @entity_store).should be_nil
134
+ end
135
+
136
+ it "canonizes urls for cache keys" do
137
+ store_simple_entry(path='/test?x=y&p=q')
138
+
139
+ hits_req = mock_request(path, {})
140
+ miss_req = mock_request('/test?p=x', {})
141
+
142
+ @store.lookup(hits_req, @entity_store).should_not be_nil
143
+ @store.lookup(miss_req, @entity_store).should be_nil
144
+ end
145
+
146
+ it 'does not find an entry with #lookup when the body does not exist' do
147
+ store_simple_entry
148
+ @response.headers['X-Content-Digest'].should_not be_nil
149
+ @entity_store.purge(@response.headers['X-Content-Digest'])
150
+ @store.lookup(@request, @entity_store).should be_nil
151
+ end
152
+
153
+ it 'restores response headers properly with #lookup' do
154
+ store_simple_entry
155
+ response = @store.lookup(@request, @entity_store)
156
+ response.headers.should == @response.headers.merge('Content-Length' => '4')
157
+ end
158
+
159
+ it 'restores response body from entity store with #lookup' do
160
+ store_simple_entry
161
+ response = @store.lookup(@request, @entity_store)
162
+ body = '' ; response.body.each {|p| body << p}
163
+ body.should == 'test'
164
+ end
165
+
166
+ it 'invalidates meta and entity store entries with #invalidate' do
167
+ store_simple_entry
168
+ @store.invalidate(@request, @entity_store)
169
+ response = @store.lookup(@request, @entity_store)
170
+ response.should be_kind_of(Rack::Cache::Response)
171
+ response.should_not be_fresh
172
+ end
173
+
174
+ it 'succeeds quietly when #invalidate called with no matching entries' do
175
+ req = mock_request('/test', {})
176
+ @store.invalidate(req, @entity_store)
177
+ @store.lookup(@request, @entity_store).should be_nil
178
+ end
179
+
180
+ # Vary =======================================================================
181
+
182
+ it 'does not return entries that Vary with #lookup' do
183
+ req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
184
+ req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
185
+ res = mock_response(200, {'Vary' => 'Foo Bar'}, ['test'])
186
+ @store.store(req1, res, @entity_store)
187
+
188
+ @store.lookup(req2, @entity_store).should be_nil
189
+ end
190
+
191
+ it 'stores multiple responses for each Vary combination' do
192
+ req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
193
+ res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
194
+ key = @store.store(req1, res1, @entity_store)
195
+
196
+ req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
197
+ res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
198
+ @store.store(req2, res2, @entity_store)
199
+
200
+ req3 = mock_request('/test', {'HTTP_FOO' => 'Baz', 'HTTP_BAR' => 'Boom'})
201
+ res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
202
+ @store.store(req3, res3, @entity_store)
203
+
204
+ slurp(@store.lookup(req3, @entity_store).body).should == 'test 3'
205
+ slurp(@store.lookup(req1, @entity_store).body).should == 'test 1'
206
+ slurp(@store.lookup(req2, @entity_store).body).should == 'test 2'
207
+
208
+ @store.read(key).length.should == 3
209
+ end
210
+
211
+ it 'overwrites non-varying responses with #store' do
212
+ req1 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
213
+ res1 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 1'])
214
+ key = @store.store(req1, res1, @entity_store)
215
+ slurp(@store.lookup(req1, @entity_store).body).should == 'test 1'
216
+
217
+ req2 = mock_request('/test', {'HTTP_FOO' => 'Bling', 'HTTP_BAR' => 'Bam'})
218
+ res2 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 2'])
219
+ @store.store(req2, res2, @entity_store)
220
+ slurp(@store.lookup(req2, @entity_store).body).should == 'test 2'
221
+
222
+ req3 = mock_request('/test', {'HTTP_FOO' => 'Foo', 'HTTP_BAR' => 'Bar'})
223
+ res3 = mock_response(200, {'Vary' => 'Foo Bar'}, ['test 3'])
224
+ @store.store(req3, res3, @entity_store)
225
+ slurp(@store.lookup(req1, @entity_store).body).should == 'test 3'
226
+
227
+ @store.read(key).length.should == 2
228
+ end
229
+
230
+ # Helper Methods =============================================================
231
+
232
+ define_method :mock_request do |uri,opts|
233
+ env = Rack::MockRequest.env_for(uri, opts || {})
234
+ Rack::Cache::Request.new(env)
235
+ end
236
+
237
+ define_method :mock_response do |status,headers,body|
238
+ headers ||= {}
239
+ body = Array(body).compact
240
+ Rack::Cache::Response.new(status, headers, body)
241
+ end
242
+
243
+ define_method :slurp do |body|
244
+ buf = ''
245
+ body.each {|part| buf << part }
246
+ buf
247
+ end
248
+
249
+ define_method :uri do |uri|
250
+ URI.parse uri
251
+ end
252
+ end
253
+ end
254
+ end
255
+ end
@@ -0,0 +1,234 @@
1
+ require 'spec_helper'
2
+
3
+ module Rack
4
+ module Session
5
+ describe "Rack::Session::Redis" do
6
+ before(:each) do
7
+ @session_key = Rack::Session::Redis::DEFAULT_OPTIONS[:key]
8
+ @session_match = /#{@session_key}=[0-9a-fA-F]+;/
9
+ @incrementor = lambda do |env|
10
+ env["rack.session"]["counter"] ||= 0
11
+ env["rack.session"]["counter"] += 1
12
+ Rack::Response.new(env["rack.session"].inspect).to_a
13
+ end
14
+ @drop_session = proc do |env|
15
+ env['rack.session.options'][:drop] = true
16
+ @incrementor.call(env)
17
+ end
18
+ @renew_session = proc do |env|
19
+ env['rack.session.options'][:renew] = true
20
+ @incrementor.call(env)
21
+ end
22
+ @defer_session = proc do |env|
23
+ env['rack.session.options'][:defer] = true
24
+ @incrementor.call(env)
25
+ end
26
+ end
27
+
28
+ it "should specify connection params" do
29
+ pool = Rack::Session::Redis.new(@incrementor, :redis_server => "redis://127.0.0.1:6380/1/theplaylist").pool
30
+ pool.should be_kind_of(::Redis::Store)
31
+ pool.to_s.should == "Redis Client connected to 127.0.0.1:6380 against DB 1 with namespace theplaylist"
32
+
33
+ pool = Rack::Session::Redis.new(@incrementor, :redis_server => ["redis://127.0.0.1:6379", "redis://127.0.0.1:6380"]).pool
34
+ pool.should be_kind_of(::Redis::DistributedStore)
35
+ end
36
+
37
+ it "creates a new cookie" do
38
+ pool = Rack::Session::Redis.new(@incrementor)
39
+ res = Rack::MockRequest.new(pool).get("/")
40
+ res["Set-Cookie"].should match(/#{@session_key}=/)
41
+ res.body.should == '{"counter"=>1}'
42
+ end
43
+
44
+ it "determines session from a cookie" do
45
+ pool = Rack::Session::Redis.new(@incrementor)
46
+ req = Rack::MockRequest.new(pool)
47
+ res = req.get("/")
48
+ cookie = res["Set-Cookie"]
49
+ req.get("/", "HTTP_COOKIE" => cookie).body.should == '{"counter"=>2}'
50
+ req.get("/", "HTTP_COOKIE" => cookie).body.should == '{"counter"=>3}'
51
+ end
52
+
53
+ it "survives nonexistant cookies" do
54
+ bad_cookie = "rack.session=blarghfasel"
55
+ pool = Rack::Session::Redis.new(@incrementor)
56
+ res = Rack::MockRequest.new(pool).
57
+ get("/", "HTTP_COOKIE" => bad_cookie)
58
+ res.body.should == '{"counter"=>1}'
59
+ cookie = res["Set-Cookie"][@session_match]
60
+ cookie.should_not match(/#{bad_cookie}/)
61
+ end
62
+
63
+ it "should maintain freshness" do
64
+ pool = Rack::Session::Redis.new(@incrementor, :expire_after => 3)
65
+ res = Rack::MockRequest.new(pool).get('/')
66
+ res.body.should include('"counter"=>1')
67
+ cookie = res["Set-Cookie"]
68
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
69
+ res["Set-Cookie"].should == cookie
70
+ res.body.should include('"counter"=>2')
71
+ puts 'Sleeping to expire session' if $DEBUG
72
+ sleep 4
73
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
74
+ res["Set-Cookie"].should_not == cookie
75
+ res.body.should include('"counter"=>1')
76
+ end
77
+
78
+ it "deletes cookies with :drop option" do
79
+ pool = Rack::Session::Redis.new(@incrementor)
80
+ req = Rack::MockRequest.new(pool)
81
+ drop = Rack::Utils::Context.new(pool, @drop_session)
82
+ dreq = Rack::MockRequest.new(drop)
83
+
84
+ res0 = req.get("/")
85
+ session = (cookie = res0["Set-Cookie"])[@session_match]
86
+ res0.body.should == '{"counter"=>1}'
87
+
88
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
89
+ res1["Set-Cookie"][@session_match].should == session
90
+ res1.body.should == '{"counter"=>2}'
91
+
92
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
93
+ res2["Set-Cookie"].should be_nil
94
+ res2.body.should == '{"counter"=>3}'
95
+
96
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
97
+ res3["Set-Cookie"][@session_match].should_not == session
98
+ res3.body.should == '{"counter"=>1}'
99
+ end
100
+
101
+ it "provides new session id with :renew option" do
102
+ pool = Rack::Session::Redis.new(@incrementor)
103
+ req = Rack::MockRequest.new(pool)
104
+ renew = Rack::Utils::Context.new(pool, @renew_session)
105
+ rreq = Rack::MockRequest.new(renew)
106
+
107
+ res0 = req.get("/")
108
+ session = (cookie = res0["Set-Cookie"])[@session_match]
109
+ res0.body.should == '{"counter"=>1}'
110
+
111
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
112
+ res1["Set-Cookie"][@session_match].should == session
113
+ res1.body.should == '{"counter"=>2}'
114
+
115
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
116
+ new_cookie = res2["Set-Cookie"]
117
+ new_session = new_cookie[@session_match]
118
+ new_session.should_not == session
119
+ res2.body.should == '{"counter"=>3}'
120
+
121
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
122
+ res3["Set-Cookie"][@session_match].should == new_session
123
+ res3.body.should == '{"counter"=>4}'
124
+ end
125
+
126
+ specify "omits cookie with :defer option" do
127
+ pool = Rack::Session::Redis.new(@incrementor)
128
+ req = Rack::MockRequest.new(pool)
129
+ defer = Rack::Utils::Context.new(pool, @defer_session)
130
+ dreq = Rack::MockRequest.new(defer)
131
+
132
+ res0 = req.get("/")
133
+ session = (cookie = res0["Set-Cookie"])[@session_match]
134
+ res0.body.should == '{"counter"=>1}'
135
+
136
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
137
+ res1["Set-Cookie"][@session_match].should == session
138
+ res1.body.should == '{"counter"=>2}'
139
+
140
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
141
+ res2["Set-Cookie"].should be_nil
142
+ res2.body.should == '{"counter"=>3}'
143
+
144
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
145
+ res3["Set-Cookie"][@session_match].should == session
146
+ res3.body.should == '{"counter"=>4}'
147
+ end
148
+
149
+ # anyone know how to do this better?
150
+ specify "multithread: should cleanly merge sessions" do
151
+ next unless $DEBUG
152
+ warn 'Running multithread test for Session::Redis'
153
+ pool = Rack::Session::Redis.new(@incrementor)
154
+ req = Rack::MockRequest.new(pool)
155
+
156
+ res = req.get('/')
157
+ res.body.should == '{"counter"=>1}'
158
+ cookie = res["Set-Cookie"]
159
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
160
+
161
+ delta_incrementor = lambda do |env|
162
+ # emulate disconjoinment of threading
163
+ env['rack.session'] = env['rack.session'].dup
164
+ Thread.stop
165
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
166
+ @incrementor.call(env)
167
+ end
168
+ tses = Rack::Utils::Context.new pool, delta_incrementor
169
+ treq = Rack::MockRequest.new(tses)
170
+ tnum = rand(7).to_i+5
171
+ r = Array.new(tnum) do
172
+ Thread.new(treq) do |run|
173
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
174
+ end
175
+ end.reverse.map{|t| t.run.join.value }
176
+ r.each do |res|
177
+ res['Set-Cookie'].should == cookie
178
+ res.body.should include('"counter"=>2')
179
+ end
180
+
181
+ session = pool.pool.get(sess_id)
182
+ session.size.should == tnum+1 # counter
183
+ session['counter'].should == 2 # meeeh
184
+
185
+ tnum = rand(7).to_i+5
186
+ r = Array.new(tnum) do |i|
187
+ delta_time = proc do |env|
188
+ env['rack.session'][i] = Time.now
189
+ Thread.stop
190
+ env['rack.session'] = env['rack.session'].dup
191
+ env['rack.session'][i] -= Time.now
192
+ @incrementor.call(env)
193
+ end
194
+ app = Rack::Utils::Context.new pool, time_delta
195
+ req = Rack::MockRequest.new app
196
+ Thread.new(req) do |run|
197
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
198
+ end
199
+ end.reverse.map{|t| t.run.join.value }
200
+ r.each do |res|
201
+ res['Set-Cookie'].should == cookie
202
+ res.body.should include('"counter"=>3')
203
+ end
204
+
205
+ session = pool.pool.get(sess_id)
206
+ session.size.should == tnum+1
207
+ session['counter'].should == 3
208
+
209
+ drop_counter = proc do |env|
210
+ env['rack.session'].del 'counter'
211
+ env['rack.session']['foo'] = 'bar'
212
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
213
+ end
214
+ tses = Rack::Utils::Context.new pool, drop_counter
215
+ treq = Rack::MockRequest.new(tses)
216
+ tnum = rand(7).to_i+5
217
+ r = Array.new(tnum) do
218
+ Thread.new(treq) do |run|
219
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
220
+ end
221
+ end.reverse.map{|t| t.run.join.value }
222
+ r.each do |res|
223
+ res['Set-Cookie'].should == cookie
224
+ res.body.should include('"foo"=>"bar"')
225
+ end
226
+
227
+ session = pool.pool.get(sess_id)
228
+ session.size.should == r.size+1
229
+ session['counter'].should be_nil
230
+ session['foo'].should == 'bar'
231
+ end
232
+ end
233
+ end
234
+ end