lack 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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