adamwiggins-sinatra 0.8.9 → 0.10.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 (79) hide show
  1. data/AUTHORS +8 -7
  2. data/CHANGES +211 -1
  3. data/LICENSE +1 -1
  4. data/README.rdoc +183 -139
  5. data/Rakefile +20 -81
  6. data/lib/sinatra.rb +5 -1
  7. data/lib/sinatra/base.rb +569 -278
  8. data/lib/sinatra/main.rb +12 -25
  9. data/lib/sinatra/showexceptions.rb +303 -0
  10. data/sinatra.gemspec +20 -44
  11. data/test/base_test.rb +140 -52
  12. data/test/builder_test.rb +14 -17
  13. data/test/contest.rb +64 -0
  14. data/test/erb_test.rb +42 -16
  15. data/test/extensions_test.rb +100 -0
  16. data/test/filter_test.rb +85 -13
  17. data/test/haml_test.rb +39 -21
  18. data/test/helper.rb +76 -0
  19. data/test/helpers_test.rb +219 -84
  20. data/test/mapped_error_test.rb +168 -146
  21. data/test/middleware_test.rb +22 -17
  22. data/test/options_test.rb +323 -54
  23. data/test/render_backtrace_test.rb +145 -0
  24. data/test/request_test.rb +28 -6
  25. data/test/response_test.rb +42 -0
  26. data/test/result_test.rb +27 -21
  27. data/test/route_added_hook_test.rb +59 -0
  28. data/test/routing_test.rb +558 -77
  29. data/test/sass_test.rb +52 -13
  30. data/test/server_test.rb +47 -0
  31. data/test/sinatra_test.rb +3 -5
  32. data/test/static_test.rb +57 -30
  33. data/test/templates_test.rb +74 -25
  34. data/test/views/error.builder +3 -0
  35. data/test/views/error.erb +3 -0
  36. data/test/views/error.haml +3 -0
  37. data/test/views/error.sass +2 -0
  38. data/test/views/foo/hello.test +1 -0
  39. metadata +50 -46
  40. data/compat/app_test.rb +0 -300
  41. data/compat/application_test.rb +0 -334
  42. data/compat/builder_test.rb +0 -101
  43. data/compat/custom_error_test.rb +0 -62
  44. data/compat/erb_test.rb +0 -136
  45. data/compat/events_test.rb +0 -75
  46. data/compat/filter_test.rb +0 -30
  47. data/compat/haml_test.rb +0 -233
  48. data/compat/helper.rb +0 -21
  49. data/compat/mapped_error_test.rb +0 -72
  50. data/compat/pipeline_test.rb +0 -71
  51. data/compat/public/foo.xml +0 -1
  52. data/compat/sass_test.rb +0 -57
  53. data/compat/sessions_test.rb +0 -39
  54. data/compat/streaming_test.rb +0 -121
  55. data/compat/sym_params_test.rb +0 -19
  56. data/compat/template_test.rb +0 -30
  57. data/compat/use_in_file_templates_test.rb +0 -47
  58. data/compat/views/foo.builder +0 -1
  59. data/compat/views/foo.erb +0 -1
  60. data/compat/views/foo.haml +0 -1
  61. data/compat/views/foo.sass +0 -2
  62. data/compat/views/foo_layout.erb +0 -2
  63. data/compat/views/foo_layout.haml +0 -2
  64. data/compat/views/layout_test/foo.builder +0 -1
  65. data/compat/views/layout_test/foo.erb +0 -1
  66. data/compat/views/layout_test/foo.haml +0 -1
  67. data/compat/views/layout_test/foo.sass +0 -2
  68. data/compat/views/layout_test/layout.builder +0 -3
  69. data/compat/views/layout_test/layout.erb +0 -1
  70. data/compat/views/layout_test/layout.haml +0 -1
  71. data/compat/views/layout_test/layout.sass +0 -2
  72. data/compat/views/no_layout/no_layout.builder +0 -1
  73. data/compat/views/no_layout/no_layout.haml +0 -1
  74. data/lib/sinatra/compat.rb +0 -239
  75. data/lib/sinatra/test.rb +0 -112
  76. data/lib/sinatra/test/rspec.rb +0 -2
  77. data/lib/sinatra/test/spec.rb +0 -2
  78. data/lib/sinatra/test/unit.rb +0 -11
  79. data/test/reload_test.rb +0 -65
