qoobaa-rack 1.0.0.1

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 (116) hide show
  1. data/COPYING +18 -0
  2. data/KNOWN-ISSUES +18 -0
  3. data/RDOX +0 -0
  4. data/README +353 -0
  5. data/Rakefile +164 -0
  6. data/SPEC +164 -0
  7. data/bin/rackup +176 -0
  8. data/contrib/rack_logo.svg +111 -0
  9. data/example/lobster.ru +4 -0
  10. data/example/protectedlobster.rb +14 -0
  11. data/example/protectedlobster.ru +8 -0
  12. data/lib/rack/adapter/camping.rb +22 -0
  13. data/lib/rack/auth/abstract/handler.rb +37 -0
  14. data/lib/rack/auth/abstract/request.rb +37 -0
  15. data/lib/rack/auth/basic.rb +58 -0
  16. data/lib/rack/auth/digest/md5.rb +124 -0
  17. data/lib/rack/auth/digest/nonce.rb +51 -0
  18. data/lib/rack/auth/digest/params.rb +55 -0
  19. data/lib/rack/auth/digest/request.rb +40 -0
  20. data/lib/rack/auth/openid.rb +487 -0
  21. data/lib/rack/builder.rb +63 -0
  22. data/lib/rack/cascade.rb +41 -0
  23. data/lib/rack/chunked.rb +49 -0
  24. data/lib/rack/commonlogger.rb +52 -0
  25. data/lib/rack/conditionalget.rb +47 -0
  26. data/lib/rack/content_length.rb +29 -0
  27. data/lib/rack/content_type.rb +23 -0
  28. data/lib/rack/deflater.rb +96 -0
  29. data/lib/rack/directory.rb +153 -0
  30. data/lib/rack/file.rb +88 -0
  31. data/lib/rack/handler/cgi.rb +61 -0
  32. data/lib/rack/handler/evented_mongrel.rb +8 -0
  33. data/lib/rack/handler/fastcgi.rb +88 -0
  34. data/lib/rack/handler/lsws.rb +60 -0
  35. data/lib/rack/handler/mongrel.rb +87 -0
  36. data/lib/rack/handler/scgi.rb +62 -0
  37. data/lib/rack/handler/swiftiplied_mongrel.rb +8 -0
  38. data/lib/rack/handler/thin.rb +18 -0
  39. data/lib/rack/handler/webrick.rb +71 -0
  40. data/lib/rack/handler.rb +69 -0
  41. data/lib/rack/head.rb +19 -0
  42. data/lib/rack/lint.rb +546 -0
  43. data/lib/rack/lobster.rb +65 -0
  44. data/lib/rack/lock.rb +16 -0
  45. data/lib/rack/methodoverride.rb +27 -0
  46. data/lib/rack/mime.rb +204 -0
  47. data/lib/rack/mock.rb +187 -0
  48. data/lib/rack/recursive.rb +57 -0
  49. data/lib/rack/reloader.rb +107 -0
  50. data/lib/rack/request.rb +248 -0
  51. data/lib/rack/response.rb +183 -0
  52. data/lib/rack/rewindable_input.rb +100 -0
  53. data/lib/rack/session/abstract/id.rb +142 -0
  54. data/lib/rack/session/cookie.rb +91 -0
  55. data/lib/rack/session/memcache.rb +109 -0
  56. data/lib/rack/session/pool.rb +100 -0
  57. data/lib/rack/showexceptions.rb +349 -0
  58. data/lib/rack/showstatus.rb +106 -0
  59. data/lib/rack/static.rb +38 -0
  60. data/lib/rack/urlmap.rb +55 -0
  61. data/lib/rack/utils.rb +528 -0
  62. data/lib/rack.rb +90 -0
  63. data/rack.gemspec +60 -0
  64. data/test/cgi/lighttpd.conf +20 -0
  65. data/test/cgi/test +9 -0
  66. data/test/cgi/test.fcgi +8 -0
  67. data/test/cgi/test.ru +7 -0
  68. data/test/multipart/binary +0 -0
  69. data/test/multipart/empty +10 -0
  70. data/test/multipart/file1.txt +1 -0
  71. data/test/multipart/ie +6 -0
  72. data/test/multipart/nested +10 -0
  73. data/test/multipart/none +9 -0
  74. data/test/multipart/text +10 -0
  75. data/test/spec_rack_auth_basic.rb +73 -0
  76. data/test/spec_rack_auth_digest.rb +226 -0
  77. data/test/spec_rack_auth_openid.rb +84 -0
  78. data/test/spec_rack_builder.rb +84 -0
  79. data/test/spec_rack_camping.rb +51 -0
  80. data/test/spec_rack_cascade.rb +48 -0
  81. data/test/spec_rack_cgi.rb +89 -0
  82. data/test/spec_rack_chunked.rb +62 -0
  83. data/test/spec_rack_commonlogger.rb +61 -0
  84. data/test/spec_rack_conditionalget.rb +41 -0
  85. data/test/spec_rack_content_length.rb +43 -0
  86. data/test/spec_rack_content_type.rb +30 -0
  87. data/test/spec_rack_deflater.rb +127 -0
  88. data/test/spec_rack_directory.rb +61 -0
  89. data/test/spec_rack_fastcgi.rb +89 -0
  90. data/test/spec_rack_file.rb +75 -0
  91. data/test/spec_rack_handler.rb +43 -0
  92. data/test/spec_rack_head.rb +30 -0
  93. data/test/spec_rack_lint.rb +521 -0
  94. data/test/spec_rack_lobster.rb +45 -0
  95. data/test/spec_rack_lock.rb +38 -0
  96. data/test/spec_rack_methodoverride.rb +60 -0
  97. data/test/spec_rack_mock.rb +243 -0
  98. data/test/spec_rack_mongrel.rb +189 -0
  99. data/test/spec_rack_recursive.rb +77 -0
  100. data/test/spec_rack_request.rb +504 -0
  101. data/test/spec_rack_response.rb +218 -0
  102. data/test/spec_rack_rewindable_input.rb +118 -0
  103. data/test/spec_rack_session_cookie.rb +82 -0
  104. data/test/spec_rack_session_memcache.rb +250 -0
  105. data/test/spec_rack_session_pool.rb +172 -0
  106. data/test/spec_rack_showexceptions.rb +21 -0
  107. data/test/spec_rack_showstatus.rb +72 -0
  108. data/test/spec_rack_static.rb +37 -0
  109. data/test/spec_rack_thin.rb +91 -0
  110. data/test/spec_rack_urlmap.rb +185 -0
  111. data/test/spec_rack_utils.rb +467 -0
  112. data/test/spec_rack_webrick.rb +130 -0
  113. data/test/testrequest.rb +57 -0
  114. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  115. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  116. metadata +276 -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