lack 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +7 -0
  2. data/bin/rackup +5 -0
  3. data/lib/rack.rb +26 -0
  4. data/lib/rack/body_proxy.rb +39 -0
  5. data/lib/rack/builder.rb +166 -0
  6. data/lib/rack/handler.rb +63 -0
  7. data/lib/rack/handler/webrick.rb +120 -0
  8. data/lib/rack/mime.rb +661 -0
  9. data/lib/rack/mock.rb +198 -0
  10. data/lib/rack/multipart.rb +31 -0
  11. data/lib/rack/multipart/generator.rb +93 -0
  12. data/lib/rack/multipart/parser.rb +239 -0
  13. data/lib/rack/multipart/uploaded_file.rb +34 -0
  14. data/lib/rack/request.rb +394 -0
  15. data/lib/rack/response.rb +160 -0
  16. data/lib/rack/server.rb +258 -0
  17. data/lib/rack/server/options.rb +121 -0
  18. data/lib/rack/utils.rb +653 -0
  19. data/lib/rack/version.rb +3 -0
  20. data/spec/spec_helper.rb +1 -0
  21. data/test/builder/anything.rb +5 -0
  22. data/test/builder/comment.ru +4 -0
  23. data/test/builder/end.ru +5 -0
  24. data/test/builder/line.ru +1 -0
  25. data/test/builder/options.ru +2 -0
  26. data/test/multipart/bad_robots +259 -0
  27. data/test/multipart/binary +0 -0
  28. data/test/multipart/content_type_and_no_filename +6 -0
  29. data/test/multipart/empty +10 -0
  30. data/test/multipart/fail_16384_nofile +814 -0
  31. data/test/multipart/file1.txt +1 -0
  32. data/test/multipart/filename_and_modification_param +7 -0
  33. data/test/multipart/filename_and_no_name +6 -0
  34. data/test/multipart/filename_with_escaped_quotes +6 -0
  35. data/test/multipart/filename_with_escaped_quotes_and_modification_param +7 -0
  36. data/test/multipart/filename_with_percent_escaped_quotes +6 -0
  37. data/test/multipart/filename_with_unescaped_percentages +6 -0
  38. data/test/multipart/filename_with_unescaped_percentages2 +6 -0
  39. data/test/multipart/filename_with_unescaped_percentages3 +6 -0
  40. data/test/multipart/filename_with_unescaped_quotes +6 -0
  41. data/test/multipart/ie +6 -0
  42. data/test/multipart/invalid_character +6 -0
  43. data/test/multipart/mixed_files +21 -0
  44. data/test/multipart/nested +10 -0
  45. data/test/multipart/none +9 -0
  46. data/test/multipart/semicolon +6 -0
  47. data/test/multipart/text +15 -0
  48. data/test/multipart/webkit +32 -0
  49. data/test/rackup/config.ru +31 -0
  50. data/test/registering_handler/rack/handler/registering_myself.rb +8 -0
  51. data/test/spec_body_proxy.rb +69 -0
  52. data/test/spec_builder.rb +223 -0
  53. data/test/spec_chunked.rb +101 -0
  54. data/test/spec_file.rb +221 -0
  55. data/test/spec_handler.rb +59 -0
  56. data/test/spec_head.rb +45 -0
  57. data/test/spec_lint.rb +522 -0
  58. data/test/spec_mime.rb +51 -0
  59. data/test/spec_mock.rb +277 -0
  60. data/test/spec_multipart.rb +547 -0
  61. data/test/spec_recursive.rb +72 -0
  62. data/test/spec_request.rb +1199 -0
  63. data/test/spec_response.rb +343 -0
  64. data/test/spec_rewindable_input.rb +118 -0
  65. data/test/spec_sendfile.rb +130 -0
  66. data/test/spec_server.rb +167 -0
  67. data/test/spec_utils.rb +635 -0
  68. data/test/spec_webrick.rb +184 -0
  69. data/test/testrequest.rb +78 -0
  70. data/test/unregistered_handler/rack/handler/unregistered.rb +7 -0
  71. data/test/unregistered_handler/rack/handler/unregistered_long_one.rb +7 -0
  72. metadata +240 -0