@@ -0,0 +1,145 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ require 'sass/error'
4
+
5
+ class RenderBacktraceTest < Test::Unit::TestCase
6
+ VIEWS = File.dirname(__FILE__) + '/views'
7
+
8
+ def assert_raise_at(filename, line, exception = RuntimeError)
9
+ f, l = nil
10
+ assert_raise(exception) do
11
+ begin
12
+ get('/')
13
+ rescue => e
14
+ f, l = e.backtrace.first.split(':')
15
+ raise
16
+ end
17
+ end
18
+ assert_equal(filename, f, "expected #{exception.name} in #{filename}, was #{f}")
19
+ assert_equal(line, l.to_i, "expected #{exception.name} in #{filename} at line #{line}, was at line #{l}")
20
+ end
21
+
22
+ def backtrace_app(&block)
23
+ mock_app {
24
+ use_in_file_templates!
25
+ set :views, RenderBacktraceTest::VIEWS
26
+ template :builder_template do
27
+ 'raise "error"'
28
+ end
29
+ template :erb_template do
30
+ '<% raise "error" %>'
31
+ end
32
+ template :haml_template do
33
+ '%h1= raise "error"'
34
+ end
35
+ template :sass_template do
36
+ '+syntax-error'
37
+ end
38
+ get '/', &block
39
+ }
40
+ end
41
+
42
+ it "provides backtrace for Builder template" do
43
+ backtrace_app { builder :error }
44
+ assert_raise_at(File.join(VIEWS,'error.builder'), 2)
45
+ end
46
+
47
+ it "provides backtrace for ERB template" do
48
+ backtrace_app { erb :error }
49
+ assert_raise_at(File.join(VIEWS,'error.erb'), 2)
50
+ end
51
+
52
+ it "provides backtrace for HAML template" do
53
+ backtrace_app { haml :error }
54
+ assert_raise_at(File.join(VIEWS,'error.haml'), 2)
55
+ end
56
+
57
+ it "provides backtrace for Sass template" do
58
+ backtrace_app { sass :error }
59
+ assert_raise_at(File.join(VIEWS,'error.sass'), 2, Sass::SyntaxError)
60
+ end
61
+
62
+ it "provides backtrace for ERB template with locals" do
63
+ backtrace_app { erb :error, {}, :french => true }
64
+ assert_raise_at(File.join(VIEWS,'error.erb'), 3)
65
+ end
66
+
67
+ it "provides backtrace for HAML template with locals" do
68
+ backtrace_app { haml :error, {}, :french => true }
69
+ assert_raise_at(File.join(VIEWS,'error.haml'), 3)
70
+ end
71
+
72
+ it "provides backtrace for inline Builder string" do
73
+ backtrace_app { builder "raise 'Ack! Thbbbt!'"}
74
+ assert_raise_at(__FILE__, (__LINE__-1))
75
+ end
76
+
77
+ it "provides backtrace for inline ERB string" do
78
+ backtrace_app { erb "<% raise 'bidi-bidi-bidi' %>" }
79
+ assert_raise_at(__FILE__, (__LINE__-1))
80
+ end
81
+
82
+ it "provides backtrace for inline HAML string" do
83
+ backtrace_app { haml "%h1= raise 'Lions and tigers and bears! Oh, my!'" }
84
+ assert_raise_at(__FILE__, (__LINE__-1))
85
+ end
86
+
87
+ # it "provides backtrace for inline Sass string" do
88
+ # backtrace_app { sass '+buh-bye' }
89
+ # assert_raise_at(__FILE__, (__LINE__-1), Sass::SyntaxError)
90
+ # end
91
+
92
+ it "provides backtrace for named Builder template" do
93
+ backtrace_app { builder :builder_template }
94
+ assert_raise_at(__FILE__, (__LINE__-68))
95
+ end
96
+
97
+ it "provides backtrace for named ERB template" do
98
+ backtrace_app { erb :erb_template }
99
+ assert_raise_at(__FILE__, (__LINE__-70))
100
+ end
101
+
102
+ it "provides backtrace for named HAML template" do
103
+ backtrace_app { haml :haml_template }
104
+ assert_raise_at(__FILE__, (__LINE__-72))
105
+ end
106
+
107
+ # it "provides backtrace for named Sass template" do
108
+ # backtrace_app { sass :sass_template }
109
+ # assert_raise_at(__FILE__, (__LINE__-74), Sass::SyntaxError)
110
+ # end
111
+
112
+ it "provides backtrace for in file Builder template" do
113
+ backtrace_app { builder :builder_in_file }
114
+ assert_raise_at(__FILE__, (__LINE__+22))
115
+ end
116
+
117
+ it "provides backtrace for in file ERB template" do
118
+ backtrace_app { erb :erb_in_file }
119
+ assert_raise_at(__FILE__, (__LINE__+20))
120
+ end
121
+
122
+ it "provides backtrace for in file HAML template" do
123
+ backtrace_app { haml :haml_in_file }
124
+ assert_raise_at(__FILE__, (__LINE__+18))
125
+ end
126
+
127
+ # it "provides backtrace for in file Sass template" do
128
+ # backtrace_app { sass :sass_in_file }
129
+ # assert_raise_at(__FILE__, (__LINE__+16), Sass::SyntaxError)
130
+ # end
131
+ end
132
+
133
+ __END__
134
+
135
+ @@ builder_in_file
136
+ raise "bif"
137
+
138
+ @@ erb_in_file
139
+ <% raise "bam" %>
140
+
141
+ @@ haml_in_file
142
+ %h1= raise "pow"
143
+
144
+ @@ sass_in_file
145
+ +blam
@@ -1,11 +1,33 @@
1
- require 'test/spec'
2
- require 'sinatra/base'
3
- require 'sinatra/test'
1
+ require File.dirname(__FILE__) + '/helper'
4
2
 
5
- describe 'Sinatra::Request' do
3
+ class RequestTest < Test::Unit::TestCase
6
4
  it 'responds to #user_agent' do
7
5
  request = Sinatra::Request.new({'HTTP_USER_AGENT' => 'Test'})
8
- request.should.respond_to :user_agent
9
- request.user_agent.should.equal 'Test'
6
+ assert request.respond_to?(:user_agent)
7
+ assert_equal 'Test', request.user_agent
8
+ end
9
+
10
+ it 'parses POST params when Content-Type is form-dataish' do
11
+ request = Sinatra::Request.new(
12
+ 'REQUEST_METHOD' => 'PUT',
13
+ 'CONTENT_TYPE' => 'application/x-www-form-urlencoded',
14
+ 'rack.input' => StringIO.new('foo=bar')
15
+ )
16
+ assert_equal 'bar', request.params['foo']
17
+ end
18
+
19
+ it 'is secure when the url scheme is https' do
20
+ request = Sinatra::Request.new('rack.url_scheme' => 'https')
21
+ assert request.secure?
22
+ end
23
+
24
+ it 'is not secure when the url scheme is http' do
25
+ request = Sinatra::Request.new('rack.url_scheme' => 'http')
26
+ assert !request.secure?
27
+ end
28
+
29
+ it 'respects X-Forwarded-Proto header for proxied SSL' do
30
+ request = Sinatra::Request.new('HTTP_X_FORWARDED_PROTO' => 'https')
31
+ assert request.secure?
10
32
  end
