kjvarga-rack 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (112) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES.rdoc +18 -0
  3. data/Manifest +117 -0
  4. data/README.rdoc +357 -0
  5. data/Rakefile +164 -0
  6. data/bin/rackup +176 -0
  7. data/contrib/rack_logo.svg +111 -0
  8. data/lib/rack.rb +90 -0
  9. data/lib/rack/adapter/camping.rb +22 -0
  10. data/lib/rack/auth/abstract/handler.rb +37 -0
  11. data/lib/rack/auth/abstract/request.rb +37 -0
  12. data/lib/rack/auth/basic.rb +58 -0
  13. data/lib/rack/auth/digest/md5.rb +124 -0
  14. data/lib/rack/auth/digest/nonce.rb +51 -0
  15. data/lib/rack/auth/digest/params.rb +55 -0
  16. data/lib/rack/auth/digest/request.rb +40 -0
  17. data/lib/rack/auth/openid.rb +487 -0
  18. data/lib/rack/builder.rb +63 -0
  19. data/lib/rack/cascade.rb +41 -0
  20. data/lib/rack/chunked.rb +49 -0
  21. data/lib/rack/commonlogger.rb +52 -0
  22. data/lib/rack/conditionalget.rb +47 -0
  23. data/lib/rack/content_length.rb +29 -0
  24. data/lib/rack/content_type.rb +23 -0
  25. data/lib/rack/deflater.rb +96 -0
  26. data/lib/rack/directory.rb +153 -0
  27. data/lib/rack/file.rb +88 -0
  28. data/lib/rack/handler.rb +69 -0
  29. data/lib/rack/handler/cgi.rb +61 -0
  30. data/lib/rack/handler/evented_mongrel.rb +8 -0
  31. data/lib/rack/handler/fastcgi.rb +88 -0
  32. data/lib/rack/handler/lsws.rb +60 -0
  33. data/lib/rack/handler/mongrel.rb +87 -0
  34. data/lib/rack/handler/scgi.rb +62 -0
  35. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  36. data/lib/rack/handler/thin.rb +18 -0
  37. data/lib/rack/handler/webrick.rb +71 -0
  38. data/lib/rack/head.rb +19 -0
  39. data/lib/rack/lint.rb +546 -0
  40. data/lib/rack/lobster.rb +65 -0
  41. data/lib/rack/lock.rb +16 -0
  42. data/lib/rack/methodoverride.rb +27 -0
  43. data/lib/rack/mime.rb +205 -0
  44. data/lib/rack/mock.rb +187 -0
  45. data/lib/rack/recursive.rb +57 -0
  46. data/lib/rack/reloader.rb +109 -0
  47. data/lib/rack/request.rb +248 -0
  48. data/lib/rack/response.rb +183 -0
  49. data/lib/rack/rewindable_input.rb +100 -0
  50. data/lib/rack/session/abstract/id.rb +142 -0
  51. data/lib/rack/session/cookie.rb +91 -0
  52. data/lib/rack/session/memcache.rb +109 -0
  53. data/lib/rack/session/pool.rb +100 -0
  54. data/lib/rack/showexceptions.rb +349 -0
  55. data/lib/rack/showstatus.rb +106 -0
  56. data/lib/rack/static.rb +38 -0
  57. data/lib/rack/urlmap.rb +55 -0
  58. data/lib/rack/utils.rb +528 -0
  59. data/rack.gemspec +140 -0
  60. data/test/cgi/lighttpd.conf +20 -0
  61. data/test/cgi/test +9 -0
  62. data/test/cgi/test.fcgi +8 -0
  63. data/test/cgi/test.ru +7 -0
  64. data/test/multipart/binary +0 -0
  65. data/test/multipart/empty +10 -0
  66. data/test/multipart/file1.txt +1 -0
  67. data/test/multipart/ie +6 -0
  68. data/test/multipart/nested +10 -0
  69. data/test/multipart/none +9 -0
  70. data/test/multipart/text +10 -0
  71. data/test/spec_rack_auth_basic.rb +73 -0
  72. data/test/spec_rack_auth_digest.rb +226 -0
  73. data/test/spec_rack_auth_openid.rb +84 -0
  74. data/test/spec_rack_builder.rb +84 -0
  75. data/test/spec_rack_camping.rb +51 -0
  76. data/test/spec_rack_cascade.rb +48 -0
  77. data/test/spec_rack_cgi.rb +89 -0
  78. data/test/spec_rack_chunked.rb +62 -0
  79. data/test/spec_rack_commonlogger.rb +61 -0
  80. data/test/spec_rack_conditionalget.rb +41 -0
  81. data/test/spec_rack_content_length.rb +43 -0
  82. data/test/spec_rack_content_type.rb +30 -0
  83. data/test/spec_rack_deflater.rb +127 -0
  84. data/test/spec_rack_directory.rb +61 -0
  85. data/test/spec_rack_fastcgi.rb +89 -0
  86. data/test/spec_rack_file.rb +75 -0
  87. data/test/spec_rack_handler.rb +43 -0
  88. data/test/spec_rack_head.rb +30 -0
  89. data/test/spec_rack_lint.rb +521 -0
  90. data/test/spec_rack_lobster.rb +45 -0
  91. data/test/spec_rack_lock.rb +38 -0
  92. data/test/spec_rack_methodoverride.rb +60 -0
  93. data/test/spec_rack_mock.rb +243 -0
  94. data/test/spec_rack_mongrel.rb +189 -0
  95. data/test/spec_rack_recursive.rb +77 -0
  96. data/test/spec_rack_request.rb +504 -0
  97. data/test/spec_rack_response.rb +218 -0
  98. data/test/spec_rack_rewindable_input.rb +118 -0
  99. data/test/spec_rack_session_cookie.rb +82 -0
  100. data/test/spec_rack_session_memcache.rb +250 -0
  101. data/test/spec_rack_session_pool.rb +172 -0
  102. data/test/spec_rack_showexceptions.rb +21 -0
  103. data/test/spec_rack_showstatus.rb +72 -0
  104. data/test/spec_rack_static.rb +37 -0
  105. data/test/spec_rack_thin.rb +91 -0
  106. data/test/spec_rack_urlmap.rb +185 -0
  107. data/test/spec_rack_utils.rb +467 -0
  108. data/test/spec_rack_webrick.rb +130 -0
  109. data/test/testrequest.rb +57 -0
  110. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  111. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  112. metadata +175 -0