@@ -0,0 +1,130 @@
1
+ require 'fileutils'
2
+ require 'rack/lint'
3
+ require 'rack/sendfile'
4
+ require 'rack/mock'
5
+ require 'tmpdir'
6
+
7
+ describe Rack::File do
8
+ should "respond to #to_path" do
9
+ Rack::File.new(Dir.pwd).should.respond_to :to_path
10
+ end
11
+ end
12
+
13
+ describe Rack::Sendfile do
14
+ def sendfile_body
15
+ FileUtils.touch File.join(Dir.tmpdir, "rack_sendfile")
16
+ res = ['Hello World']
17
+ def res.to_path ; File.join(Dir.tmpdir, "rack_sendfile") ; end
18
+ res
19
+ end
20
+
21
+ def simple_app(body=sendfile_body)
22
+ lambda { |env| [200, {'Content-Type' => 'text/plain'}, body] }
23
+ end
24
+
25
+ def sendfile_app(body, mappings = [])
26
+ Rack::Lint.new Rack::Sendfile.new(simple_app(body), nil, mappings)
27
+ end
28
+
29
+ def request(headers={}, body=sendfile_body, mappings=[])
30
+ yield Rack::MockRequest.new(sendfile_app(body, mappings)).get('/', headers)
31
+ end
32
+
33
+ def open_file(path)
34
+ Class.new(File) do
35
+ unless method_defined?(:to_path)
36
+ alias :to_path :path
37
+ end
38
+ end.open(path, 'wb+')
39
+ end
40
+
41
+ it "does nothing when no X-Sendfile-Type header present" do
42
+ request do |response|
43
+ response.should.be.ok
44
+ response.body.should.equal 'Hello World'
45
+ response.headers.should.not.include 'X-Sendfile'
46
+ end
47
+ end
48
+
49
+ it "sets X-Sendfile response header and discards body" do
50
+ request 'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile' do |response|
51
+ response.should.be.ok
52
+ response.body.should.be.empty
53
+ response.headers['Content-Length'].should.equal '0'
54
+ response.headers['X-Sendfile'].should.equal File.join(Dir.tmpdir, "rack_sendfile")
55
+ end
56
+ end
57
+
58
+ it "sets X-Lighttpd-Send-File response header and discards body" do
59
+ request 'HTTP_X_SENDFILE_TYPE' => 'X-Lighttpd-Send-File' do |response|
60
+ response.should.be.ok
61
+ response.body.should.be.empty
62
+ response.headers['Content-Length'].should.equal '0'
63
+ response.headers['X-Lighttpd-Send-File'].should.equal File.join(Dir.tmpdir, "rack_sendfile")
64
+ end
65
+ end
66
+
67
+ it "sets X-Accel-Redirect response header and discards body" do
68
+ headers = {
69
+ 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect',
70
+ 'HTTP_X_ACCEL_MAPPING' => "#{Dir.tmpdir}/=/foo/bar/"
71
+ }
72
+ request headers do |response|
73
+ response.should.be.ok
74
+ response.body.should.be.empty
75
+ response.headers['Content-Length'].should.equal '0'
76
+ response.headers['X-Accel-Redirect'].should.equal '/foo/bar/rack_sendfile'
77
+ end
78
+ end
79
+
80
+ it 'writes to rack.error when no X-Accel-Mapping is specified' do
81
+ request 'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect' do |response|
82
+ response.should.be.ok
83
+ response.body.should.equal 'Hello World'
84
+ response.headers.should.not.include 'X-Accel-Redirect'
85
+ response.errors.should.include 'X-Accel-Mapping'
86
+ end
87
+ end
88
+
89
+ it 'does nothing when body does not respond to #to_path' do
90
+ request({'HTTP_X_SENDFILE_TYPE' => 'X-Sendfile'}, ['Not a file...']) do |response|
91
+ response.body.should.equal 'Not a file...'
92
+ response.headers.should.not.include 'X-Sendfile'
93
+ end
94
+ end
95
+
96
+ it "sets X-Accel-Redirect response header and discards body when initialized with multiple mappings" do
97
+ begin
98
+ dir1 = Dir.mktmpdir
99
+ dir2 = Dir.mktmpdir
100
+
101
+ first_body = open_file(File.join(dir1, 'rack_sendfile'))
102
+ first_body.puts 'hello world'
103
+
104
+ second_body = open_file(File.join(dir2, 'rack_sendfile'))
105
+ second_body.puts 'goodbye world'
106
+
107
+ mappings = [
108
+ ["#{dir1}/", '/foo/bar/'],
109
+ ["#{dir2}/", '/wibble/']
110
+ ]
111
+
112
+ request({'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect'}, first_body, mappings) do |response|
113
+ response.should.be.ok
114
+ response.body.should.be.empty
115
+ response.headers['Content-Length'].should.equal '0'
116
+ response.headers['X-Accel-Redirect'].should.equal '/foo/bar/rack_sendfile'
117
+ end
118
+
119
+ request({'HTTP_X_SENDFILE_TYPE' => 'X-Accel-Redirect'}, second_body, mappings) do |response|
120
+ response.should.be.ok
121
+ response.body.should.be.empty
122
+ response.headers['Content-Length'].should.equal '0'
123
+ response.headers['X-Accel-Redirect'].should.equal '/wibble/rack_sendfile'
124
+ end
125
+ ensure
126
+ FileUtils.remove_entry_secure dir1
127
+ FileUtils.remove_entry_secure dir2
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,167 @@
1
+ require 'rack'
2
+ require 'rack/server'
3
+ require 'tempfile'
4
+ require 'socket'
5
+ require 'open-uri'
6
+
7
+ describe Rack::Server do
8
+
9
+ def app
10
+ lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }
11
+ end
12
+
13
+ def with_stderr
14
+ old, $stderr = $stderr, StringIO.new
15
+ yield $stderr
16
+ ensure
17
+ $stderr = old
18
+ end
19
+
20
+ it "overrides :config if :app is passed in" do
21
+ server = Rack::Server.new(:app => "FOO")
22
+ server.app.should.equal "FOO"
23
+ end
24
+
25
+ should "prefer to use :builder when it is passed in" do
26
+ server = Rack::Server.new(:builder => "run lambda { |env| [200, {'Content-Type' => 'text/plain'}, ['success']] }")
27
+ server.app.class.should.equal Proc
28
+ Rack::MockRequest.new(server.app).get("/").body.to_s.should.equal 'success'
29
+ end
30
+
31
+ should "allow subclasses to override middleware" do
32
+ server = Class.new(Rack::Server).class_eval { def middleware; Hash.new [] end; self }
33
+ server.middleware['deployment'].should.not.equal []
34
+ server.new(:app => 'foo').middleware['deployment'].should.equal []
35
+ end
36
+
37
+ should "allow subclasses to override default middleware" do
38
+ server = Class.new(Rack::Server).instance_eval { def default_middleware_by_environment; Hash.new [] end; self }
39
+ server.middleware['deployment'].should.equal []
40
+ server.new(:app => 'foo').middleware['deployment'].should.equal []
41
+ end
42
+
43
+ should "only provide default middleware for development and deployment environments" do
44
+ Rack::Server.default_middleware_by_environment.keys.sort.should.equal %w(deployment development)
45
+ end
46
+
47
+ should "always return an empty array for unknown environments" do
48
+ server = Rack::Server.new(:app => 'foo')
49
+ server.middleware['production'].should.equal []
50
+ end
51
+
52
+ should "not include Rack::Lint in deployment environment" do
53
+ server = Rack::Server.new(:app => 'foo')
54
+ server.middleware['deployment'].flatten.should.not.include(Rack::Lint)
55
+ end
56
+
57
+ should "not include Rack::ShowExceptions in deployment environment" do
58
+ server = Rack::Server.new(:app => 'foo')
59
+ server.middleware['deployment'].flatten.should.not.include(Rack::ShowExceptions)
60
+ end
61
+
62
+ should "include Rack::TempfileReaper in deployment environment" do
63
+ server = Rack::Server.new(:app => 'foo')
64
+ server.middleware['deployment'].flatten.should.include(Rack::TempfileReaper)
65
+ end
66
+
67
+ should "support CGI" do
68
+ begin
69
+ o, ENV["REQUEST_METHOD"] = ENV["REQUEST_METHOD"], 'foo'
70
+ server = Rack::Server.new(:app => 'foo')
71
+ server.server.name =~ /CGI/
72
+ Rack::Server.logging_middleware.call(server).should.eql(nil)
73
+ ensure
74
+ ENV['REQUEST_METHOD'] = o
75
+ end
76
+ end
77
+
78
+ should "be quiet if said so" do
79
+ server = Rack::Server.new(:app => "FOO", :quiet => true)
80
+ Rack::Server.logging_middleware.call(server).should.eql(nil)
81
+ end
82
+
83
+ should "use a full path to the pidfile" do
84
+ # avoids issues with daemonize chdir
85
+ opts = Rack::Server.new.send(:parse_options, %w[--pid testing.pid])
86
+ opts[:pid].should.eql(::File.expand_path('testing.pid'))
87
+ end
88
+
89
+ should "run a server" do
90
+ pidfile = Tempfile.open('pidfile') { |f| break f }.path
91
+ FileUtils.rm pidfile
92
+ server = Rack::Server.new(
93
+ :app => app,
94
+ :environment => 'none',
95
+ :pid => pidfile,
96
+ :Port => TCPServer.open('127.0.0.1', 0){|s| s.addr[1] },
97
+ :Host => '127.0.0.1',
98
+ :daemonize => false,
99
+ :server => 'webrick'
100
+ )
101
+ t = Thread.new { server.start { |s| Thread.current[:server] = s } }
102
+ t.join(0.01) until t[:server] && t[:server].status != :Stop
103
+ body = open("http://127.0.0.1:#{server.options[:Port]}/") { |f| f.read }
104
+ body.should.eql('success')
105
+
106
+ Process.kill(:INT, $$)
107
+ t.join
108
+ open(pidfile) { |f| f.read.should.eql $$.to_s }
109
+ end
110
+
111
+ should "check pid file presence and running process" do
112
+ pidfile = Tempfile.open('pidfile') { |f| f.write($$); break f }.path
113
+ server = Rack::Server.new(:pid => pidfile)
114
+ server.send(:pidfile_process_status).should.eql :running
115
+ end
116
+
117
+ should "check pid file presence and dead process" do
118
+ dead_pid = `echo $$`.to_i
119
+ pidfile = Tempfile.open('pidfile') { |f| f.write(dead_pid); break f }.path
120
+ server = Rack::Server.new(:pid => pidfile)
121
+ server.send(:pidfile_process_status).should.eql :dead
122
+ end
123
+
124
+ should "check pid file presence and exited process" do
125
+ pidfile = Tempfile.open('pidfile') { |f| break f }.path
126
+ ::File.delete(pidfile)
127
+ server = Rack::Server.new(:pid => pidfile)
128
+ server.send(:pidfile_process_status).should.eql :exited
129
+ end
130
+
131
+ should "check pid file presence and not owned process" do
132
+ pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
133
+ server = Rack::Server.new(:pid => pidfile)
134
+ server.send(:pidfile_process_status).should.eql :not_owned
135
+ end
136
+
137
+ should "not write pid file when it is created after check" do
138
+ pidfile = Tempfile.open('pidfile') { |f| break f }.path
139
+ ::File.delete(pidfile)
140
+ server = Rack::Server.new(:pid => pidfile)
141
+ ::File.open(pidfile, 'w') { |f| f.write(1) }
142
+ with_stderr do |err|
143
+ should.raise(SystemExit) do
144
+ server.send(:write_pid)
145
+ end
146
+ err.rewind
147
+ output = err.read
148
+ output.should.match(/already running/)
149
+ output.should.include? pidfile
150
+ end
151
+ end
152
+
153
+ should "inform the user about existing pidfiles with running processes" do
154
+ pidfile = Tempfile.open('pidfile') { |f| f.write(1); break f }.path
155
+ server = Rack::Server.new(:pid => pidfile)
156
+ with_stderr do |err|
157
+ should.raise(SystemExit) do
158
+ server.start
159
+ end
160
+ err.rewind
161
+ output = err.read
162
+ output.should.match(/already running/)
163
+ output.should.include? pidfile
164
+ end
165
+ end
166
+
167
+ end
@@ -0,0 +1,635 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require 'rack/utils'
3
+ require 'rack/mock'
4
+ require 'timeout'
5
+
6
+ describe Rack::Utils do
7
+
8
+ # A helper method which checks
9
+ # if certain query parameters
10
+ # are equal.
11
+ def equal_query_to(query)
12
+ parts = query.split('&')
13
+ lambda{|other| (parts & other.split('&')) == parts }
14
+ end
15
+
16
+ def kcodeu
17
+ one8 = RUBY_VERSION.to_f < 1.9
18
+ default_kcode, $KCODE = $KCODE, 'U' if one8
19
+ yield
20
+ ensure
21
+ $KCODE = default_kcode if one8
22
+ end
23
+
24
+ should "round trip binary data" do
25
+ r = [218, 0].pack 'CC'
26
+ if defined?(::Encoding)
27
+ z = Rack::Utils.unescape(Rack::Utils.escape(r), Encoding::BINARY)
28
+ else
29
+ z = Rack::Utils.unescape(Rack::Utils.escape(r))
30
+ end
31
+ r.should.equal z
32
+ end
33
+
34
+ should "escape correctly" do
35
+ Rack::Utils.escape("fo<o>bar").should.equal "fo%3Co%3Ebar"
36
+ Rack::Utils.escape("a space").should.equal "a+space"
37
+ Rack::Utils.escape("q1!2\"'w$5&7/z8)?\\").
38
+ should.equal "q1%212%22%27w%245%267%2Fz8%29%3F%5C"
39
+ end
40
+
41
+ should "escape correctly for multibyte characters" do
42
+ matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
43
+ matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
44
+ Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
45
+ matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
46
+ matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
47
+ Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
48
+ end
49
+
50
+ if RUBY_VERSION[/^\d+\.\d+/] == '1.8'
51
+ should "escape correctly for multibyte characters if $KCODE is set to 'U'" do
52
+ kcodeu do
53
+ matz_name = "\xE3\x81\xBE\xE3\x81\xA4\xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsumoto
54
+ matz_name.force_encoding("UTF-8") if matz_name.respond_to? :force_encoding
55
+ Rack::Utils.escape(matz_name).should.equal '%E3%81%BE%E3%81%A4%E3%82%82%E3%81%A8'
56
+ matz_name_sep = "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0] # Matsu moto
57
+ matz_name_sep.force_encoding("UTF-8") if matz_name_sep.respond_to? :force_encoding
58
+ Rack::Utils.escape(matz_name_sep).should.equal '%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8'
59
+ end
60
+ end
61
+
62
+ should "unescape multibyte characters correctly if $KCODE is set to 'U'" do
63
+ kcodeu do
64
+ Rack::Utils.unescape('%E3%81%BE%E3%81%A4+%E3%82%82%E3%81%A8').should.equal(
65
+ "\xE3\x81\xBE\xE3\x81\xA4 \xE3\x82\x82\xE3\x81\xA8".unpack("a*")[0])
66
+ end
67
+ end
68
+ end
69
+
70
+ should "escape objects that responds to to_s" do
71
+ kcodeu do
72
+ Rack::Utils.escape(:id).should.equal "id"
73
+ end
74
+ end
75
+
76
+ if "".respond_to?(:encode)
77
+ should "escape non-UTF8 strings" do
78
+ Rack::Utils.escape("ø".encode("ISO-8859-1")).should.equal "%F8"
79
+ end
80
+ end
81
+
82
+ should "not hang on escaping long strings that end in % (http://redmine.ruby-lang.org/issues/5149)" do
83
+ lambda {
84
+ timeout(1) do
85
+ lambda {
86
+ URI.decode_www_form_component "A string that causes catastrophic backtracking as it gets longer %"
87
+ }.should.raise(ArgumentError)
88
+ end
89
+ }.should.not.raise(Timeout::Error)
90
+ end
91
+
92
+ should "escape path spaces with %20" do
93
+ Rack::Utils.escape_path("foo bar").should.equal "foo%20bar"
94
+ end
95
+
96
+ should "unescape correctly" do
97
+ Rack::Utils.unescape("fo%3Co%3Ebar").should.equal "fo<o>bar"
98
+ Rack::Utils.unescape("a+space").should.equal "a space"
99
+ Rack::Utils.unescape("a%20space").should.equal "a space"
100
+ Rack::Utils.unescape("q1%212%22%27w%245%267%2Fz8%29%3F%5C").
101
+ should.equal "q1!2\"'w$5&7/z8)?\\"
102
+ end
103
+
104
+ should "parse query strings correctly" do
105
+ Rack::Utils.parse_query("foo=bar").
106
+ should.equal "foo" => "bar"
107
+ Rack::Utils.parse_query("foo=\"bar\"").
108
+ should.equal "foo" => "\"bar\""
109
+ Rack::Utils.parse_query("foo=bar&foo=quux").
110
+ should.equal "foo" => ["bar", "quux"]
111
+ Rack::Utils.parse_query("foo=1&bar=2").
112
+ should.equal "foo" => "1", "bar" => "2"
113
+ Rack::Utils.parse_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
114
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
115
+ Rack::Utils.parse_query("foo%3Dbaz=bar").should.equal "foo=baz" => "bar"
116
+ Rack::Utils.parse_query("=").should.equal "" => ""
117
+ Rack::Utils.parse_query("=value").should.equal "" => "value"
118
+ Rack::Utils.parse_query("key=").should.equal "key" => ""
119
+ Rack::Utils.parse_query("&key&").should.equal "key" => nil
120
+ Rack::Utils.parse_query(";key;", ";,").should.equal "key" => nil
121
+ Rack::Utils.parse_query(",key,", ";,").should.equal "key" => nil
122
+ Rack::Utils.parse_query(";foo=bar,;", ";,").should.equal "foo" => "bar"
123
+ Rack::Utils.parse_query(",foo=bar;,", ";,").should.equal "foo" => "bar"
124
+ end
125
+
126
+ should "not create infinite loops with cycle structures" do
127
+ ex = { "foo" => nil }
128
+ ex["foo"] = ex
129
+
130
+ params = Rack::Utils::KeySpaceConstrainedParams.new
131
+ params['foo'] = params
132
+ lambda {
133
+ params.to_params_hash.to_s.should.equal ex.to_s
134
+ }.should.not.raise
135
+ end
136
+
137
+ should "parse nested query strings correctly" do
138
+ Rack::Utils.parse_nested_query("foo").
139
+ should.equal "foo" => nil
140
+ Rack::Utils.parse_nested_query("foo=").
141
+ should.equal "foo" => ""
142
+ Rack::Utils.parse_nested_query("foo=bar").
143
+ should.equal "foo" => "bar"
144
+ Rack::Utils.parse_nested_query("foo=\"bar\"").
145
+ should.equal "foo" => "\"bar\""
146
+
147
+ Rack::Utils.parse_nested_query("foo=bar&foo=quux").
148
+ should.equal "foo" => "quux"
149
+ Rack::Utils.parse_nested_query("foo&foo=").
150
+ should.equal "foo" => ""
151
+ Rack::Utils.parse_nested_query("foo=1&bar=2").
152
+ should.equal "foo" => "1", "bar" => "2"
153
+ Rack::Utils.parse_nested_query("&foo=1&&bar=2").
154
+ should.equal "foo" => "1", "bar" => "2"
155
+ Rack::Utils.parse_nested_query("foo&bar=").
156
+ should.equal "foo" => nil, "bar" => ""
157
+ Rack::Utils.parse_nested_query("foo=bar&baz=").
158
+ should.equal "foo" => "bar", "baz" => ""
159
+ Rack::Utils.parse_nested_query("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F").
160
+ should.equal "my weird field" => "q1!2\"'w$5&7/z8)?"
161
+
162
+ Rack::Utils.parse_nested_query("a=b&pid%3D1234=1023").
163
+ should.equal "pid=1234" => "1023", "a" => "b"
164
+
165
+ Rack::Utils.parse_nested_query("foo[]").
166
+ should.equal "foo" => [nil]
167
+ Rack::Utils.parse_nested_query("foo[]=").
168
+ should.equal "foo" => [""]
169
+ Rack::Utils.parse_nested_query("foo[]=bar").
170
+ should.equal "foo" => ["bar"]
171
+ Rack::Utils.parse_nested_query("foo[]=bar&foo").
172
+ should.equal "foo" => nil
173
+ Rack::Utils.parse_nested_query("foo[]=bar&foo[").
174
+ should.equal "foo" => ["bar"], "foo[" => nil
175
+ Rack::Utils.parse_nested_query("foo[]=bar&foo[=baz").
176
+ should.equal "foo" => ["bar"], "foo[" => "baz"
177
+ Rack::Utils.parse_nested_query("foo[]=bar&foo[]").
178
+ should.equal "foo" => ["bar", nil]
179
+ Rack::Utils.parse_nested_query("foo[]=bar&foo[]=").
180
+ should.equal "foo" => ["bar", ""]
181
+
182
+ Rack::Utils.parse_nested_query("foo[]=1&foo[]=2").
183
+ should.equal "foo" => ["1", "2"]
184
+ Rack::Utils.parse_nested_query("foo=bar&baz[]=1&baz[]=2&baz[]=3").
185
+ should.equal "foo" => "bar", "baz" => ["1", "2", "3"]
186
+ Rack::Utils.parse_nested_query("foo[]=bar&baz[]=1&baz[]=2&baz[]=3").
187
+ should.equal "foo" => ["bar"], "baz" => ["1", "2", "3"]
188
+
189
+ Rack::Utils.parse_nested_query("x[y][z]=1").
190
+ should.equal "x" => {"y" => {"z" => "1"}}
191
+ Rack::Utils.parse_nested_query("x[y][z][]=1").
192
+ should.equal "x" => {"y" => {"z" => ["1"]}}
193
+ Rack::Utils.parse_nested_query("x[y][z]=1&x[y][z]=2").
194
+ should.equal "x" => {"y" => {"z" => "2"}}
195
+ Rack::Utils.parse_nested_query("x[y][z][]=1&x[y][z][]=2").
196
+ should.equal "x" => {"y" => {"z" => ["1", "2"]}}
197
+
198
+ Rack::Utils.parse_nested_query("x[y][][z]=1").
199
+ should.equal "x" => {"y" => [{"z" => "1"}]}
200
+ Rack::Utils.parse_nested_query("x[y][][z][]=1").
201
+ should.equal "x" => {"y" => [{"z" => ["1"]}]}
202
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=2").
203
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "2"}]}
204
+
205
+ Rack::Utils.parse_nested_query("x[y][][v][w]=1").
206
+ should.equal "x" => {"y" => [{"v" => {"w" => "1"}}]}
207
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][v][w]=2").
208
+ should.equal "x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}
209
+
210
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][z]=2").
211
+ should.equal "x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}
212
+ Rack::Utils.parse_nested_query("x[y][][z]=1&x[y][][w]=a&x[y][][z]=2&x[y][][w]=3").
213
+ should.equal "x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}
214
+
215
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y]z=2") }.
216
+ should.raise(Rack::Utils::ParameterTypeError).
217
+ message.should.equal "expected Hash (got String) for param `y'"
218
+
219
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[]=1") }.
220
+ should.raise(Rack::Utils::ParameterTypeError).
221
+ message.should.match(/expected Array \(got [^)]*\) for param `x'/)
222
+
223
+ lambda { Rack::Utils.parse_nested_query("x[y]=1&x[y][][w]=2") }.
224
+ should.raise(Rack::Utils::ParameterTypeError).
225
+ message.should.equal "expected Array (got String) for param `y'"
226
+
227
+ if RUBY_VERSION.to_f > 1.9
228
+ lambda { Rack::Utils.parse_nested_query("foo%81E=1") }.
229
+ should.raise(Rack::Utils::InvalidParameterError).
230
+ message.should.equal "invalid byte sequence in UTF-8"
231
+ end
232
+ end
233
+
234
+ should "build query strings correctly" do
235
+ Rack::Utils.build_query("foo" => "bar").should.be equal_query_to("foo=bar")
236
+ Rack::Utils.build_query("foo" => ["bar", "quux"]).
237
+ should.be equal_query_to("foo=bar&foo=quux")
238
+ Rack::Utils.build_query("foo" => "1", "bar" => "2").
239
+ should.be equal_query_to("foo=1&bar=2")
240
+ Rack::Utils.build_query("my weird field" => "q1!2\"'w$5&7/z8)?").
241
+ should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F")
242
+ end
243
+
244
+ should "build nested query strings correctly" do
245
+ Rack::Utils.build_nested_query("foo" => nil).should.equal "foo"
246
+ Rack::Utils.build_nested_query("foo" => "").should.equal "foo="
247
+ Rack::Utils.build_nested_query("foo" => "bar").should.equal "foo=bar"
248
+
249
+ Rack::Utils.build_nested_query("foo" => "1", "bar" => "2").
250
+ should.be equal_query_to("foo=1&bar=2")
251
+ Rack::Utils.build_nested_query("foo" => 1, "bar" => 2).
252
+ should.be equal_query_to("foo=1&bar=2")
253
+ Rack::Utils.build_nested_query("my weird field" => "q1!2\"'w$5&7/z8)?").
254
+ should.be equal_query_to("my+weird+field=q1%212%22%27w%245%267%2Fz8%29%3F")
255
+
256
+ Rack::Utils.build_nested_query("foo" => [nil]).
257
+ should.equal "foo[]"
258
+ Rack::Utils.build_nested_query("foo" => [""]).
259
+ should.equal "foo[]="
260
+ Rack::Utils.build_nested_query("foo" => ["bar"]).
261
+ should.equal "foo[]=bar"
262
+ Rack::Utils.build_nested_query('foo' => []).
263
+ should.equal ''
264
+ Rack::Utils.build_nested_query('foo' => {}).
265
+ should.equal ''
266
+ Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => []).
267
+ should.equal 'foo=bar'
268
+ Rack::Utils.build_nested_query('foo' => 'bar', 'baz' => {}).
269
+ should.equal 'foo=bar'
270
+
271
+ # The ordering of the output query string is unpredictable with 1.8's
272
+ # unordered hash. Test that build_nested_query performs the inverse
273
+ # function of parse_nested_query.
274
+ [{"foo" => nil, "bar" => ""},
275
+ {"foo" => "bar", "baz" => ""},
276
+ {"foo" => ["1", "2"]},
277
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
278
+ {"foo" => ["bar"], "baz" => ["1", "2", "3"]},
279
+ {"foo" => ["1", "2"]},
280
+ {"foo" => "bar", "baz" => ["1", "2", "3"]},
281
+ {"x" => {"y" => {"z" => "1"}}},
282
+ {"x" => {"y" => {"z" => ["1"]}}},
283
+ {"x" => {"y" => {"z" => ["1", "2"]}}},
284
+ {"x" => {"y" => [{"z" => "1"}]}},
285
+ {"x" => {"y" => [{"z" => ["1"]}]}},
286
+ {"x" => {"y" => [{"z" => "1", "w" => "2"}]}},
287
+ {"x" => {"y" => [{"v" => {"w" => "1"}}]}},
288
+ {"x" => {"y" => [{"z" => "1", "v" => {"w" => "2"}}]}},
289
+ {"x" => {"y" => [{"z" => "1"}, {"z" => "2"}]}},
290
+ {"x" => {"y" => [{"z" => "1", "w" => "a"}, {"z" => "2", "w" => "3"}]}}
291
+ ].each { |params|
292
+ qs = Rack::Utils.build_nested_query(params)
293
+ Rack::Utils.parse_nested_query(qs).should.equal params
294
+ }
295
+
296
+ lambda { Rack::Utils.build_nested_query("foo=bar") }.
297
+ should.raise(ArgumentError).
298
+ message.should.equal "value must be a Hash"
299
+ end
300
+
301
+ should "parse query strings that have a non-existent value" do
302
+ key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
303
+ Rack::Utils.parse_query(key).should.equal Rack::Utils.unescape(key) => nil
304
+ end
305
+
306
+ should "build query strings without = with non-existent values" do
307
+ key = "post/2011/08/27/Deux-%22rat%C3%A9s%22-de-l-Universit"
308
+ key = Rack::Utils.unescape(key)
309
+ Rack::Utils.build_query(key => nil).should.equal Rack::Utils.escape(key)
310
+ end
311
+
312
+ should "parse q-values" do
313
+ # XXX handle accept-extension
314
+ Rack::Utils.q_values("foo;q=0.5,bar,baz;q=0.9").should.equal [
315
+ [ 'foo', 0.5 ],
316
+ [ 'bar', 1.0 ],
317
+ [ 'baz', 0.9 ]
318
+ ]
319
+ end
320
+
321
+ should "select best quality match" do
322
+ Rack::Utils.best_q_match("text/html", %w[text/html]).should.equal "text/html"
323
+
324
+ # More specific matches are preferred
325
+ Rack::Utils.best_q_match("text/*;q=0.5,text/html;q=1.0", %w[text/html]).should.equal "text/html"
326
+
327
+ # Higher quality matches are preferred
328
+ Rack::Utils.best_q_match("text/*;q=0.5,text/plain;q=1.0", %w[text/plain text/html]).should.equal "text/plain"
329
+
330
+ # Respect requested content type
331
+ Rack::Utils.best_q_match("application/json", %w[application/vnd.lotus-1-2-3 application/json]).should.equal "application/json"
332
+
333
+ # All else equal, the available mimes are preferred in order
334
+ Rack::Utils.best_q_match("text/*", %w[text/html text/plain]).should.equal "text/html"
335
+ Rack::Utils.best_q_match("text/plain,text/html", %w[text/html text/plain]).should.equal "text/html"
336
+
337
+ # When there are no matches, return nil:
338
+ Rack::Utils.best_q_match("application/json", %w[text/html text/plain]).should.equal nil
339
+ end
340
+
341
+ should "escape html entities [&><'\"/]" do
342
+ Rack::Utils.escape_html("foo").should.equal "foo"
343
+ Rack::Utils.escape_html("f&o").should.equal "f&amp;o"
344
+ Rack::Utils.escape_html("f<o").should.equal "f&lt;o"
345
+ Rack::Utils.escape_html("f>o").should.equal "f&gt;o"
346
+ Rack::Utils.escape_html("f'o").should.equal "f&#x27;o"
347
+ Rack::Utils.escape_html('f"o').should.equal "f&quot;o"
348
+ Rack::Utils.escape_html("f/o").should.equal "f&#x2F;o"
349
+ Rack::Utils.escape_html("<foo></foo>").should.equal "&lt;foo&gt;&lt;&#x2F;foo&gt;"
350
+ end
351
+
352
+ should "escape html entities even on MRI when it's bugged" do
353
+ test_escape = lambda do
354
+ kcodeu do
355
+ Rack::Utils.escape_html("\300<").should.equal "\300&lt;"
356
+ end
357
+ end
358
+
359
+ if RUBY_VERSION.to_f < 1.9
360
+ test_escape.call
361
+ else
362
+ test_escape.should.raise(ArgumentError)
363
+ end
364
+ end
365
+
366
+ if "".respond_to?(:encode)
367
+ should "escape html entities in unicode strings" do
368
+ # the following will cause warnings if the regex is poorly encoded:
369
+ Rack::Utils.escape_html("☃").should.equal "☃"
370
+ end
371
+ end
372
+
373
+ should "figure out which encodings are acceptable" do
374
+ helper = lambda do |a, b|
375
+ Rack::Request.new(Rack::MockRequest.env_for("", "HTTP_ACCEPT_ENCODING" => a))
376
+ Rack::Utils.select_best_encoding(a, b)
377
+ end
378
+
379
+ helper.call(%w(), [["x", 1]]).should.equal(nil)
380
+ helper.call(%w(identity), [["identity", 0.0]]).should.equal(nil)
381
+ helper.call(%w(identity), [["*", 0.0]]).should.equal(nil)
382
+
383
+ helper.call(%w(identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("identity")
384
+
385
+ helper.call(%w(compress gzip identity), [["compress", 1.0], ["gzip", 1.0]]).should.equal("compress")
386
+ helper.call(%w(compress gzip identity), [["compress", 0.5], ["gzip", 1.0]]).should.equal("gzip")
387
+
388
+ helper.call(%w(foo bar identity), []).should.equal("identity")
389
+ helper.call(%w(foo bar identity), [["*", 1.0]]).should.equal("foo")
390
+ helper.call(%w(foo bar identity), [["*", 1.0], ["foo", 0.9]]).should.equal("bar")
391
+
392
+ helper.call(%w(foo bar identity), [["foo", 0], ["bar", 0]]).should.equal("identity")
393
+ helper.call(%w(foo bar baz identity), [["*", 0], ["identity", 0.1]]).should.equal("identity")
394
+ end
395
+
396
+ should "return the bytesize of String" do
397
+ Rack::Utils.bytesize("FOO\xE2\x82\xAC").should.equal 6
398
+ end
399
+
400
+ should "should perform constant time string comparison" do
401
+ Rack::Utils.secure_compare('a', 'a').should.equal true
402
+ Rack::Utils.secure_compare('a', 'b').should.equal false
403
+ end
404
+
405
+ should "return status code for integer" do
406
+ Rack::Utils.status_code(200).should.equal 200
407
+ end
408
+
409
+ should "return status code for string" do
410
+ Rack::Utils.status_code("200").should.equal 200
411
+ end
412
+
413
+ should "return status code for symbol" do
414
+ Rack::Utils.status_code(:ok).should.equal 200
415
+ end
416
+
417
+ should "return rfc2822 format from rfc2822 helper" do
418
+ Rack::Utils.rfc2822(Time.at(0).gmtime).should == "Thu, 01 Jan 1970 00:00:00 -0000"
419
+ end
420
+
421
+ should "return rfc2109 format from rfc2109 helper" do
422
+ Rack::Utils.rfc2109(Time.at(0).gmtime).should == "Thu, 01-Jan-1970 00:00:00 GMT"
423
+ end
424
+
425
+ should "clean directory traversal" do
426
+ Rack::Utils.clean_path_info("/cgi/../cgi/test").should.equal "/cgi/test"
427
+ Rack::Utils.clean_path_info(".").should.empty
428
+ Rack::Utils.clean_path_info("test/..").should.empty
429
+ end
430
+
431
+ should "clean unsafe directory traversal to safe path" do
432
+ Rack::Utils.clean_path_info("/../README.rdoc").should.equal "/README.rdoc"
433
+ Rack::Utils.clean_path_info("../test/spec_utils.rb").should.equal "test/spec_utils.rb"
434
+ end
435
+
436
+ should "not clean directory traversal with encoded periods" do
437
+ Rack::Utils.clean_path_info("/%2E%2E/README").should.equal "/%2E%2E/README"
438
+ end
439
+
440
+ should "clean slash only paths" do
441
+ Rack::Utils.clean_path_info("/").should.equal "/"
442
+ end
443
+ end
444
+
445
+ describe Rack::Utils, "byte_range" do
446
+ should "ignore missing or syntactically invalid byte ranges" do
447
+ Rack::Utils.byte_ranges({},500).should.equal nil
448
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "foobar"},500).should.equal nil
449
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "furlongs=123-456"},500).should.equal nil
450
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes="},500).should.equal nil
451
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-"},500).should.equal nil
452
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123,456"},500).should.equal nil
453
+ # A range of non-positive length is syntactically invalid and ignored:
454
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-123"},500).should.equal nil
455
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=456-455"},500).should.equal nil
456
+ end
457
+
458
+ should "parse simple byte ranges" do
459
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},500).should.equal [(123..456)]
460
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-"},500).should.equal [(123..499)]
461
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},500).should.equal [(400..499)]
462
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},500).should.equal [(0..0)]
463
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=499-499"},500).should.equal [(499..499)]
464
+ end
465
+
466
+ should "parse several byte ranges" do
467
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-600,601-999"},1000).should.equal [(500..600),(601..999)]
468
+ end
469
+
470
+ should "truncate byte ranges" do
471
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-999"},500).should.equal [(123..499)]
472
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=600-999"},500).should.equal []
473
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-999"},500).should.equal [(0..499)]
474
+ end
475
+
476
+ should "ignore unsatisfiable byte ranges" do
477
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-501"},500).should.equal []
478
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=500-"},500).should.equal []
479
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=999-"},500).should.equal []
480
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},500).should.equal []
481
+ end
482
+
483
+ should "handle byte ranges of empty files" do
484
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=123-456"},0).should.equal []
485
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-"},0).should.equal []
486
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-100"},0).should.equal []
487
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=0-0"},0).should.equal []
488
+ Rack::Utils.byte_ranges({"HTTP_RANGE" => "bytes=-0"},0).should.equal []
489
+ end
490
+ end
491
+
492
+ describe Rack::Utils::HeaderHash do
493
+ should "retain header case" do
494
+ h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
495
+ h['ETag'] = 'Boo!'
496
+ h.to_hash.should.equal "Content-MD5" => "d5ff4e2a0 ...", "ETag" => 'Boo!'
497
+ end
498
+
499
+ should "check existence of keys case insensitively" do
500
+ h = Rack::Utils::HeaderHash.new("Content-MD5" => "d5ff4e2a0 ...")
501
+ h.should.include 'content-md5'
502
+ h.should.not.include 'ETag'
503
+ end
504
+
505
+ should "merge case-insensitively" do
506
+ h = Rack::Utils::HeaderHash.new("ETag" => 'HELLO', "content-length" => '123')
507
+ merged = h.merge("Etag" => 'WORLD', 'Content-Length' => '321', "Foo" => 'BAR')
508
+ merged.should.equal "Etag"=>'WORLD', "Content-Length"=>'321', "Foo"=>'BAR'
509
+ end
510
+
511
+ should "overwrite case insensitively and assume the new key's case" do
512
+ h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
513
+ h["foo-bar"] = "bizzle"
514
+ h["FOO-BAR"].should.equal "bizzle"
515
+ h.length.should.equal 1
516
+ h.to_hash.should.equal "foo-bar" => "bizzle"
517
+ end
518
+
519
+ should "be converted to real Hash" do
520
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
521
+ h.to_hash.should.be.instance_of Hash
522
+ end
523
+
524
+ should "convert Array values to Strings when converting to Hash" do
525
+ h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
526
+ h.to_hash.should.equal({ "foo" => "bar\nbaz" })
527
+ end
528
+
529
+ should "replace hashes correctly" do
530
+ h = Rack::Utils::HeaderHash.new("Foo-Bar" => "baz")
531
+ j = {"foo" => "bar"}
532
+ h.replace(j)
533
+ h["foo"].should.equal "bar"
534
+ end
535
+
536
+ should "be able to delete the given key case-sensitively" do
537
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
538
+ h.delete("foo")
539
+ h["foo"].should.be.nil
540
+ h["FOO"].should.be.nil
541
+ end
542
+
543
+ should "be able to delete the given key case-insensitively" do
544
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
545
+ h.delete("FOO")
546
+ h["foo"].should.be.nil
547
+ h["FOO"].should.be.nil
548
+ end
549
+
550
+ should "return the deleted value when #delete is called on an existing key" do
551
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
552
+ h.delete("Foo").should.equal("bar")
553
+ end
554
+
555
+ should "return nil when #delete is called on a non-existant key" do
556
+ h = Rack::Utils::HeaderHash.new("foo" => "bar")
557
+ h.delete("Hello").should.be.nil
558
+ end
559
+
560
+ should "avoid unnecessary object creation if possible" do
561
+ a = Rack::Utils::HeaderHash.new("foo" => "bar")
562
+ b = Rack::Utils::HeaderHash.new(a)
563
+ b.object_id.should.equal(a.object_id)
564
+ b.should.equal(a)
565
+ end
566
+
567
+ should "convert Array values to Strings when responding to #each" do
568
+ h = Rack::Utils::HeaderHash.new("foo" => ["bar", "baz"])
569
+ h.each do |k,v|
570
+ k.should.equal("foo")
571
+ v.should.equal("bar\nbaz")
572
+ end
573
+ end
574
+
575
+ should "not create headers out of thin air" do
576
+ h = Rack::Utils::HeaderHash.new
577
+ h['foo']
578
+ h['foo'].should.be.nil
579
+ h.should.not.include 'foo'
580
+ end
581
+ end
582
+
583
+ describe Rack::Utils::Context do
584
+ class ContextTest
585
+ attr_reader :app
586
+ def initialize app; @app=app; end
587
+ def call env; context env; end
588
+ def context env, app=@app; app.call(env); end
589
+ end
590
+ test_target1 = proc{|e| e.to_s+' world' }
591
+ test_target2 = proc{|e| e.to_i+2 }
592
+ test_target3 = proc{|e| nil }
593
+ test_target4 = proc{|e| [200,{'Content-Type'=>'text/plain', 'Content-Length'=>'0'},['']] }
594
+ test_app = ContextTest.new test_target4
595
+
596
+ should "set context correctly" do
597
+ test_app.app.should.equal test_target4
598
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
599
+ c1.for.should.equal test_app
600
+ c1.app.should.equal test_target1
601
+ c2 = Rack::Utils::Context.new(test_app, test_target2)
602
+ c2.for.should.equal test_app
603
+ c2.app.should.equal test_target2
604
+ end
605
+
606
+ should "alter app on recontexting" do
607
+ c1 = Rack::Utils::Context.new(test_app, test_target1)
608
+ c2 = c1.recontext(test_target2)
609
+ c2.for.should.equal test_app
610
+ c2.app.should.equal test_target2
611
+ c3 = c2.recontext(test_target3)
612
+ c3.for.should.equal test_app
613
+ c3.app.should.equal test_target3
614
+ end
615
+
616
+ should "run different apps" do
617
+ c1 = Rack::Utils::Context.new test_app, test_target1
618
+ c2 = c1.recontext test_target2
619
+ c3 = c2.recontext test_target3
620
+ c4 = c3.recontext test_target4
621
+ a4 = Rack::Lint.new c4
622
+ a5 = Rack::Lint.new test_app
623
+ r1 = c1.call('hello')
624
+ r1.should.equal 'hello world'
625
+ r2 = c2.call(2)
626
+ r2.should.equal 4
627
+ r3 = c3.call(:misc_symbol)
628
+ r3.should.be.nil
629
+ r4 = Rack::MockRequest.new(a4).get('/')
630
+ r4.status.should.equal 200
631
+ r5 = Rack::MockRequest.new(a5).get('/')
632
+ r5.status.should.equal 200
633
+ r4.body.should.equal r5.body
634
+ end
635
+ end