11
33
  end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+
3
+ require File.dirname(__FILE__) + '/helper'
4
+
5
+ class ResponseTest < Test::Unit::TestCase
6
+ setup do
7
+ @response = Sinatra::Response.new
8
+ end
9
+
10
+ it "initializes with 200, text/html, and empty body" do
11
+ assert_equal 200, @response.status
12
+ assert_equal 'text/html', @response['Content-Type']
13
+ assert_equal [], @response.body
14
+ end
15
+
16
+ it 'uses case insensitive headers' do
17
+ @response['content-type'] = 'application/foo'
18
+ assert_equal 'application/foo', @response['Content-Type']
19
+ assert_equal 'application/foo', @response['CONTENT-TYPE']
20
+ end
21
+
22
+ it 'writes to body' do
23
+ @response.body = 'Hello'
24
+ @response.write ' World'
25
+ assert_equal 'Hello World', @response.body
26
+ end
27
+
28
+ [204, 304].each do |status_code|
29
+ it "removes the Content-Type header and body when response status is #{status_code}" do
30
+ @response.status = status_code
31
+ @response.body = ['Hello World']
32
+ assert_equal [status_code, {}, []], @response.finish
33
+ end
34
+ end
35
+
36
+ it 'Calculates the Content-Length using the bytesize of the body' do
37
+ @response.body = ['Hello', 'World!', '✈']
38
+ status, headers, body = @response.finish
39
+ assert_equal '14', headers['Content-Length']
40
+ assert_equal @response.body, body
41
+ end
42
+ end
@@ -1,10 +1,6 @@
1
- require 'test/spec'
2
- require 'sinatra/base'
3
- require 'sinatra/test'
4
-
5
- describe 'Result Handling' do
6
- include Sinatra::Test
1
+ require File.dirname(__FILE__) + '/helper'
7
2
 
3
+ class ResultTest < Test::Unit::TestCase
8
4
  it "sets response.body when result is a String" do
9
5
  mock_app {
10
6
  get '/' do
@@ -13,8 +9,8 @@ describe 'Result Handling' do
13
9
  }
14
10
 
15
11
  get '/'
16
- should.be.ok
17
- body.should.equal 'Hello World'
12
+ assert ok?
13
+ assert_equal 'Hello World', body
18
14
  end
19
15
 
20
16
  it "sets response.body when result is an Array of Strings" do
@@ -25,8 +21,8 @@ describe 'Result Handling' do
25
21
  }
26
22
 
27
23
  get '/'
28
- should.be.ok
29
- body.should.equal 'HelloWorld'
24
+ assert ok?
25
+ assert_equal 'HelloWorld', body
30
26
  end
31
27
 
32
28
  it "sets response.body when result responds to #each" do
@@ -39,8 +35,8 @@ describe 'Result Handling' do
39
35
  }
40
36
 
41
37
  get '/'
42
- should.be.ok
43
- body.should.equal 'Hello World'
38
+ assert ok?
39
+ assert_equal 'Hello World', body
44
40
  end
45
41
 
46
42
  it "sets response.body to [] when result is nil" do
@@ -51,8 +47,8 @@ describe 'Result Handling' do
51
47
  }
52
48
 
53
49
  get '/'
54
- should.be.ok
55
- body.should.equal ''
50
+ assert ok?
51
+ assert_equal '', body
56
52
  end
57
53
 
58
54
  it "sets status, headers, and body when result is a Rack response tuple" do
@@ -63,9 +59,9 @@ describe 'Result Handling' do
63
59
  }
64
60
 
65
61
  get '/'
66
- status.should.equal 205
67
- response['Content-Type'].should.equal 'foo/bar'
68
- body.should.equal 'Hello World'
62
+ assert_equal 205, status
63
+ assert_equal 'foo/bar', response['Content-Type']
64
+ assert_equal 'Hello World', body
69
65
  end
70
66
 
71
67
  it "sets status and body when result is a two-tuple" do
@@ -76,8 +72,18 @@ describe 'Result Handling' do
76
72
  }
77
73
 
78
74
  get '/'
79
- status.should.equal 409
80
- body.should.equal 'formula of'
75
+ assert_equal 409, status
76
+ assert_equal 'formula of', body
77
+ end
78
+
79
+ it "raises a TypeError when result is a non two or three tuple Array" do
80
+ mock_app {
81
+ get '/' do
82
+ [409, 'formula of', 'something else', 'even more']
83
+ end
84
+ }
85
+
86
+ assert_raise(TypeError) { get '/' }
81
87
  end
82
88
 
83
89
  it "sets status when result is a Fixnum status code" do
@@ -86,7 +92,7 @@ describe 'Result Handling' do
86
92
  }
87
93
 
88
94
  get '/'
89
- status.should.equal 205
90
- body.should.be.empty
95
+ assert_equal 205, status
96
+ assert_equal '', body
91
97
  end
92
98
  end
