adamwiggins-sinatra 0.8.9
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.
- data/AUTHORS +40 -0
- data/CHANGES +167 -0
- data/LICENSE +22 -0
- data/README.rdoc +529 -0
- data/Rakefile +180 -0
- data/compat/app_test.rb +300 -0
- data/compat/application_test.rb +334 -0
- data/compat/builder_test.rb +101 -0
- data/compat/custom_error_test.rb +62 -0
- data/compat/erb_test.rb +136 -0
- data/compat/events_test.rb +75 -0
- data/compat/filter_test.rb +30 -0
- data/compat/haml_test.rb +233 -0
- data/compat/helper.rb +21 -0
- data/compat/mapped_error_test.rb +72 -0
- data/compat/pipeline_test.rb +71 -0
- data/compat/public/foo.xml +1 -0
- data/compat/sass_test.rb +57 -0
- data/compat/sessions_test.rb +39 -0
- data/compat/streaming_test.rb +121 -0
- data/compat/sym_params_test.rb +19 -0
- data/compat/template_test.rb +30 -0
- data/compat/use_in_file_templates_test.rb +47 -0
- data/compat/views/foo.builder +1 -0
- data/compat/views/foo.erb +1 -0
- data/compat/views/foo.haml +1 -0
- data/compat/views/foo.sass +2 -0
- data/compat/views/foo_layout.erb +2 -0
- data/compat/views/foo_layout.haml +2 -0
- data/compat/views/layout_test/foo.builder +1 -0
- data/compat/views/layout_test/foo.erb +1 -0
- data/compat/views/layout_test/foo.haml +1 -0
- data/compat/views/layout_test/foo.sass +2 -0
- data/compat/views/layout_test/layout.builder +3 -0
- data/compat/views/layout_test/layout.erb +1 -0
- data/compat/views/layout_test/layout.haml +1 -0
- data/compat/views/layout_test/layout.sass +2 -0
- data/compat/views/no_layout/no_layout.builder +1 -0
- data/compat/views/no_layout/no_layout.haml +1 -0
- data/lib/sinatra/base.rb +818 -0
- data/lib/sinatra/compat.rb +239 -0
- data/lib/sinatra/images/404.png +0 -0
- data/lib/sinatra/images/500.png +0 -0
- data/lib/sinatra/main.rb +48 -0
- data/lib/sinatra/test/rspec.rb +2 -0
- data/lib/sinatra/test/spec.rb +2 -0
- data/lib/sinatra/test/unit.rb +11 -0
- data/lib/sinatra/test.rb +112 -0
- data/lib/sinatra.rb +3 -0
- data/sinatra.gemspec +107 -0
- data/test/base_test.rb +72 -0
- data/test/builder_test.rb +68 -0
- data/test/data/reload_app_file.rb +3 -0
- data/test/erb_test.rb +55 -0
- data/test/filter_test.rb +39 -0
- data/test/haml_test.rb +72 -0
- data/test/helpers_test.rb +368 -0
- data/test/mapped_error_test.rb +164 -0
- data/test/middleware_test.rb +63 -0
- data/test/options_test.rb +103 -0
- data/test/reload_test.rb +65 -0
- data/test/request_test.rb +11 -0
- data/test/result_test.rb +92 -0
- data/test/routing_test.rb +338 -0
- data/test/sass_test.rb +40 -0
- data/test/sinatra_test.rb +15 -0
- data/test/static_test.rb +60 -0
- data/test/templates_test.rb +92 -0
- data/test/views/hello.builder +1 -0
- data/test/views/hello.erb +1 -0
- data/test/views/hello.haml +1 -0
- data/test/views/hello.sass +2 -0
- data/test/views/hello.test +1 -0
- data/test/views/layout2.builder +3 -0
- data/test/views/layout2.erb +2 -0
- data/test/views/layout2.haml +2 -0
- data/test/views/layout2.test +1 -0
- metadata +159 -0
data/test/erb_test.rb
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
require 'sinatra/base'
|
|
3
|
+
require 'sinatra/test'
|
|
4
|
+
|
|
5
|
+
describe "ERB Templates" do
|
|
6
|
+
include Sinatra::Test
|
|
7
|
+
|
|
8
|
+
def erb_app(&block)
|
|
9
|
+
mock_app {
|
|
10
|
+
set :views, File.dirname(__FILE__) + '/views'
|
|
11
|
+
get '/', &block
|
|
12
|
+
}
|
|
13
|
+
get '/'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'renders inline ERB strings' do
|
|
17
|
+
erb_app { erb '<%= 1 + 1 %>' }
|
|
18
|
+
should.be.ok
|
|
19
|
+
body.should.equal '2'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'renders .erb files in views path' do
|
|
23
|
+
erb_app { erb :hello }
|
|
24
|
+
should.be.ok
|
|
25
|
+
body.should.equal "Hello World\n"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'takes a :locals option' do
|
|
29
|
+
erb_app {
|
|
30
|
+
locals = {:foo => 'Bar'}
|
|
31
|
+
erb '<%= foo %>', :locals => locals
|
|
32
|
+
}
|
|
33
|
+
should.be.ok
|
|
34
|
+
body.should.equal 'Bar'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "renders with inline layouts" do
|
|
38
|
+
mock_app {
|
|
39
|
+
layout { 'THIS. IS. <%= yield.upcase %>!' }
|
|
40
|
+
get('/') { erb 'Sparta' }
|
|
41
|
+
}
|
|
42
|
+
get '/'
|
|
43
|
+
should.be.ok
|
|
44
|
+
body.should.equal 'THIS. IS. SPARTA!'
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
it "renders with file layouts" do
|
|
48
|
+
erb_app {
|
|
49
|
+
erb 'Hello World', :layout => :layout2
|
|
50
|
+
}
|
|
51
|
+
should.be.ok
|
|
52
|
+
body.should.equal "ERB Layout!\nHello World\n"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
data/test/filter_test.rb
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
require 'sinatra/base'
|
|
3
|
+
require 'sinatra/test'
|
|
4
|
+
|
|
5
|
+
describe "Filters" do
|
|
6
|
+
include Sinatra::Test
|
|
7
|
+
|
|
8
|
+
it "executes filters in the order defined" do
|
|
9
|
+
count = 0
|
|
10
|
+
mock_app do
|
|
11
|
+
get('/') { 'Hello World' }
|
|
12
|
+
before {
|
|
13
|
+
count.should.be 0
|
|
14
|
+
count = 1
|
|
15
|
+
}
|
|
16
|
+
before {
|
|
17
|
+
count.should.be 1
|
|
18
|
+
count = 2
|
|
19
|
+
}
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
get '/'
|
|
23
|
+
should.be.ok
|
|
24
|
+
count.should.be 2
|
|
25
|
+
body.should.equal 'Hello World'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "allows filters to modify the request" do
|
|
29
|
+
mock_app {
|
|
30
|
+
get('/foo') { 'foo' }
|
|
31
|
+
get('/bar') { 'bar' }
|
|
32
|
+
before { request.path_info = '/bar' }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
get '/foo'
|
|
36
|
+
should.be.ok
|
|
37
|
+
body.should.be == 'bar'
|
|
38
|
+
end
|
|
39
|
+
end
|
data/test/haml_test.rb
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
require 'sinatra/base'
|
|
3
|
+
require 'sinatra/test'
|
|
4
|
+
|
|
5
|
+
describe "HAML Templates" do
|
|
6
|
+
include Sinatra::Test
|
|
7
|
+
|
|
8
|
+
def haml_app(&block)
|
|
9
|
+
mock_app {
|
|
10
|
+
set :views, File.dirname(__FILE__) + '/views'
|
|
11
|
+
get '/', &block
|
|
12
|
+
}
|
|
13
|
+
get '/'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
it 'renders inline HAML strings' do
|
|
17
|
+
haml_app { haml '%h1 Hiya' }
|
|
18
|
+
should.be.ok
|
|
19
|
+
body.should.equal "<h1>Hiya</h1>\n"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
it 'renders .haml files in views path' do
|
|
23
|
+
haml_app { haml :hello }
|
|
24
|
+
should.be.ok
|
|
25
|
+
body.should.equal "<h1>Hello From Haml</h1>\n"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it "renders with inline layouts" do
|
|
29
|
+
mock_app {
|
|
30
|
+
layout { %q(%h1= 'THIS. IS. ' + yield.upcase) }
|
|
31
|
+
get('/') { haml '%em Sparta' }
|
|
32
|
+
}
|
|
33
|
+
get '/'
|
|
34
|
+
should.be.ok
|
|
35
|
+
body.should.equal "<h1>THIS. IS. <EM>SPARTA</EM></h1>\n"
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "renders with file layouts" do
|
|
39
|
+
haml_app {
|
|
40
|
+
haml 'Hello World', :layout => :layout2
|
|
41
|
+
}
|
|
42
|
+
should.be.ok
|
|
43
|
+
body.should.equal "<h1>HAML Layout!</h1>\n<p>Hello World</p>\n"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "raises error if template not found" do
|
|
47
|
+
mock_app {
|
|
48
|
+
get('/') { haml :no_such_template }
|
|
49
|
+
}
|
|
50
|
+
lambda { get('/') }.should.raise(Errno::ENOENT)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it "passes HAML options to the Haml engine" do
|
|
54
|
+
haml_app {
|
|
55
|
+
haml "!!!\n%h1 Hello World", :options => {:format => :html5}
|
|
56
|
+
}
|
|
57
|
+
should.be.ok
|
|
58
|
+
body.should.equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n"
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "passes default HAML options to the Haml engine" do
|
|
62
|
+
mock_app {
|
|
63
|
+
set :haml, {:format => :html5}
|
|
64
|
+
get '/' do
|
|
65
|
+
haml "!!!\n%h1 Hello World"
|
|
66
|
+
end
|
|
67
|
+
}
|
|
68
|
+
get '/'
|
|
69
|
+
should.be.ok
|
|
70
|
+
body.should.equal "<!DOCTYPE html>\n<h1>Hello World</h1>\n"
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
require 'sinatra/base'
|
|
3
|
+
require 'sinatra/test'
|
|
4
|
+
|
|
5
|
+
class Test::Unit::TestCase
|
|
6
|
+
include Sinatra::Test
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
describe 'Sinatra::Helpers' do
|
|
10
|
+
describe '#status' do
|
|
11
|
+
setup do
|
|
12
|
+
mock_app {
|
|
13
|
+
get '/' do
|
|
14
|
+
status 207
|
|
15
|
+
nil
|
|
16
|
+
end
|
|
17
|
+
}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it 'sets the response status code' do
|
|
21
|
+
get '/'
|
|
22
|
+
response.status.should.equal 207
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe '#body' do
|
|
27
|
+
it 'takes a block for defered body generation' do
|
|
28
|
+
mock_app {
|
|
29
|
+
get '/' do
|
|
30
|
+
body { 'Hello World' }
|
|
31
|
+
end
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get '/'
|
|
35
|
+
body.should.equal 'Hello World'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it 'takes a String, Array, or other object responding to #each' do
|
|
39
|
+
mock_app {
|
|
40
|
+
get '/' do
|
|
41
|
+
body 'Hello World'
|
|
42
|
+
end
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get '/'
|
|
46
|
+
body.should.equal 'Hello World'
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
describe '#redirect' do
|
|
51
|
+
it 'uses a 302 when only a path is given' do
|
|
52
|
+
mock_app {
|
|
53
|
+
get '/' do
|
|
54
|
+
redirect '/foo'
|
|
55
|
+
fail 'redirect should halt'
|
|
56
|
+
end
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
get '/'
|
|
60
|
+
status.should.equal 302
|
|
61
|
+
body.should.be.empty
|
|
62
|
+
response['Location'].should.equal '/foo'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it 'uses the code given when specified' do
|
|
66
|
+
mock_app {
|
|
67
|
+
get '/' do
|
|
68
|
+
redirect '/foo', 301
|
|
69
|
+
fail 'redirect should halt'
|
|
70
|
+
end
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get '/'
|
|
74
|
+
status.should.equal 301
|
|
75
|
+
body.should.be.empty
|
|
76
|
+
response['Location'].should.equal '/foo'
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
describe '#error' do
|
|
81
|
+
it 'sets a status code and halts' do
|
|
82
|
+
mock_app {
|
|
83
|
+
get '/' do
|
|
84
|
+
error 501
|
|
85
|
+
fail 'error should halt'
|
|
86
|
+
end
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get '/'
|
|
90
|
+
status.should.equal 501
|
|
91
|
+
body.should.be.empty
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it 'takes an optional body' do
|
|
95
|
+
mock_app {
|
|
96
|
+
get '/' do
|
|
97
|
+
error 501, 'FAIL'
|
|
98
|
+
fail 'error should halt'
|
|
99
|
+
end
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
get '/'
|
|
103
|
+
status.should.equal 501
|
|
104
|
+
body.should.equal 'FAIL'
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'uses a 500 status code when first argument is a body' do
|
|
108
|
+
mock_app {
|
|
109
|
+
get '/' do
|
|
110
|
+
error 'FAIL'
|
|
111
|
+
fail 'error should halt'
|
|
112
|
+
end
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get '/'
|
|
116
|
+
status.should.equal 500
|
|
117
|
+
body.should.equal 'FAIL'
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
describe '#not_found' do
|
|
122
|
+
it 'halts with a 404 status' do
|
|
123
|
+
mock_app {
|
|
124
|
+
get '/' do
|
|
125
|
+
not_found
|
|
126
|
+
fail 'not_found should halt'
|
|
127
|
+
end
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
get '/'
|
|
131
|
+
status.should.equal 404
|
|
132
|
+
body.should.be.empty
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
describe '#session' do
|
|
137
|
+
it 'uses the existing rack.session' do
|
|
138
|
+
mock_app {
|
|
139
|
+
get '/' do
|
|
140
|
+
session[:foo]
|
|
141
|
+
end
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
get '/', :env => { 'rack.session' => { :foo => 'bar' } }
|
|
145
|
+
body.should.equal 'bar'
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it 'creates a new session when none provided' do
|
|
149
|
+
mock_app {
|
|
150
|
+
get '/' do
|
|
151
|
+
session.should.be.empty
|
|
152
|
+
session[:foo] = 'bar'
|
|
153
|
+
'Hi'
|
|
154
|
+
end
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
get '/'
|
|
158
|
+
body.should.equal 'Hi'
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
describe '#media_type' do
|
|
163
|
+
include Sinatra::Helpers
|
|
164
|
+
it "looks up media types in Rack's MIME registry" do
|
|
165
|
+
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
|
166
|
+
media_type('foo').should.equal 'application/foo'
|
|
167
|
+
media_type('.foo').should.equal 'application/foo'
|
|
168
|
+
media_type(:foo).should.equal 'application/foo'
|
|
169
|
+
end
|
|
170
|
+
it 'returns nil when given nil' do
|
|
171
|
+
media_type(nil).should.be.nil
|
|
172
|
+
end
|
|
173
|
+
it 'returns nil when media type not registered' do
|
|
174
|
+
media_type(:bizzle).should.be.nil
|
|
175
|
+
end
|
|
176
|
+
it 'returns the argument when given a media type string' do
|
|
177
|
+
media_type('text/plain').should.equal 'text/plain'
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
describe '#content_type' do
|
|
182
|
+
it 'sets the Content-Type header' do
|
|
183
|
+
mock_app {
|
|
184
|
+
get '/' do
|
|
185
|
+
content_type 'text/plain'
|
|
186
|
+
'Hello World'
|
|
187
|
+
end
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
get '/'
|
|
191
|
+
response['Content-Type'].should.equal 'text/plain'
|
|
192
|
+
body.should.equal 'Hello World'
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
it 'takes media type parameters (like charset=)' do
|
|
196
|
+
mock_app {
|
|
197
|
+
get '/' do
|
|
198
|
+
content_type 'text/html', :charset => 'utf-8'
|
|
199
|
+
"<h1>Hello, World</h1>"
|
|
200
|
+
end
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
get '/'
|
|
204
|
+
should.be.ok
|
|
205
|
+
response['Content-Type'].should.equal 'text/html;charset=utf-8'
|
|
206
|
+
body.should.equal "<h1>Hello, World</h1>"
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
it "looks up symbols in Rack's mime types dictionary" do
|
|
210
|
+
Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
|
|
211
|
+
mock_app {
|
|
212
|
+
get '/foo.xml' do
|
|
213
|
+
content_type :foo
|
|
214
|
+
"I AM FOO"
|
|
215
|
+
end
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get '/foo.xml'
|
|
219
|
+
should.be.ok
|
|
220
|
+
response['Content-Type'].should.equal 'application/foo'
|
|
221
|
+
body.should.equal 'I AM FOO'
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
it 'fails when no mime type is registered for the argument provided' do
|
|
225
|
+
mock_app {
|
|
226
|
+
get '/foo.xml' do
|
|
227
|
+
content_type :bizzle
|
|
228
|
+
"I AM FOO"
|
|
229
|
+
end
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
lambda { get '/foo.xml' }.should.raise RuntimeError
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
describe '#send_file' do
|
|
237
|
+
before {
|
|
238
|
+
@file = File.dirname(__FILE__) + '/file.txt'
|
|
239
|
+
File.open(@file, 'wb') { |io| io.write('Hello World') }
|
|
240
|
+
}
|
|
241
|
+
after {
|
|
242
|
+
File.unlink @file
|
|
243
|
+
@file = nil
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
def send_file_app
|
|
247
|
+
path = @file
|
|
248
|
+
mock_app {
|
|
249
|
+
get '/file.txt' do
|
|
250
|
+
send_file path
|
|
251
|
+
end
|
|
252
|
+
}
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "sends the contents of the file" do
|
|
256
|
+
send_file_app
|
|
257
|
+
get '/file.txt'
|
|
258
|
+
should.be.ok
|
|
259
|
+
body.should.equal 'Hello World'
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
it 'sets the Content-Type response header if a mime-type can be located' do
|
|
263
|
+
send_file_app
|
|
264
|
+
get '/file.txt'
|
|
265
|
+
response['Content-Type'].should.equal 'text/plain'
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
it 'sets the Content-Length response header' do
|
|
269
|
+
send_file_app
|
|
270
|
+
get '/file.txt'
|
|
271
|
+
response['Content-Length'].should.equal 'Hello World'.length.to_s
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
it 'sets the Last-Modified response header' do
|
|
275
|
+
send_file_app
|
|
276
|
+
get '/file.txt'
|
|
277
|
+
response['Last-Modified'].should.equal File.mtime(@file).httpdate
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
it "returns a 404 when not found" do
|
|
281
|
+
mock_app {
|
|
282
|
+
get '/' do
|
|
283
|
+
send_file 'this-file-does-not-exist.txt'
|
|
284
|
+
end
|
|
285
|
+
}
|
|
286
|
+
get '/'
|
|
287
|
+
should.be.not_found
|
|
288
|
+
end
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
describe '#last_modified' do
|
|
292
|
+
before do
|
|
293
|
+
now = Time.now
|
|
294
|
+
mock_app {
|
|
295
|
+
get '/' do
|
|
296
|
+
body { 'Hello World' }
|
|
297
|
+
last_modified now
|
|
298
|
+
'Boo!'
|
|
299
|
+
end
|
|
300
|
+
}
|
|
301
|
+
@now = now
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
it 'sets the Last-Modified header to a valid RFC 2616 date value' do
|
|
305
|
+
get '/'
|
|
306
|
+
response['Last-Modified'].should.equal @now.httpdate
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it 'returns a body when conditional get misses' do
|
|
310
|
+
get '/'
|
|
311
|
+
status.should.be 200
|
|
312
|
+
body.should.equal 'Boo!'
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
it 'halts when a conditional GET matches' do
|
|
316
|
+
get '/', :env => { 'HTTP_IF_MODIFIED_SINCE' => @now.httpdate }
|
|
317
|
+
status.should.be 304
|
|
318
|
+
body.should.be.empty
|
|
319
|
+
end
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
describe '#etag' do
|
|
323
|
+
before do
|
|
324
|
+
mock_app {
|
|
325
|
+
get '/' do
|
|
326
|
+
body { 'Hello World' }
|
|
327
|
+
etag 'FOO'
|
|
328
|
+
'Boo!'
|
|
329
|
+
end
|
|
330
|
+
}
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
it 'sets the ETag header' do
|
|
334
|
+
get '/'
|
|
335
|
+
response['ETag'].should.equal '"FOO"'
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
it 'returns a body when conditional get misses' do
|
|
339
|
+
get '/'
|
|
340
|
+
status.should.be 200
|
|
341
|
+
body.should.equal 'Boo!'
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
it 'halts when a conditional GET matches' do
|
|
345
|
+
get '/', :env => { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
|
|
346
|
+
status.should.be 304
|
|
347
|
+
body.should.be.empty
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
it 'should handle multiple ETag values in If-None-Match header' do
|
|
351
|
+
get '/', :env => { 'HTTP_IF_NONE_MATCH' => '"BAR", *' }
|
|
352
|
+
status.should.be 304
|
|
353
|
+
body.should.be.empty
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
it 'uses a weak etag with the :weak option' do
|
|
357
|
+
mock_app {
|
|
358
|
+
get '/' do
|
|
359
|
+
etag 'FOO', :weak
|
|
360
|
+
"that's weak, dude."
|
|
361
|
+
end
|
|
362
|
+
}
|
|
363
|
+
get '/'
|
|
364
|
+
response['ETag'].should.equal 'W/"FOO"'
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
end
|
|
368
|
+
end
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
require 'test/spec'
|
|
2
|
+
require 'sinatra/base'
|
|
3
|
+
require 'sinatra/test'
|
|
4
|
+
|
|
5
|
+
describe 'Exception Mappings' do
|
|
6
|
+
include Sinatra::Test
|
|
7
|
+
|
|
8
|
+
class FooError < RuntimeError
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
it 'invokes handlers registered with ::error when raised' do
|
|
12
|
+
mock_app {
|
|
13
|
+
set :raise_errors, false
|
|
14
|
+
error(FooError) { 'Foo!' }
|
|
15
|
+
get '/' do
|
|
16
|
+
raise FooError
|
|
17
|
+
end
|
|
18
|
+
}
|
|
19
|
+
get '/'
|
|
20
|
+
status.should.equal 500
|
|
21
|
+
body.should.equal 'Foo!'
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it 'uses the Exception handler if no matching handler found' do
|
|
25
|
+
mock_app {
|
|
26
|
+
set :raise_errors, false
|
|
27
|
+
error(Exception) { 'Exception!' }
|
|
28
|
+
get '/' do
|
|
29
|
+
raise FooError
|
|
30
|
+
end
|
|
31
|
+
}
|
|
32
|
+
get '/'
|
|
33
|
+
status.should.equal 500
|
|
34
|
+
body.should.equal 'Exception!'
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
it "sets env['sinatra.error'] to the rescued exception" do
|
|
38
|
+
mock_app {
|
|
39
|
+
set :raise_errors, false
|
|
40
|
+
error(FooError) {
|
|
41
|
+
env.should.include 'sinatra.error'
|
|
42
|
+
env['sinatra.error'].should.be.kind_of FooError
|
|
43
|
+
'looks good'
|
|
44
|
+
}
|
|
45
|
+
get '/' do
|
|
46
|
+
raise FooError
|
|
47
|
+
end
|
|
48
|
+
}
|
|
49
|
+
get '/'
|
|
50
|
+
body.should.equal 'looks good'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
it 'dumps errors to rack.errors when dump_errors is enabled' do
|
|
54
|
+
mock_app {
|
|
55
|
+
set :raise_errors, false
|
|
56
|
+
set :dump_errors, true
|
|
57
|
+
get('/') { raise FooError, 'BOOM!' }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get '/'
|
|
61
|
+
status.should.equal 500
|
|
62
|
+
@response.errors.should.match(/FooError - BOOM!:/)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
it "raises without calling the handler when the raise_errors options is set" do
|
|
66
|
+
mock_app {
|
|
67
|
+
set :raise_errors, true
|
|
68
|
+
error(FooError) { "she's not there." }
|
|
69
|
+
get '/' do
|
|
70
|
+
raise FooError
|
|
71
|
+
end
|
|
72
|
+
}
|
|
73
|
+
lambda { get '/' }.should.raise FooError
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
it "never raises Sinatra::NotFound beyond the application" do
|
|
77
|
+
mock_app {
|
|
78
|
+
set :raise_errors, true
|
|
79
|
+
get '/' do
|
|
80
|
+
raise Sinatra::NotFound
|
|
81
|
+
end
|
|
82
|
+
}
|
|
83
|
+
lambda { get '/' }.should.not.raise Sinatra::NotFound
|
|
84
|
+
status.should.equal 404
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
class FooNotFound < Sinatra::NotFound
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
it "cascades for subclasses of Sinatra::NotFound" do
|
|
91
|
+
mock_app {
|
|
92
|
+
set :raise_errors, true
|
|
93
|
+
error(FooNotFound) { "foo! not found." }
|
|
94
|
+
get '/' do
|
|
95
|
+
raise FooNotFound
|
|
96
|
+
end
|
|
97
|
+
}
|
|
98
|
+
lambda { get '/' }.should.not.raise FooNotFound
|
|
99
|
+
status.should.equal 404
|
|
100
|
+
body.should.equal 'foo! not found.'
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it 'has a not_found method for backwards compatibility' do
|
|
104
|
+
mock_app {
|
|
105
|
+
not_found do
|
|
106
|
+
"Lost, are we?"
|
|
107
|
+
end
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
get '/test'
|
|
111
|
+
status.should.equal 404
|
|
112
|
+
body.should.equal "Lost, are we?"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
describe 'Custom Error Pages' do
|
|
117
|
+
it 'allows numeric status code mappings to be registered with ::error' do
|
|
118
|
+
mock_app {
|
|
119
|
+
set :raise_errors, false
|
|
120
|
+
error(500) { 'Foo!' }
|
|
121
|
+
get '/' do
|
|
122
|
+
[500, {}, 'Internal Foo Error']
|
|
123
|
+
end
|
|
124
|
+
}
|
|
125
|
+
get '/'
|
|
126
|
+
status.should.equal 500
|
|
127
|
+
body.should.equal 'Foo!'
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it 'allows ranges of status code mappings to be registered with :error' do
|
|
131
|
+
mock_app {
|
|
132
|
+
set :raise_errors, false
|
|
133
|
+
error(500..550) { "Error: #{response.status}" }
|
|
134
|
+
get '/' do
|
|
135
|
+
[507, {}, 'A very special error']
|
|
136
|
+
end
|
|
137
|
+
}
|
|
138
|
+
get '/'
|
|
139
|
+
status.should.equal 507
|
|
140
|
+
body.should.equal 'Error: 507'
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
class FooError < RuntimeError
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
it 'runs after exception mappings and overwrites body' do
|
|
147
|
+
mock_app {
|
|
148
|
+
set :raise_errors, false
|
|
149
|
+
error FooError do
|
|
150
|
+
response.status = 502
|
|
151
|
+
'from exception mapping'
|
|
152
|
+
end
|
|
153
|
+
error(500) { 'from 500 handler' }
|
|
154
|
+
error(502) { 'from custom error page' }
|
|
155
|
+
|
|
156
|
+
get '/' do
|
|
157
|
+
raise FooError
|
|
158
|
+
end
|
|
159
|
+
}
|
|
160
|
+
get '/'
|
|
161
|
+
status.should.equal 502
|
|
162
|
+
body.should.equal 'from custom error page'
|
|
163
|
+
end
|
|
164
|
+
end
|