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.
Files changed (78) hide show
  1. data/AUTHORS +40 -0
  2. data/CHANGES +167 -0
  3. data/LICENSE +22 -0
  4. data/README.rdoc +529 -0
  5. data/Rakefile +180 -0
  6. data/compat/app_test.rb +300 -0
  7. data/compat/application_test.rb +334 -0
  8. data/compat/builder_test.rb +101 -0
  9. data/compat/custom_error_test.rb +62 -0
  10. data/compat/erb_test.rb +136 -0
  11. data/compat/events_test.rb +75 -0
  12. data/compat/filter_test.rb +30 -0
  13. data/compat/haml_test.rb +233 -0
  14. data/compat/helper.rb +21 -0
  15. data/compat/mapped_error_test.rb +72 -0
  16. data/compat/pipeline_test.rb +71 -0
  17. data/compat/public/foo.xml +1 -0
  18. data/compat/sass_test.rb +57 -0
  19. data/compat/sessions_test.rb +39 -0
  20. data/compat/streaming_test.rb +121 -0
  21. data/compat/sym_params_test.rb +19 -0
  22. data/compat/template_test.rb +30 -0
  23. data/compat/use_in_file_templates_test.rb +47 -0
  24. data/compat/views/foo.builder +1 -0
  25. data/compat/views/foo.erb +1 -0
  26. data/compat/views/foo.haml +1 -0
  27. data/compat/views/foo.sass +2 -0
  28. data/compat/views/foo_layout.erb +2 -0
  29. data/compat/views/foo_layout.haml +2 -0
  30. data/compat/views/layout_test/foo.builder +1 -0
  31. data/compat/views/layout_test/foo.erb +1 -0
  32. data/compat/views/layout_test/foo.haml +1 -0
  33. data/compat/views/layout_test/foo.sass +2 -0
  34. data/compat/views/layout_test/layout.builder +3 -0
  35. data/compat/views/layout_test/layout.erb +1 -0
  36. data/compat/views/layout_test/layout.haml +1 -0
  37. data/compat/views/layout_test/layout.sass +2 -0
  38. data/compat/views/no_layout/no_layout.builder +1 -0
  39. data/compat/views/no_layout/no_layout.haml +1 -0
  40. data/lib/sinatra/base.rb +818 -0
  41. data/lib/sinatra/compat.rb +239 -0
  42. data/lib/sinatra/images/404.png +0 -0
  43. data/lib/sinatra/images/500.png +0 -0
  44. data/lib/sinatra/main.rb +48 -0
  45. data/lib/sinatra/test/rspec.rb +2 -0
  46. data/lib/sinatra/test/spec.rb +2 -0
  47. data/lib/sinatra/test/unit.rb +11 -0
  48. data/lib/sinatra/test.rb +112 -0
  49. data/lib/sinatra.rb +3 -0
  50. data/sinatra.gemspec +107 -0
  51. data/test/base_test.rb +72 -0
  52. data/test/builder_test.rb +68 -0
  53. data/test/data/reload_app_file.rb +3 -0
  54. data/test/erb_test.rb +55 -0
  55. data/test/filter_test.rb +39 -0
  56. data/test/haml_test.rb +72 -0
  57. data/test/helpers_test.rb +368 -0
  58. data/test/mapped_error_test.rb +164 -0
  59. data/test/middleware_test.rb +63 -0
  60. data/test/options_test.rb +103 -0
  61. data/test/reload_test.rb +65 -0
  62. data/test/request_test.rb +11 -0
  63. data/test/result_test.rb +92 -0
  64. data/test/routing_test.rb +338 -0
  65. data/test/sass_test.rb +40 -0
  66. data/test/sinatra_test.rb +15 -0
  67. data/test/static_test.rb +60 -0
  68. data/test/templates_test.rb +92 -0
  69. data/test/views/hello.builder +1 -0
  70. data/test/views/hello.erb +1 -0
  71. data/test/views/hello.haml +1 -0
  72. data/test/views/hello.sass +2 -0
  73. data/test/views/hello.test +1 -0
  74. data/test/views/layout2.builder +3 -0
  75. data/test/views/layout2.erb +2 -0
  76. data/test/views/layout2.haml +2 -0
  77. data/test/views/layout2.test +1 -0
  78. 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
@@ -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