@@ -0,0 +1,59 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ module RouteAddedTest
4
+ @routes, @procs = [], []
5
+ def self.routes ; @routes ; end
6
+ def self.procs ; @procs ; end
7
+ def self.route_added(verb, path, proc)
8
+ @routes << [verb, path]
9
+ @procs << proc
10
+ end
11
+ end
12
+
13
+ class RouteAddedHookTest < Test::Unit::TestCase
14
+ setup {
15
+ RouteAddedTest.routes.clear
16
+ RouteAddedTest.procs.clear
17
+ }
18
+
19
+ it "should be notified of an added route" do
20
+ mock_app(Class.new(Sinatra::Base)) {
21
+ register RouteAddedTest
22
+ get('/') {}
23
+ }
24
+
25
+ assert_equal [["GET", "/"], ["HEAD", "/"]],
26
+ RouteAddedTest.routes
27
+ end
28
+
29
+ it "should include hooks from superclass" do
30
+ a = Class.new(Class.new(Sinatra::Base))
31
+ b = Class.new(a)
32
+
33
+ a.register RouteAddedTest
34
+ b.class_eval { post("/sub_app_route") {} }
35
+
36
+ assert_equal [["POST", "/sub_app_route"]],
37
+ RouteAddedTest.routes
38
+ end
39
+
40
+ it "should only run once per extension" do
41
+ mock_app(Class.new(Sinatra::Base)) {
42
+ register RouteAddedTest
43
+ register RouteAddedTest
44
+ get('/') {}
45
+ }
46
+
47
+ assert_equal [["GET", "/"], ["HEAD", "/"]],
48
+ RouteAddedTest.routes
49
+ end
50
+
51
+ it "should pass route blocks as an argument" do
52
+ mock_app(Class.new(Sinatra::Base)) {
53
+ register RouteAddedTest
54
+ get('/') {}
55
+ }
56
+
57
+ assert_kind_of Proc, RouteAddedTest.procs.first
58
+ end
59
+ end
@@ -1,11 +1,28 @@
1
- require 'test/spec'
2
- require 'sinatra/base'
3
- require 'sinatra/test'
1
+ require File.dirname(__FILE__) + '/helper'
4
2
 
5
- describe "Routing" do
6
- include Sinatra::Test
3
+ # Helper method for easy route pattern matching testing
4
+ def route_def(pattern)
5
+ mock_app { get(pattern) { } }
6
+ end
7
+
8
+ class RegexpLookAlike
9
+ class MatchData
10
+ def captures
11
+ ["this", "is", "a", "test"]
12
+ end
13
+ end
14
+
15
+ def match(string)
16
+ ::RegexpLookAlike::MatchData.new if string == "/this/is/a/test/"
17
+ end
18
+
19
+ def keys
20
+ ["one", "two", "three", "four"]
21
+ end
22
+ end
7
23
 
8
- %w[get put post delete head].each do |verb|
24
+ class RoutingTest < Test::Unit::TestCase
25
+ %w[get put post delete].each do |verb|
9
26
  it "defines #{verb.upcase} request handlers with #{verb}" do
10
27
  mock_app {
11
28
  send verb, '/hello' do
@@ -15,40 +32,91 @@ describe "Routing" do
15
32
 
16
33
  request = Rack::MockRequest.new(@app)
17
34
  response = request.request(verb.upcase, '/hello', {})
18
- response.should.be.ok
19
- response.body.should.equal 'Hello World'
35
+ assert response.ok?
36
+ assert_equal 'Hello World', response.body
20
37
  end
21
38
  end
22
39
 
40
+ it "defines HEAD request handlers with HEAD" do
41
+ mock_app {
42
+ head '/hello' do
43
+ response['X-Hello'] = 'World!'
44
+ 'remove me'
45
+ end
46
+ }
47
+
48
+ request = Rack::MockRequest.new(@app)
49
+ response = request.request('HEAD', '/hello', {})
50
+ assert response.ok?
51
+ assert_equal 'World!', response['X-Hello']
52
+ assert_equal '', response.body
53
+ end
54
+
23
55
  it "404s when no route satisfies the request" do
24
56
  mock_app {
25
57
  get('/foo') { }
26
58
  }
27
59
  get '/bar'
28
- status.should.equal 404
60
+ assert_equal 404, status
61
+ end
62
+
63
+ it "overrides the content-type in error handlers" do
64
+ mock_app {
65
+ before { content_type 'text/plain' }
66
+ error Sinatra::NotFound do
67
+ content_type "text/html"
68
+ "<h1>Not Found</h1>"
69
+ end
70
+ }
71
+
72
+ get '/foo'
73
+ assert_equal 404, status
74
+ assert_equal 'text/html', response["Content-Type"]
75
+ assert_equal "<h1>Not Found</h1>", response.body
76
+ end
77
+
78
+ it 'takes multiple definitions of a route' do
79
+ mock_app {
80
+ user_agent(/Foo/)
81
+ get '/foo' do
82
+ 'foo'
83
+ end
84
+
85
+ get '/foo' do
86
+ 'not foo'
87
+ end
88
+ }
89
+
90
+ get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo'
91
+ assert ok?
92
+ assert_equal 'foo', body
93
+
94
+ get '/foo'
95
+ assert ok?
96
+ assert_equal 'not foo', body
29
97
  end
30
98
 
31
99
  it "exposes params with indifferent hash" do
32
100
  mock_app {
33
101
  get '/:foo' do
34
- params['foo'].should.equal 'bar'
35
- params[:foo].should.equal 'bar'
102
+ assert_equal 'bar', params['foo']
103
+ assert_equal 'bar', params[:foo]
36
104
  'well, alright'
37
105
  end
38
106
  }
39
107
  get '/bar'
40
- body.should.equal 'well, alright'
108
+ assert_equal 'well, alright', body
41
109
  end
42
110
 
43
111
  it "merges named params and query string params in params" do
44
112
  mock_app {
45
113
  get '/:foo' do
46
- params['foo'].should.equal 'bar'
47
- params['baz'].should.equal 'biz'
114
+ assert_equal 'bar', params['foo']
115
+ assert_equal 'biz', params['baz']
48
116
  end
49
117
  }
50
118
  get '/bar?baz=biz'
51
- should.be.ok
119
+ assert ok?
52
120
  end
53
121
 
54
122
  it "supports named params like /hello/:person" do
@@ -58,7 +126,7 @@ describe "Routing" do
58
126
  end
59
127
  }