@@ -0,0 +1,218 @@
1
+ require 'test/spec'
2
+ require 'set'
3
+
4
+ require 'rack/response'
5
+
6
+ context "Rack::Response" do
7
+ specify "has sensible default values" do
8
+ response = Rack::Response.new
9
+ status, header, body = response.finish
10
+ status.should.equal 200
11
+ header.should.equal "Content-Type" => "text/html"
12
+ body.each { |part|
13
+ part.should.equal ""
14
+ }
15
+
16
+ response = Rack::Response.new
17
+ status, header, body = *response
18
+ status.should.equal 200
19
+ header.should.equal "Content-Type" => "text/html"
20
+ body.each { |part|
21
+ part.should.equal ""
22
+ }
23
+ end
24
+
25
+ specify "can be written to" do
26
+ response = Rack::Response.new
27
+
28
+ status, header, body = response.finish do
29
+ response.write "foo"
30
+ response.write "bar"
31
+ response.write "baz"
32
+ end
33
+
34
+ parts = []
35
+ body.each { |part| parts << part }
36
+
37
+ parts.should.equal ["foo", "bar", "baz"]
38
+ end
39
+
40
+ specify "can set and read headers" do
41
+ response = Rack::Response.new
42
+ response["Content-Type"].should.equal "text/html"
43
+ response["Content-Type"] = "text/plain"
44
+ response["Content-Type"].should.equal "text/plain"
45
+ end
46
+
47
+ specify "can set cookies" do
48
+ response = Rack::Response.new
49
+
50
+ response.set_cookie "foo", "bar"
51
+ response["Set-Cookie"].should.equal "foo=bar"
52
+ response.set_cookie "foo2", "bar2"
53
+ response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2"]
54
+ response.set_cookie "foo3", "bar3"
55
+ response["Set-Cookie"].should.equal ["foo=bar", "foo2=bar2", "foo3=bar3"]
56
+ end
57
+
58
+ specify "formats the Cookie expiration date accordingly to RFC 2109" do
59
+ response = Rack::Response.new
60
+
61
+ response.set_cookie "foo", {:value => "bar", :expires => Time.now+10}
62
+ response["Set-Cookie"].should.match(
63
+ /expires=..., \d\d-...-\d\d\d\d \d\d:\d\d:\d\d .../)
64
+ end
65
+
66
+ specify "can set secure cookies" do
67
+ response = Rack::Response.new
68
+ response.set_cookie "foo", {:value => "bar", :secure => true}
69
+ response["Set-Cookie"].should.equal "foo=bar; secure"
70
+ end
71
+
72
+ specify "can set http only cookies" do
73
+ response = Rack::Response.new
74
+ response.set_cookie "foo", {:value => "bar", :httponly => true}
75
+ response["Set-Cookie"].should.equal "foo=bar; HttpOnly"
76
+ end
77
+
78
+ specify "can delete cookies" do
79
+ response = Rack::Response.new
80
+ response.set_cookie "foo", "bar"
81
+ response.set_cookie "foo2", "bar2"
82
+ response.delete_cookie "foo"
83
+ response["Set-Cookie"].should.equal ["foo2=bar2",
84
+ "foo=; expires=Thu, 01-Jan-1970 00:00:00 GMT"]
85
+ end
86
+
87
+ specify "can do redirects" do
88
+ response = Rack::Response.new
89
+ response.redirect "/foo"
90
+ status, header, body = response.finish
91
+
92
+ status.should.equal 302
93
+ header["Location"].should.equal "/foo"
94
+
95
+ response = Rack::Response.new
96
+ response.redirect "/foo", 307
97
+ status, header, body = response.finish
98
+
99
+ status.should.equal 307
100
+ end
101
+
102
+ specify "has a useful constructor" do
103
+ r = Rack::Response.new("foo")
104
+ status, header, body = r.finish
105
+ str = ""; body.each { |part| str << part }
106
+ str.should.equal "foo"
107
+
108
+ r = Rack::Response.new(["foo", "bar"])
109
+ status, header, body = r.finish
110
+ str = ""; body.each { |part| str << part }
111
+ str.should.equal "foobar"
112
+
113
+ r = Rack::Response.new(["foo", "bar"].to_set)
114
+ r.write "foo"
115
+ status, header, body = r.finish
116
+ str = ""; body.each { |part| str << part }
117
+ str.should.equal "foobarfoo"
118
+
119
+ r = Rack::Response.new([], 500)
120
+ r.status.should.equal 500
121
+ end
122
+
123
+ specify "has a constructor that can take a block" do
124
+ r = Rack::Response.new { |res|
125
+ res.status = 404
126
+ res.write "foo"
127
+ }
128
+ status, header, body = r.finish
129
+ str = ""; body.each { |part| str << part }
130
+ str.should.equal "foo"
131
+ status.should.equal 404
132
+ end
133
+
134
+ specify "doesn't return invalid responses" do
135
+ r = Rack::Response.new(["foo", "bar"], 204)
136
+ status, header, body = r.finish
137
+ str = ""; body.each { |part| str << part }
138
+ str.should.be.empty
139
+ header["Content-Type"].should.equal nil
140
+
141
+ lambda {
142
+ Rack::Response.new(Object.new)
143
+ }.should.raise(TypeError).
144
+ message.should =~ /stringable or iterable required/
145
+ end
146
+
147
+ specify "knows if it's empty" do
148
+ r = Rack::Response.new
149
+ r.should.be.empty
150
+ r.write "foo"
151
+ r.should.not.be.empty
152
+
153
+ r = Rack::Response.new
154
+ r.should.be.empty
155
+ r.finish
156
+ r.should.be.empty
157
+
158
+ r = Rack::Response.new
159
+ r.should.be.empty
160
+ r.finish { }
161
+ r.should.not.be.empty
162
+ end
163
+
164
+ specify "should provide access to the HTTP status" do
165
+ res = Rack::Response.new
166
+ res.status = 200
167
+ res.should.be.successful
168
+ res.should.be.ok
169
+
170
+ res.status = 404
171
+ res.should.not.be.successful
172
+ res.should.be.client_error
173
+ res.should.be.not_found
174
+
175
+ res.status = 501
176
+ res.should.not.be.successful
177
+ res.should.be.server_error
178
+
179
+ res.status = 307
180
+ res.should.be.redirect
181
+ end
182
+
183
+ specify "should provide access to the HTTP headers" do
184
+ res = Rack::Response.new
185
+ res["Content-Type"] = "text/yaml"
186
+
187
+ res.should.include "Content-Type"
188
+ res.headers["Content-Type"].should.equal "text/yaml"
189
+ res["Content-Type"].should.equal "text/yaml"
190
+ res.content_type.should.equal "text/yaml"
191
+ res.content_length.should.be.nil
192
+ res.location.should.be.nil
193
+ end
194
+
195
+ specify "does not add or change Content-Length when #finish()ing" do
196
+ res = Rack::Response.new
197
+ res.status = 200
198
+ res.finish
199
+ res.headers["Content-Length"].should.be.nil
200
+
201
+ res = Rack::Response.new
202
+ res.status = 200
203
+ res.headers["Content-Length"] = "10"
204
+ res.finish
205
+ res.headers["Content-Length"].should.equal "10"
206
+ end
207
+
208
+ specify "updates Content-Length when body appended to using #write" do
209
+ res = Rack::Response.new
210
+ res.status = 200
211
+ res.headers["Content-Length"].should.be.nil
212
+ res.write "Hi"
213
+ res.headers["Content-Length"].should.equal "2"
214
+ res.write " there"
215
+ res.headers["Content-Length"].should.equal "8"
216
+ end
217
+
218
+ end
@@ -0,0 +1,118 @@
1
+ require 'test/spec'
2
+ require 'stringio'
3
+ require 'rack/rewindable_input'
4
+
5
+ shared_context "a rewindable IO object" do
6
+ setup do
7
+ @rio = Rack::RewindableInput.new(@io)
8
+ end
9
+
10
+ teardown do
11
+ @rio.close
12
+ end
13
+
14
+ specify "should be able to handle to read()" do
15
+ @rio.read.should.equal "hello world"
16
+ end
17
+
18
+ specify "should be able to handle to read(nil)" do
19
+ @rio.read(nil).should.equal "hello world"
20
+ end
21
+
22
+ specify "should be able to handle to read(length)" do
23
+ @rio.read(1).should.equal "h"
24
+ end
25
+
26
+ specify "should be able to handle to read(length, buffer)" do
27
+ buffer = ""
28
+ result = @rio.read(1, buffer)
29
+ result.should.equal "h"
30
+ result.object_id.should.equal buffer.object_id
31
+ end
32
+
33
+ specify "should be able to handle to read(nil, buffer)" do
34
+ buffer = ""
35
+ result = @rio.read(nil, buffer)
36
+ result.should.equal "hello world"
37
+ result.object_id.should.equal buffer.object_id
38
+ end
39
+
40
+ specify "should rewind to the beginning when #rewind is called" do
41
+ @rio.read(1)
42
+ @rio.rewind
43
+ @rio.read.should.equal "hello world"
44
+ end
45
+
46
+ specify "should be able to handle gets" do
47
+ @rio.gets.should == "hello world"
48
+ end
49
+
50
+ specify "should be able to handle each" do
51
+ array = []
52
+ @rio.each do |data|
53
+ array << data
54
+ end
55
+ array.should.equal(["hello world"])
56
+ end
57
+
58
+ specify "should not buffer into a Tempfile if no data has been read yet" do
59
+ @rio.instance_variable_get(:@rewindable_io).should.be.nil
60
+ end
61
+
62
+ specify "should buffer into a Tempfile when data has been consumed for the first time" do
63
+ @rio.read(1)
64
+ tempfile = @rio.instance_variable_get(:@rewindable_io)
65
+ tempfile.should.not.be.nil
66
+ @rio.read(1)
67
+ tempfile2 = @rio.instance_variable_get(:@rewindable_io)
68
+ tempfile2.should.equal tempfile
69
+ end
70
+
71
+ specify "should close the underlying tempfile upon calling #close" do
72
+ @rio.read(1)
73
+ tempfile = @rio.instance_variable_get(:@rewindable_io)
74
+ @rio.close
75
+ tempfile.should.be.closed
76
+ end
77
+
78
+ specify "should be possibel to call #close when no data has been buffered yet" do
79
+ @rio.close
80
+ end
81
+
82
+ specify "should be possible to call #close multiple times" do
83
+ @rio.close
84
+ @rio.close
85
+ end
86
+ end
87
+
88
+ context "Rack::RewindableInput" do
89
+ context "given an IO object that is already rewindable" do
90
+ setup do
91
+ @io = StringIO.new("hello world")
92
+ end
93
+
94
+ it_should_behave_like "a rewindable IO object"
95
+ end
96
+
97
+ context "given an IO object that is not rewindable" do
98
+ setup do
99
+ @io = StringIO.new("hello world")
100
+ @io.instance_eval do
101
+ undef :rewind
102
+ end
103
+ end
104
+
105
+ it_should_behave_like "a rewindable IO object"
106
+ end
107
+
108
+ context "given an IO object whose rewind method raises Errno::ESPIPE" do
109
+ setup do
110
+ @io = StringIO.new("hello world")
111
+ def @io.rewind
112
+ raise Errno::ESPIPE, "You can't rewind this!"
113
+ end
114
+ end
115
+
116
+ it_should_behave_like "a rewindable IO object"
117
+ end
118
+ end
@@ -0,0 +1,82 @@
1
+ require 'test/spec'
2
+
3
+ require 'rack/session/cookie'
4
+ require 'rack/mock'
5
+ require 'rack/response'
6
+
7
+ context "Rack::Session::Cookie" do
8
+ incrementor = lambda { |env|
9
+ env["rack.session"]["counter"] ||= 0
10
+ env["rack.session"]["counter"] += 1
11
+ Rack::Response.new(env["rack.session"].inspect).to_a
12
+ }
13
+
14
+ specify "creates a new cookie" do
15
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
16
+ res["Set-Cookie"].should.match("rack.session=")
17
+ res.body.should.equal '{"counter"=>1}'
18
+ end
19
+
20
+ specify "loads from a cookie" do
21
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).get("/")
22
+ cookie = res["Set-Cookie"]
23
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
24
+ get("/", "HTTP_COOKIE" => cookie)
25
+ res.body.should.equal '{"counter"=>2}'
26
+ cookie = res["Set-Cookie"]
27
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
28
+ get("/", "HTTP_COOKIE" => cookie)
29
+ res.body.should.equal '{"counter"=>3}'
30
+ end
31
+
32
+ specify "survives broken cookies" do
33
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor)).
34
+ get("/", "HTTP_COOKIE" => "rack.session=blarghfasel")
35
+ res.body.should.equal '{"counter"=>1}'
36
+ end
37
+
38
+ bigcookie = lambda { |env|
39
+ env["rack.session"]["cookie"] = "big" * 3000
40
+ Rack::Response.new(env["rack.session"].inspect).to_a
41
+ }
42
+
43
+ specify "barks on too big cookies" do
44
+ lambda {
45
+ Rack::MockRequest.new(Rack::Session::Cookie.new(bigcookie)).
46
+ get("/", :fatal => true)
47
+ }.should.raise(Rack::MockRequest::FatalWarning)
48
+ end
49
+
50
+ specify "creates a new cookie with integrity hash" do
51
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
52
+ if RUBY_VERSION < "1.9"
53
+ res["Set-Cookie"].should.match("rack.session=BAh7BiIMY291bnRlcmkG%0A--1439b4d37b9d4b04c603848382f712d6fcd31088")
54
+ else
55
+ res["Set-Cookie"].should.match("rack.session=BAh7BkkiDGNvdW50ZXIGOg1lbmNvZGluZyINVVMtQVNDSUlpBg%3D%3D%0A--d7a6637b94d2728194a96c18484e1f7ed9074a83")
56
+ end
57
+ end
58
+
59
+ specify "loads from a cookie wih integrity hash" do
60
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).get("/")
61
+ cookie = res["Set-Cookie"]
62
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
63
+ get("/", "HTTP_COOKIE" => cookie)
64
+ res.body.should.equal '{"counter"=>2}'
65
+ cookie = res["Set-Cookie"]
66
+ res = Rack::MockRequest.new(Rack::Session::Cookie.new(incrementor, :secret => 'test')).
67
+ get("/", "HTTP_COOKIE" => cookie)
68
+ res.body.should.equal '{"counter"=>3}'
69
+ end
70
+
71
+ specify "ignores tampered with session cookies" do
72
+ app = Rack::Session::Cookie.new(incrementor, :secret => 'test')
73
+ response1 = Rack::MockRequest.new(app).get("/")
74
+ _, digest = response1["Set-Cookie"].split("--")
75
+ tampered_with_cookie = "hackerman-was-here" + "--" + digest
76
+ response2 = Rack::MockRequest.new(app).get("/", "HTTP_COOKIE" =>
77
+ tampered_with_cookie)
78
+
79
+ # The tampered-with cookie is ignored, so we get back an identical Set-Cookie
80
+ response2["Set-Cookie"].should.equal(response1["Set-Cookie"])
81
+ end
82
+ end
@@ -0,0 +1,250 @@
1
+ require 'test/spec'
2
+
3
+ begin
4
+ require 'rack/session/memcache'
5
+ require 'rack/mock'
6
+ require 'rack/response'
7
+ require 'thread'
8
+
9
+ pool = Rack::Session::Memcache.new(lambda {})
10
+
11
+ context "Rack::Session::Memcache" do
12
+ session_key = Rack::Session::Memcache::DEFAULT_OPTIONS[:key]
13
+ session_match = /#{session_key}=[0-9a-fA-F]+;/
14
+ incrementor = lambda do |env|
15
+ env["rack.session"]["counter"] ||= 0
16
+ env["rack.session"]["counter"] += 1
17
+ Rack::Response.new(env["rack.session"].inspect).to_a
18
+ end
19
+ drop_session = proc do |env|
20
+ env['rack.session.options'][:drop] = true
21
+ incrementor.call(env)
22
+ end
23
+ renew_session = proc do |env|
24
+ env['rack.session.options'][:renew] = true
25
+ incrementor.call(env)
26
+ end
27
+ defer_session = proc do |env|
28
+ env['rack.session.options'][:defer] = true
29
+ incrementor.call(env)
30
+ end
31
+
32
+ specify "MemCache can connect to existing server" do
33
+ test_pool = MemCache.new :namespace => 'test:rack:session'
34
+ end
35
+
36
+ specify "faults on no connection" do
37
+ if RUBY_VERSION < "1.9"
38
+ lambda do
39
+ Rack::Session::Memcache.new(incrementor, :memcache_server => '')
40
+ end.should.raise
41
+ else
42
+ lambda do
43
+ Rack::Session::Memcache.new(incrementor, :memcache_server => '')
44
+ end.should.raise ArgumentError
45
+ end
46
+ end
47
+
48
+ specify "creates a new cookie" do
49
+ pool = Rack::Session::Memcache.new(incrementor)
50
+ res = Rack::MockRequest.new(pool).get("/")
51
+ res["Set-Cookie"].should.match("#{session_key}=")
52
+ res.body.should.equal '{"counter"=>1}'
53
+ end
54
+
55
+ specify "determines session from a cookie" do
56
+ pool = Rack::Session::Memcache.new(incrementor)
57
+ req = Rack::MockRequest.new(pool)
58
+ res = req.get("/")
59
+ cookie = res["Set-Cookie"]
60
+ req.get("/", "HTTP_COOKIE" => cookie).
61
+ body.should.equal '{"counter"=>2}'
62
+ req.get("/", "HTTP_COOKIE" => cookie).
63
+ body.should.equal '{"counter"=>3}'
64
+ end
65
+
66
+ specify "survives nonexistant cookies" do
67
+ bad_cookie = "rack.session=blarghfasel"
68
+ pool = Rack::Session::Memcache.new(incrementor)
69
+ res = Rack::MockRequest.new(pool).
70
+ get("/", "HTTP_COOKIE" => bad_cookie)
71
+ res.body.should.equal '{"counter"=>1}'
72
+ cookie = res["Set-Cookie"][session_match]
73
+ cookie.should.not.match(/#{bad_cookie}/)
74
+ end
75
+
76
+ specify "maintains freshness" do
77
+ pool = Rack::Session::Memcache.new(incrementor, :expire_after => 3)
78
+ res = Rack::MockRequest.new(pool).get('/')
79
+ res.body.should.include '"counter"=>1'
80
+ cookie = res["Set-Cookie"]
81
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
82
+ res["Set-Cookie"].should.equal cookie
83
+ res.body.should.include '"counter"=>2'
84
+ puts 'Sleeping to expire session' if $DEBUG
85
+ sleep 4
86
+ res = Rack::MockRequest.new(pool).get('/', "HTTP_COOKIE" => cookie)
87
+ res["Set-Cookie"].should.not.equal cookie
88
+ res.body.should.include '"counter"=>1'
89
+ end
90
+
91
+ specify "deletes cookies with :drop option" do
92
+ pool = Rack::Session::Memcache.new(incrementor)
93
+ req = Rack::MockRequest.new(pool)
94
+ drop = Rack::Utils::Context.new(pool, drop_session)
95
+ dreq = Rack::MockRequest.new(drop)
96
+
97
+ res0 = req.get("/")
98
+ session = (cookie = res0["Set-Cookie"])[session_match]
99
+ res0.body.should.equal '{"counter"=>1}'
100
+
101
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
102
+ res1["Set-Cookie"][session_match].should.equal session
103
+ res1.body.should.equal '{"counter"=>2}'
104
+
105
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
106
+ res2["Set-Cookie"].should.equal nil
107
+ res2.body.should.equal '{"counter"=>3}'
108
+
109
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
110
+ res3["Set-Cookie"][session_match].should.not.equal session
111
+ res3.body.should.equal '{"counter"=>1}'
112
+ end
113
+
114
+ specify "provides new session id with :renew option" do
115
+ pool = Rack::Session::Memcache.new(incrementor)
116
+ req = Rack::MockRequest.new(pool)
117
+ renew = Rack::Utils::Context.new(pool, renew_session)
118
+ rreq = Rack::MockRequest.new(renew)
119
+
120
+ res0 = req.get("/")
121
+ session = (cookie = res0["Set-Cookie"])[session_match]
122
+ res0.body.should.equal '{"counter"=>1}'
123
+
124
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
125
+ res1["Set-Cookie"][session_match].should.equal session
126
+ res1.body.should.equal '{"counter"=>2}'
127
+
128
+ res2 = rreq.get("/", "HTTP_COOKIE" => cookie)
129
+ new_cookie = res2["Set-Cookie"]
130
+ new_session = new_cookie[session_match]
131
+ new_session.should.not.equal session
132
+ res2.body.should.equal '{"counter"=>3}'
133
+
134
+ res3 = req.get("/", "HTTP_COOKIE" => new_cookie)
135
+ res3["Set-Cookie"][session_match].should.equal new_session
136
+ res3.body.should.equal '{"counter"=>4}'
137
+ end
138
+
139
+ specify "omits cookie with :defer option" do
140
+ pool = Rack::Session::Memcache.new(incrementor)
141
+ req = Rack::MockRequest.new(pool)
142
+ defer = Rack::Utils::Context.new(pool, defer_session)
143
+ dreq = Rack::MockRequest.new(defer)
144
+
145
+ res0 = req.get("/")
146
+ session = (cookie = res0["Set-Cookie"])[session_match]
147
+ res0.body.should.equal '{"counter"=>1}'
148
+
149
+ res1 = req.get("/", "HTTP_COOKIE" => cookie)
150
+ res1["Set-Cookie"][session_match].should.equal session
151
+ res1.body.should.equal '{"counter"=>2}'
152
+
153
+ res2 = dreq.get("/", "HTTP_COOKIE" => cookie)
154
+ res2["Set-Cookie"].should.equal nil
155
+ res2.body.should.equal '{"counter"=>3}'
156
+
157
+ res3 = req.get("/", "HTTP_COOKIE" => cookie)
158
+ res3["Set-Cookie"][session_match].should.equal session
159
+ res3.body.should.equal '{"counter"=>4}'
160
+ end
161
+
162
+ # anyone know how to do this better?
163
+ specify "multithread: should cleanly merge sessions" do
164
+ next unless $DEBUG
165
+ warn 'Running multithread test for Session::Memcache'
166
+ pool = Rack::Session::Memcache.new(incrementor)
167
+ req = Rack::MockRequest.new(pool)
168
+
169
+ res = req.get('/')
170
+ res.body.should.equal '{"counter"=>1}'
171
+ cookie = res["Set-Cookie"]
172
+ sess_id = cookie[/#{pool.key}=([^,;]+)/,1]
173
+
174
+ delta_incrementor = lambda do |env|
175
+ # emulate disconjoinment of threading
176
+ env['rack.session'] = env['rack.session'].dup
177
+ Thread.stop
178
+ env['rack.session'][(Time.now.usec*rand).to_i] = true
179
+ incrementor.call(env)
180
+ end
181
+ tses = Rack::Utils::Context.new pool, delta_incrementor
182
+ treq = Rack::MockRequest.new(tses)
183
+ tnum = rand(7).to_i+5
184
+ r = Array.new(tnum) do
185
+ Thread.new(treq) do |run|
186
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
187
+ end
188
+ end.reverse.map{|t| t.run.join.value }
189
+ r.each do |request|
190
+ request['Set-Cookie'].should.equal cookie
191
+ request.body.should.include '"counter"=>2'
192
+ end
193
+
194
+ session = pool.pool.get(sess_id)
195
+ session.size.should.be tnum+1 # counter
196
+ session['counter'].should.be 2 # meeeh
197
+
198
+ tnum = rand(7).to_i+5
199
+ r = Array.new(tnum) do |i|
200
+ delta_time = proc do |env|
201
+ env['rack.session'][i] = Time.now
202
+ Thread.stop
203
+ env['rack.session'] = env['rack.session'].dup
204
+ env['rack.session'][i] -= Time.now
205
+ incrementor.call(env)
206
+ end
207
+ app = Rack::Utils::Context.new pool, time_delta
208
+ req = Rack::MockRequest.new app
209
+ Thread.new(req) do |run|
210
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
211
+ end
212
+ end.reverse.map{|t| t.run.join.value }
213
+ r.each do |request|
214
+ request['Set-Cookie'].should.equal cookie
215
+ request.body.should.include '"counter"=>3'
216
+ end
217
+
218
+ session = pool.pool.get(sess_id)
219
+ session.size.should.be tnum+1
220
+ session['counter'].should.be 3
221
+
222
+ drop_counter = proc do |env|
223
+ env['rack.session'].delete 'counter'
224
+ env['rack.session']['foo'] = 'bar'
225
+ [200, {'Content-Type'=>'text/plain'}, env['rack.session'].inspect]
226
+ end
227
+ tses = Rack::Utils::Context.new pool, drop_counter
228
+ treq = Rack::MockRequest.new(tses)
229
+ tnum = rand(7).to_i+5
230
+ r = Array.new(tnum) do
231
+ Thread.new(treq) do |run|
232
+ run.get('/', "HTTP_COOKIE" => cookie, 'rack.multithread' => true)
233
+ end
234
+ end.reverse.map{|t| t.run.join.value }
235
+ r.each do |request|
236
+ request['Set-Cookie'].should.equal cookie
237
+ request.body.should.include '"foo"=>"bar"'
238
+ end
239
+
240
+ session = pool.pool.get(sess_id)
241
+ session.size.should.be r.size+1
242
+ session['counter'].should.be.nil?
243
+ session['foo'].should.equal 'bar'
244
+ end
245
+ end
246
+ rescue RuntimeError
247
+ $stderr.puts "Skipping Rack::Session::Memcache tests. Start memcached and try again."
248
+ rescue LoadError
249
+ $stderr.puts "Skipping Rack::Session::Memcache tests (Memcache is required). `gem install memcache-client` and try again."
250
+ end