60
128
  get '/hello/Frank'
61
- body.should.equal 'Hello Frank'
129
+ assert_equal 'Hello Frank', body
62
130
  end
63
131
 
64
132
  it "supports optional named params like /?:foo?/?:bar?" do
@@ -69,61 +137,181 @@ describe "Routing" do
69
137
  }
70
138
 
71
139
  get '/hello/world'
72
- should.be.ok
73
- body.should.equal "foo=hello;bar=world"
140
+ assert ok?
141
+ assert_equal "foo=hello;bar=world", body
74
142
 
75
143
  get '/hello'
76
- should.be.ok
77
- body.should.equal "foo=hello;bar="
144
+ assert ok?
145
+ assert_equal "foo=hello;bar=", body
78
146
 
79
147
  get '/'
80
- should.be.ok
81
- body.should.equal "foo=;bar="
148
+ assert ok?
149
+ assert_equal "foo=;bar=", body
82
150
  end
83
151
 
84
152
  it "supports single splat params like /*" do
85
153
  mock_app {
86
154
  get '/*' do
87
- params['splat'].should.be.kind_of Array
155
+ assert params['splat'].kind_of?(Array)
88
156
  params['splat'].join "\n"
89
157
  end
90
158
  }
91
159
 
92
160
  get '/foo'
93
- body.should.equal "foo"
161
+ assert_equal "foo", body
94
162
 
95
163
  get '/foo/bar/baz'
96
- body.should.equal "foo/bar/baz"
164
+ assert_equal "foo/bar/baz", body
97
165
  end
98
166
 
99
167
  it "supports mixing multiple splat params like /*/foo/*/*" do
100
168
  mock_app {
101
169
  get '/*/foo/*/*' do
102
- params['splat'].should.be.kind_of Array
170
+ assert params['splat'].kind_of?(Array)
103
171
  params['splat'].join "\n"
104
172
  end
105
173
  }
106
174
 
107
175
  get '/bar/foo/bling/baz/boom'
108
- body.should.equal "bar\nbling\nbaz/boom"
176
+ assert_equal "bar\nbling\nbaz/boom", body
109
177
 
110
178
  get '/bar/foo/baz'
111
- should.be.not_found
179
+ assert not_found?
112
180
  end
113
181
 
114
182
  it "supports mixing named and splat params like /:foo/*" do
115
183
  mock_app {
116
184
  get '/:foo/*' do
117
- params['foo'].should.equal 'foo'
118
- params['splat'].should.equal ['bar/baz']
185
+ assert_equal 'foo', params['foo']
186
+ assert_equal ['bar/baz'], params['splat']
119
187
  end
120
188
  }
121
189
 
122
190
  get '/foo/bar/baz'
123
- should.be.ok
191
+ assert ok?
192
+ end
193
+
194
+ it "matches a dot ('.') as part of a named param" do
195
+ mock_app {
196
+ get '/:foo/:bar' do
197
+ params[:foo]
198
+ end
199
+ }
200
+
201
+ get '/user@example.com/name'
202
+ assert_equal 200, response.status
203
+ assert_equal 'user@example.com', body
204
+ end
205
+
206
+ it "matches a literal dot ('.') outside of named params" do
207
+ mock_app {
208
+ get '/:file.:ext' do
209
+ assert_equal 'pony', params[:file]
210
+ assert_equal 'jpg', params[:ext]
211
+ 'right on'
212
+ end
213
+ }
214
+
215
+ get '/pony.jpg'
216
+ assert_equal 200, response.status
217
+ assert_equal 'right on', body
218
+ end
219
+
220
+ it "literally matches . in paths" do
221
+ route_def '/test.bar'
222
+
223
+ get '/test.bar'
224
+ assert ok?
225
+ get 'test0bar'
226
+ assert not_found?
227
+ end
228
+
229
+ it "literally matches $ in paths" do
230
+ route_def '/test$/'
231
+
232
+ get '/test$/'
233
+ assert ok?
234
+ end
235
+
236
+ it "literally matches + in paths" do
237
+ route_def '/te+st/'
238
+
239
+ get '/te%2Bst/'
240
+ assert ok?
241
+ get '/teeeeeeest/'
242
+ assert not_found?
243
+ end
244
+
245
+ it "literally matches () in paths" do
246
+ route_def '/test(bar)/'
247
+
248
+ get '/test(bar)/'
249
+ assert ok?
250
+ end
251
+
252
+ it "supports basic nested params" do
253
+ mock_app {
254
+ get '/hi' do
255
+ params["person"]["name"]
256
+ end
257
+ }
258
+
259
+ get "/hi?person[name]=John+Doe"
260
+ assert ok?
261
+ assert_equal "John Doe", body
262
+ end
263
+
264
+ it "exposes nested params with indifferent hash" do
265
+ mock_app {
266
+ get '/testme' do
267
+ assert_equal 'baz', params['bar']['foo']
268
+ assert_equal 'baz', params['bar'][:foo]
269
+ 'well, alright'
270
+ end
271
+ }
272
+ get '/testme?bar[foo]=baz'
273
+ assert_equal 'well, alright', body
124
274
  end
125
275
 
126
- it "supports paths that include spaces" do
276
+ it "supports deeply nested params" do
277
+ expected_params = {
278
+ "emacs" => {
279
+ "map" => { "goto-line" => "M-g g" },
280
+ "version" => "22.3.1"
281
+ },
282
+ "browser" => {
283
+ "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}},
284
+ "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}}
285
+ },
286
+ "paste" => {"name"=>"hello world", "syntax"=>"ruby"}
287
+ }
288
+ mock_app {
289
+ get '/foo' do
290
+ assert_equal expected_params, params
291
+ 'looks good'
292
+ end
293
+ }
294
+ get '/foo', expected_params
295
+ assert ok?
296
+ assert_equal 'looks good', body
297
+ end
298
+
299
+ it "preserves non-nested params" do
300
+ mock_app {
301
+ get '/foo' do
302
+ assert_equal "2", params["article_id"]
303
+ assert_equal "awesome", params['comment']['body']
304
+ assert_nil params['comment[body]']
305
+ 'looks good'
306
+ end
307
+ }
308
+
309
+ get '/foo?article_id=2&comment[body]=awesome'
310
+ assert ok?
311
+ assert_equal 'looks good', body
312
+ end
313
+
314
+ it "matches paths that include spaces encoded with %20" do
127
315
  mock_app {
128
316
  get '/path with spaces' do
129
317
  'looks good'
@@ -131,21 +319,33 @@ describe "Routing" do
131
319
  }
132
320
 
133
321
  get '/path%20with%20spaces'
134
- should.be.ok
135
- body.should.equal 'looks good'
322
+ assert ok?
323
+ assert_equal 'looks good', body
324
+ end
325
+
326
+ it "matches paths that include spaces encoded with +" do
327
+ mock_app {
328
+ get '/path with spaces' do
329
+ 'looks good'
330
+ end
331
+ }
332
+
333
+ get '/path+with+spaces'
334
+ assert ok?
335
+ assert_equal 'looks good', body
136
336
  end
137
337
 
138
338
  it "URL decodes named parameters and splats" do
139
339
  mock_app {
140
340
  get '/:foo/*' do
141
- params['foo'].should.equal 'hello world'
142
- params['splat'].should.equal ['how are you']
341
+ assert_equal 'hello world', params['foo']
342
+ assert_equal ['how are you'], params['splat']
143
343
  nil
144
344
  end
145
345
  }
146
346
 
147
347
  get '/hello%20world/how%20are%20you'
148
- should.be.ok
348
+ assert ok?
149
349
  end
150
350
 
151
351
  it 'supports regular expressions' do
@@ -156,21 +356,43 @@ describe "Routing" do
156
356
  }
157
357
 
158
358
  get '/foooom/bar'
159
- should.be.ok
160
- body.should.equal 'Hello World'
359
+ assert ok?
360
+ assert_equal 'Hello World', body
161
361
  end
162
362
 
163
363
  it 'makes regular expression captures available in params[:captures]' do
164
364
  mock_app {
165
365
  get(/^\/fo(.*)\/ba(.*)/) do
166
- params[:captures].should.equal ['orooomma', 'f']
366
+ assert_equal ['orooomma', 'f'], params[:captures]
167
367
  'right on'
168
368
  end
169
369
  }
170
370
 
171
371
  get '/foorooomma/baf'
172
- should.be.ok
173
- body.should.equal 'right on'
372
+ assert ok?
373
+ assert_equal 'right on', body
374
+ end
375
+
376
+ it 'supports regular expression look-alike routes' do
377
+ mock_app {
378
+ get(RegexpLookAlike.new) do
379
+ assert_equal 'this', params[:one]
380
+ assert_equal 'is', params[:two]
381
+ assert_equal 'a', params[:three]
382
+ assert_equal 'test', params[:four]
383
+ 'right on'
384
+ end
385
+ }
386
+
387
+ get '/this/is/a/test/'
388
+ assert ok?
389
+ assert_equal 'right on', body
390
+ end
391
+
392
+ it 'raises a TypeError when pattern is not a String or Regexp' do
393
+ assert_raise(TypeError) {
394
+ mock_app { get(42){} }
395
+ }
174
396
  end
175
397
 
176
398
  it "returns response immediately on halt" do
@@ -182,8 +404,32 @@ describe "Routing" do
182
404
  }
183
405
 
184
406
  get '/'
185
- should.be.ok
186
- body.should.equal 'Hello World'
407
+ assert ok?
408
+ assert_equal 'Hello World', body
409
+ end
410
+
411
+ it "halts with a response tuple" do
412
+ mock_app {
413
+ get '/' do
414
+ halt 295, {'Content-Type' => 'text/plain'}, 'Hello World'
415
+ end
416
+ }
417
+
418
+ get '/'
419
+ assert_equal 295, status
420
+ assert_equal 'text/plain', response['Content-Type']
421
+ assert_equal 'Hello World', body
422
+ end
423
+
424
+ it "halts with an array of strings" do
425
+ mock_app {
426
+ get '/' do
427
+ halt %w[Hello World How Are You]
428
+ end
429
+ }
430
+
431
+ get '/'
432
+ assert_equal 'HelloWorldHowAreYou', body
187
433
  end
188
434
 
189
435
  it "transitions to the next matching route on pass" do
@@ -194,14 +440,14 @@ describe "Routing" do
194
440
  end
195
441
 
196
442
  get '/*' do
197
- params.should.not.include 'foo'
443
+ assert !params.include?('foo')
198
444
  'Hello World'
199
445
  end
200
446
  }
201
447
 
202
448
  get '/bar'
203
- should.be.ok
204
- body.should.equal 'Hello World'
449
+ assert ok?
450
+ assert_equal 'Hello World', body
205
451
  end
206
452
 
207
453
  it "transitions to 404 when passed and no subsequent route matches" do
@@ -213,7 +459,7 @@ describe "Routing" do
213
459
  }
214
460
 
215
461
  get '/bar'
216
- should.be.not_found
462
+ assert not_found?
217
463
  end
218
464
 
219
465
  it "passes when matching condition returns false" do
@@ -225,11 +471,11 @@ describe "Routing" do
225
471
  }
226
472
 
227
473
  get '/bar'
228
- should.be.ok
229
- body.should.equal 'Hello World'
474
+ assert ok?
475
+ assert_equal 'Hello World', body
230
476
 
231
477
  get '/foo'
232
- should.be.not_found
478
+ assert not_found?
233
479
  end
234
480
 
235
481
  it "does not pass when matching condition returns nil" do
@@ -241,8 +487,8 @@ describe "Routing" do
241
487
  }
242
488
 
243
489
  get '/bar'
244
- should.be.ok
245
- body.should.equal 'Hello World'
490
+ assert ok?
491
+ assert_equal 'Hello World', body
246
492
  end
247
493
 
248
494
  it "passes to next route when condition calls pass explicitly" do
@@ -254,11 +500,11 @@ describe "Routing" do
254
500
  }
255
501
 
256
502
  get '/bar'
257
- should.be.ok
258
- body.should.equal 'Hello World'
503
+ assert ok?
504
+ assert_equal 'Hello World', body
259
505
 
260
506
  get '/foo'
261
- should.be.not_found
507
+ assert not_found?
262
508
  end
263
509
 
264
510
  it "passes to the next route when host_name does not match" do
@@ -269,11 +515,11 @@ describe "Routing" do
269
515
  end
270
516
  }
271
517
  get '/foo'
272
- should.be.not_found
518
+ assert not_found?
273
519
 
274
- get '/foo', :env => { 'HTTP_HOST' => 'example.com' }
275
- status.should.equal 200
276
- body.should.equal 'Hello World'
520
+ get '/foo', {}, { 'HTTP_HOST' => 'example.com' }
521
+ assert_equal 200, status
522
+ assert_equal 'Hello World', body
277
523
  end
278
524
 
279
525
  it "passes to the next route when user_agent does not match" do
@@ -284,11 +530,11 @@ describe "Routing" do
284
530
  end
285
531
  }
286
532
  get '/foo'
287
- should.be.not_found
533
+ assert not_found?
288
534
 
289
- get '/foo', :env => { 'HTTP_USER_AGENT' => 'Foo Bar' }
290
- status.should.equal 200
291
- body.should.equal 'Hello World'
535
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
536
+ assert_equal 200, status
537
+ assert_equal 'Hello World', body
292
538
  end
293
539
 
294
540
  it "makes captures in user agent pattern available in params[:agent]" do
@@ -298,9 +544,9 @@ describe "Routing" do
298
544
  'Hello ' + params[:agent].first
299
545
  end
300
546
  }
301
- get '/foo', :env => { 'HTTP_USER_AGENT' => 'Foo Bar' }
302
- status.should.equal 200
303
- body.should.equal 'Hello Bar'
547
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
548
+ assert_equal 200, status
549
+ assert_equal 'Hello Bar', body
304
550
  end
305
551
 
306
552
  it "filters by accept header" do
@@ -310,13 +556,13 @@ describe "Routing" do
310
556
  end
311
557
  }
312
558
 
313
- get '/', :env => { :accept => 'application/xml' }
314
- should.be.ok
315
- body.should.equal 'application/xml'
316
- response.headers['Content-Type'].should.equal 'application/xml'
559
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
560
+ assert ok?
561
+ assert_equal 'application/xml', body
562
+ assert_equal 'application/xml', response.headers['Content-Type']
317
563
 
318
- get '/', :env => { :accept => 'text/html' }
319
- should.not.be.ok
564
+ get '/', {}, { :accept => 'text/html' }
565
+ assert !ok?
320
566
  end
321
567
 
322
568
  it "allows multiple mime types for accept header" do
@@ -329,10 +575,245 @@ describe "Routing" do
329
575
  }
330
576
 
331
577
  types.each do |type|
332
- get '/', :env => { :accept => type }
333
- should.be.ok
334
- body.should.equal type
335
- response.headers['Content-Type'].should.equal type
578
+ get '/', {}, { 'HTTP_ACCEPT' => type }
579
+ assert ok?
580
+ assert_equal type, body
581
+ assert_equal type, response.headers['Content-Type']
336
582
  end
337
583
  end
584
+
585
+ it 'degrades gracefully when optional accept header is not provided' do
586
+ mock_app {
587
+ get '/', :provides => :xml do
588
+ request.env['HTTP_ACCEPT']
589
+ end
590
+ get '/' do
591
+ 'default'
592
+ end
593
+ }
594
+ get '/'
595
+ assert ok?
596
+ assert_equal 'default', body
597
+ end
598
+
599
+ it 'passes a single url param as block parameters when one param is specified' do
600
+ mock_app {
601
+ get '/:foo' do |foo|
602
+ assert_equal 'bar', foo
603
+ end
604
+ }
605
+
606
+ get '/bar'
607
+ assert ok?
608
+ end
609
+
610
+ it 'passes multiple params as block parameters when many are specified' do
611
+ mock_app {
612
+ get '/:foo/:bar/:baz' do |foo, bar, baz|
613
+ assert_equal 'abc', foo
614
+ assert_equal 'def', bar
615
+ assert_equal 'ghi', baz
616
+ end
617
+ }
618
+
619
+ get '/abc/def/ghi'
620
+ assert ok?
621
+ end
622
+
623
+ it 'passes regular expression captures as block parameters' do
624
+ mock_app {
625
+ get(/^\/fo(.*)\/ba(.*)/) do |foo, bar|
626
+ assert_equal 'orooomma', foo
627
+ assert_equal 'f', bar
628
+ 'looks good'
629
+ end
630
+ }
631
+
632
+ get '/foorooomma/baf'
633
+ assert ok?
634
+ assert_equal 'looks good', body
635
+ end
636
+
637
+ it "supports mixing multiple splat params like /*/foo/*/* as block parameters" do
638
+ mock_app {
639
+ get '/*/foo/*/*' do |foo, bar, baz|
640
+ assert_equal 'bar', foo
641
+ assert_equal 'bling', bar
642
+ assert_equal 'baz/boom', baz
643
+ 'looks good'
644
+ end
645
+ }
646
+
647
+ get '/bar/foo/bling/baz/boom'
648
+ assert ok?
649
+ assert_equal 'looks good', body
650
+ end
651
+
652
+ it 'raises an ArgumentError with block arity > 1 and too many values' do
653
+ mock_app {
654
+ get '/:foo/:bar/:baz' do |foo, bar|
655
+ 'quux'
656
+ end
657
+ }
658
+
659
+ assert_raise(ArgumentError) { get '/a/b/c' }
660
+ end
661
+
662
+ it 'raises an ArgumentError with block param arity > 1 and too few values' do
663
+ mock_app {
664
+ get '/:foo/:bar' do |foo, bar, baz|
665
+ 'quux'
666
+ end
667
+ }
668
+
669
+ assert_raise(ArgumentError) { get '/a/b' }
670
+ end
671
+
672
+ it 'succeeds if no block parameters are specified' do
673
+ mock_app {
674
+ get '/:foo/:bar' do
675
+ 'quux'
676
+ end
677
+ }
678
+
679
+ get '/a/b'
680
+ assert ok?
681
+ assert_equal 'quux', body
682
+ end
683
+
684
+ it 'passes all params with block param arity -1 (splat args)' do
685
+ mock_app {
686
+ get '/:foo/:bar' do |*args|
687
+ args.join
688
+ end
689
+ }
690
+
691
+ get '/a/b'
692
+ assert ok?
693
+ assert_equal 'ab', body
694
+ end
695
+
696
+ it 'allows custom route-conditions to be set via route options' do
697
+ protector = Module.new {
698
+ def protect(*args)
699
+ condition {
700
+ unless authorize(params["user"], params["password"])
701
+ halt 403, "go away"
702
+ end
703
+ }
704
+ end
705
+ }
706
+
707
+ mock_app {
708
+ register protector
709
+
710
+ helpers do
711
+ def authorize(username, password)
712
+ username == "foo" && password == "bar"
713
+ end
714
+ end
715
+
716
+ get "/", :protect => true do
717
+ "hey"
718
+ end
719
+ }
720
+
721
+ get "/"
722
+ assert forbidden?
723
+ assert_equal "go away", body
724
+
725
+ get "/", :user => "foo", :password => "bar"
726
+ assert ok?
727
+ assert_equal "hey", body
728
+ end
729
+
730
+ # NOTE Block params behaves differently under 1.8 and 1.9. Under 1.8, block
731
+ # param arity is lax: declaring a mismatched number of block params results
732
+ # in a warning. Under 1.9, block param arity is strict: mismatched block
733
+ # arity raises an ArgumentError.
734
+
735
+ if RUBY_VERSION >= '1.9'
736
+
737
+ it 'raises an ArgumentError with block param arity 1 and no values' do
738
+ mock_app {
739
+ get '/foo' do |foo|
740
+ 'quux'
741
+ end
742
+ }
743
+
744
+ assert_raise(ArgumentError) { get '/foo' }
745
+ end
746
+
747
+ it 'raises an ArgumentError with block param arity 1 and too many values' do
748
+ mock_app {
749
+ get '/:foo/:bar/:baz' do |foo|
750
+ 'quux'
751
+ end
752
+ }
753
+
754
+ assert_raise(ArgumentError) { get '/a/b/c' }
755
+ end
756
+
757
+ else
758
+
759
+ it 'does not raise an ArgumentError with block param arity 1 and no values' do
760
+ mock_app {
761
+ get '/foo' do |foo|
762
+ 'quux'
763
+ end
764
+ }
765
+
766
+ silence_warnings { get '/foo' }
767
+ assert ok?
768
+ assert_equal 'quux', body
769
+ end
770
+
771
+ it 'does not raise an ArgumentError with block param arity 1 and too many values' do
772
+ mock_app {
773
+ get '/:foo/:bar/:baz' do |foo|
774
+ 'quux'
775
+ end
776
+ }
777
+
778
+ silence_warnings { get '/a/b/c' }
779
+ assert ok?
780
+ assert_equal 'quux', body
781
+ end
782
+
783
+ end
784
+
785
+ it "matches routes defined in superclasses" do
786
+ base = Class.new(Sinatra::Base)
787
+ base.get('/foo') { 'foo in baseclass' }
788
+
789
+ mock_app(base) {
790
+ get('/bar') { 'bar in subclass' }
791
+ }
792
+
793
+ get '/foo'
794
+ assert ok?
795
+ assert_equal 'foo in baseclass', body
796
+
797
+ get '/bar'
798
+ assert ok?
799
+ assert_equal 'bar in subclass', body
800
+ end
801
+
802
+ it "matches routes in subclasses before superclasses" do
803
+ base = Class.new(Sinatra::Base)
804
+ base.get('/foo') { 'foo in baseclass' }
805
+ base.get('/bar') { 'bar in baseclass' }
806
+
807
+ mock_app(base) {
808
+ get('/foo') { 'foo in subclass' }
809
+ }
810
+
811
+ get '/foo'
812
+ assert ok?
813
+ assert_equal 'foo in subclass', body
814
+
815
+ get '/bar'
816
+ assert ok?
817
+ assert_equal 'bar in baseclass', body
818
+ end
338
819
  end