sinatra-base 1.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 (58) hide show
  1. data/AUTHORS +43 -0
  2. data/CHANGES +511 -0
  3. data/LICENSE +22 -0
  4. data/README.jp.rdoc +552 -0
  5. data/README.rdoc +636 -0
  6. data/Rakefile +116 -0
  7. data/lib/sinatra.rb +7 -0
  8. data/lib/sinatra/base.rb +1167 -0
  9. data/lib/sinatra/images/404.png +0 -0
  10. data/lib/sinatra/images/500.png +0 -0
  11. data/lib/sinatra/main.rb +28 -0
  12. data/lib/sinatra/showexceptions.rb +307 -0
  13. data/lib/sinatra/tilt.rb +746 -0
  14. data/sinatra-base.gemspec +94 -0
  15. data/test/base_test.rb +160 -0
  16. data/test/builder_test.rb +65 -0
  17. data/test/contest.rb +64 -0
  18. data/test/erb_test.rb +81 -0
  19. data/test/erubis_test.rb +82 -0
  20. data/test/extensions_test.rb +100 -0
  21. data/test/filter_test.rb +221 -0
  22. data/test/haml_test.rb +95 -0
  23. data/test/helper.rb +76 -0
  24. data/test/helpers_test.rb +582 -0
  25. data/test/less_test.rb +37 -0
  26. data/test/mapped_error_test.rb +197 -0
  27. data/test/middleware_test.rb +68 -0
  28. data/test/public/favicon.ico +0 -0
  29. data/test/request_test.rb +33 -0
  30. data/test/response_test.rb +42 -0
  31. data/test/result_test.rb +98 -0
  32. data/test/route_added_hook_test.rb +59 -0
  33. data/test/routing_test.rb +860 -0
  34. data/test/sass_test.rb +85 -0
  35. data/test/server_test.rb +47 -0
  36. data/test/settings_test.rb +368 -0
  37. data/test/sinatra_test.rb +13 -0
  38. data/test/static_test.rb +93 -0
  39. data/test/templates_test.rb +159 -0
  40. data/test/views/error.builder +3 -0
  41. data/test/views/error.erb +3 -0
  42. data/test/views/error.erubis +3 -0
  43. data/test/views/error.haml +3 -0
  44. data/test/views/error.sass +2 -0
  45. data/test/views/foo/hello.test +1 -0
  46. data/test/views/hello.builder +1 -0
  47. data/test/views/hello.erb +1 -0
  48. data/test/views/hello.erubis +1 -0
  49. data/test/views/hello.haml +1 -0
  50. data/test/views/hello.less +5 -0
  51. data/test/views/hello.sass +2 -0
  52. data/test/views/hello.test +1 -0
  53. data/test/views/layout2.builder +3 -0
  54. data/test/views/layout2.erb +2 -0
  55. data/test/views/layout2.erubis +2 -0
  56. data/test/views/layout2.haml +2 -0
  57. data/test/views/layout2.test +1 -0
  58. metadata +257 -0
@@ -0,0 +1,76 @@
1
+ ENV['RACK_ENV'] = 'test'
2
+
3
+ begin
4
+ require 'rack'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ require 'rack'
8
+ end
9
+
10
+ testdir = File.dirname(__FILE__)
11
+ $LOAD_PATH.unshift testdir unless $LOAD_PATH.include?(testdir)
12
+
13
+ libdir = File.dirname(File.dirname(__FILE__)) + '/lib'
14
+ $LOAD_PATH.unshift libdir unless $LOAD_PATH.include?(libdir)
15
+
16
+ require 'contest'
17
+ require 'rack/test'
18
+ require 'sinatra/base'
19
+
20
+ class Sinatra::Base
21
+ # Allow assertions in request context
22
+ include Test::Unit::Assertions
23
+ end
24
+
25
+ Sinatra::Base.set :environment, :test
26
+
27
+ class Test::Unit::TestCase
28
+ include Rack::Test::Methods
29
+
30
+ class << self
31
+ alias_method :it, :test
32
+ end
33
+
34
+ alias_method :response, :last_response
35
+
36
+ setup do
37
+ Sinatra::Base.set :environment, :test
38
+ end
39
+
40
+ # Sets up a Sinatra::Base subclass defined with the block
41
+ # given. Used in setup or individual spec methods to establish
42
+ # the application.
43
+ def mock_app(base=Sinatra::Base, &block)
44
+ @app = Sinatra.new(base, &block)
45
+ end
46
+
47
+ def app
48
+ Rack::Lint.new(@app)
49
+ end
50
+
51
+ def body
52
+ response.body.to_s
53
+ end
54
+
55
+ # Delegate other missing methods to response.
56
+ def method_missing(name, *args, &block)
57
+ if response && response.respond_to?(name)
58
+ response.send(name, *args, &block)
59
+ else
60
+ super
61
+ end
62
+ end
63
+
64
+ # Also check response since we delegate there.
65
+ def respond_to?(symbol, include_private=false)
66
+ super || (response && response.respond_to?(symbol, include_private))
67
+ end
68
+
69
+ # Do not output warnings for the duration of the block.
70
+ def silence_warnings
71
+ $VERBOSE, v = nil, $VERBOSE
72
+ yield
73
+ ensure
74
+ $VERBOSE = v
75
+ end
76
+ end
@@ -0,0 +1,582 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
3
+ class HelpersTest < Test::Unit::TestCase
4
+ def test_default
5
+ assert true
6
+ end
7
+
8
+ describe 'status' do
9
+ setup do
10
+ mock_app {
11
+ get '/' do
12
+ status 207
13
+ nil
14
+ end
15
+ }
16
+ end
17
+
18
+ it 'sets the response status code' do
19
+ get '/'
20
+ assert_equal 207, response.status
21
+ end
22
+ end
23
+
24
+ describe 'body' do
25
+ it 'takes a block for defered body generation' do
26
+ mock_app {
27
+ get '/' do
28
+ body { 'Hello World' }
29
+ end
30
+ }
31
+
32
+ get '/'
33
+ assert_equal 'Hello World', body
34
+ end
35
+
36
+ it 'takes a String, Array, or other object responding to #each' do
37
+ mock_app {
38
+ get '/' do
39
+ body 'Hello World'
40
+ end
41
+ }
42
+
43
+ get '/'
44
+ assert_equal 'Hello World', body
45
+ end
46
+ end
47
+
48
+ describe 'redirect' do
49
+ it 'uses a 302 when only a path is given' do
50
+ mock_app {
51
+ get '/' do
52
+ redirect '/foo'
53
+ fail 'redirect should halt'
54
+ end
55
+ }
56
+
57
+ get '/'
58
+ assert_equal 302, status
59
+ assert_equal '', body
60
+ assert_equal '/foo', response['Location']
61
+ end
62
+
63
+ it 'uses the code given when specified' do
64
+ mock_app {
65
+ get '/' do
66
+ redirect '/foo', 301
67
+ fail 'redirect should halt'
68
+ end
69
+ }
70
+
71
+ get '/'
72
+ assert_equal 301, status
73
+ assert_equal '', body
74
+ assert_equal '/foo', response['Location']
75
+ end
76
+
77
+ it 'redirects back to request.referer when passed back' do
78
+ mock_app {
79
+ get '/try_redirect' do
80
+ redirect back
81
+ end
82
+ }
83
+
84
+ request = Rack::MockRequest.new(@app)
85
+ response = request.get('/try_redirect', 'HTTP_REFERER' => '/foo')
86
+ assert_equal 302, response.status
87
+ assert_equal '/foo', response['Location']
88
+ end
89
+ end
90
+
91
+ describe 'error' do
92
+ it 'sets a status code and halts' do
93
+ mock_app {
94
+ get '/' do
95
+ error 501
96
+ fail 'error should halt'
97
+ end
98
+ }
99
+
100
+ get '/'
101
+ assert_equal 501, status
102
+ assert_equal '', body
103
+ end
104
+
105
+ it 'takes an optional body' do
106
+ mock_app {
107
+ get '/' do
108
+ error 501, 'FAIL'
109
+ fail 'error should halt'
110
+ end
111
+ }
112
+
113
+ get '/'
114
+ assert_equal 501, status
115
+ assert_equal 'FAIL', body
116
+ end
117
+
118
+ it 'uses a 500 status code when first argument is a body' do
119
+ mock_app {
120
+ get '/' do
121
+ error 'FAIL'
122
+ fail 'error should halt'
123
+ end
124
+ }
125
+
126
+ get '/'
127
+ assert_equal 500, status
128
+ assert_equal 'FAIL', body
129
+ end
130
+ end
131
+
132
+ describe 'not_found' do
133
+ it 'halts with a 404 status' do
134
+ mock_app {
135
+ get '/' do
136
+ not_found
137
+ fail 'not_found should halt'
138
+ end
139
+ }
140
+
141
+ get '/'
142
+ assert_equal 404, status
143
+ assert_equal '', body
144
+ end
145
+
146
+ it 'does not set a X-Cascade header' do
147
+ mock_app {
148
+ get '/' do
149
+ not_found
150
+ fail 'not_found should halt'
151
+ end
152
+ }
153
+
154
+ get '/'
155
+ assert_equal 404, status
156
+ assert_equal nil, response.headers['X-Cascade']
157
+ end
158
+ end
159
+
160
+ describe 'headers' do
161
+ it 'sets headers on the response object when given a Hash' do
162
+ mock_app {
163
+ get '/' do
164
+ headers 'X-Foo' => 'bar', 'X-Baz' => 'bling'
165
+ 'kthx'
166
+ end
167
+ }
168
+
169
+ get '/'
170
+ assert ok?
171
+ assert_equal 'bar', response['X-Foo']
172
+ assert_equal 'bling', response['X-Baz']
173
+ assert_equal 'kthx', body
174
+ end
175
+
176
+ it 'returns the response headers hash when no hash provided' do
177
+ mock_app {
178
+ get '/' do
179
+ headers['X-Foo'] = 'bar'
180
+ 'kthx'
181
+ end
182
+ }
183
+
184
+ get '/'
185
+ assert ok?
186
+ assert_equal 'bar', response['X-Foo']
187
+ end
188
+ end
189
+
190
+ describe 'session' do
191
+ it 'uses the existing rack.session' do
192
+ mock_app {
193
+ get '/' do
194
+ session[:foo]
195
+ end
196
+ }
197
+
198
+ get '/', {}, { 'rack.session' => { :foo => 'bar' } }
199
+ assert_equal 'bar', body
200
+ end
201
+
202
+ it 'creates a new session when none provided' do
203
+ mock_app {
204
+ enable :sessions
205
+
206
+ get '/' do
207
+ assert session.empty?
208
+ session[:foo] = 'bar'
209
+ redirect '/hi'
210
+ end
211
+
212
+ get '/hi' do
213
+ "hi #{session[:foo]}"
214
+ end
215
+ }
216
+
217
+ get '/'
218
+ follow_redirect!
219
+ assert_equal 'hi bar', body
220
+ end
221
+ end
222
+
223
+ describe 'mime_type' do
224
+ include Sinatra::Helpers
225
+
226
+ it "looks up mime types in Rack's MIME registry" do
227
+ Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
228
+ assert_equal 'application/foo', mime_type('foo')
229
+ assert_equal 'application/foo', mime_type('.foo')
230
+ assert_equal 'application/foo', mime_type(:foo)
231
+ end
232
+
233
+ it 'returns nil when given nil' do
234
+ assert mime_type(nil).nil?
235
+ end
236
+
237
+ it 'returns nil when media type not registered' do
238
+ assert mime_type(:bizzle).nil?
239
+ end
240
+
241
+ it 'returns the argument when given a media type string' do
242
+ assert_equal 'text/plain', mime_type('text/plain')
243
+ end
244
+ end
245
+
246
+ test 'Base.mime_type registers mime type' do
247
+ mock_app {
248
+ mime_type :foo, 'application/foo'
249
+
250
+ get '/' do
251
+ "foo is #{mime_type(:foo)}"
252
+ end
253
+ }
254
+
255
+ get '/'
256
+ assert_equal 'foo is application/foo', body
257
+ end
258
+
259
+ describe 'content_type' do
260
+ it 'sets the Content-Type header' do
261
+ mock_app {
262
+ get '/' do
263
+ content_type 'text/plain'
264
+ 'Hello World'
265
+ end
266
+ }
267
+
268
+ get '/'
269
+ assert_equal 'text/plain', response['Content-Type']
270
+ assert_equal 'Hello World', body
271
+ end
272
+
273
+ it 'takes media type parameters (like charset=)' do
274
+ mock_app {
275
+ get '/' do
276
+ content_type 'text/html', :charset => 'utf-8'
277
+ "<h1>Hello, World</h1>"
278
+ end
279
+ }
280
+
281
+ get '/'
282
+ assert ok?
283
+ assert_equal 'text/html;charset=utf-8', response['Content-Type']
284
+ assert_equal "<h1>Hello, World</h1>", body
285
+ end
286
+
287
+ it "looks up symbols in Rack's mime types dictionary" do
288
+ Rack::Mime::MIME_TYPES['.foo'] = 'application/foo'
289
+ mock_app {
290
+ get '/foo.xml' do
291
+ content_type :foo
292
+ "I AM FOO"
293
+ end
294
+ }
295
+
296
+ get '/foo.xml'
297
+ assert ok?
298
+ assert_equal 'application/foo', response['Content-Type']
299
+ assert_equal 'I AM FOO', body
300
+ end
301
+
302
+ it 'fails when no mime type is registered for the argument provided' do
303
+ mock_app {
304
+ get '/foo.xml' do
305
+ content_type :bizzle
306
+ "I AM FOO"
307
+ end
308
+ }
309
+
310
+ assert_raise(RuntimeError) { get '/foo.xml' }
311
+ end
312
+ end
313
+
314
+ describe 'send_file' do
315
+ setup do
316
+ @file = File.dirname(__FILE__) + '/file.txt'
317
+ File.open(@file, 'wb') { |io| io.write('Hello World') }
318
+ end
319
+
320
+ def teardown
321
+ File.unlink @file
322
+ @file = nil
323
+ end
324
+
325
+ def send_file_app(opts={})
326
+ path = @file
327
+ mock_app {
328
+ get '/file.txt' do
329
+ send_file path, opts
330
+ end
331
+ }
332
+ end
333
+
334
+ it "sends the contents of the file" do
335
+ send_file_app
336
+ get '/file.txt'
337
+ assert ok?
338
+ assert_equal 'Hello World', body
339
+ end
340
+
341
+ it 'sets the Content-Type response header if a mime-type can be located' do
342
+ send_file_app
343
+ get '/file.txt'
344
+ assert_equal 'text/plain', response['Content-Type']
345
+ end
346
+
347
+ it 'sets the Content-Length response header' do
348
+ send_file_app
349
+ get '/file.txt'
350
+ assert_equal 'Hello World'.length.to_s, response['Content-Length']
351
+ end
352
+
353
+ it 'sets the Last-Modified response header' do
354
+ send_file_app
355
+ get '/file.txt'
356
+ assert_equal File.mtime(@file).httpdate, response['Last-Modified']
357
+ end
358
+
359
+ it "returns a 404 when not found" do
360
+ mock_app {
361
+ get '/' do
362
+ send_file 'this-file-does-not-exist.txt'
363
+ end
364
+ }
365
+ get '/'
366
+ assert not_found?
367
+ end
368
+
369
+ it "does not set the Content-Disposition header by default" do
370
+ send_file_app
371
+ get '/file.txt'
372
+ assert_nil response['Content-Disposition']
373
+ end
374
+
375
+ it "sets the Content-Disposition header when :disposition set to 'attachment'" do
376
+ send_file_app :disposition => 'attachment'
377
+ get '/file.txt'
378
+ assert_equal 'attachment; filename="file.txt"', response['Content-Disposition']
379
+ end
380
+
381
+ it "sets the Content-Disposition header when :filename provided" do
382
+ send_file_app :filename => 'foo.txt'
383
+ get '/file.txt'
384
+ assert_equal 'attachment; filename="foo.txt"', response['Content-Disposition']
385
+ end
386
+ end
387
+
388
+ describe 'cache_control' do
389
+ setup do
390
+ mock_app {
391
+ get '/' do
392
+ cache_control :public, :no_cache, :max_age => 60
393
+ 'Hello World'
394
+ end
395
+ }
396
+ end
397
+
398
+ it 'sets the Cache-Control header' do
399
+ get '/'
400
+ assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
401
+ end
402
+ end
403
+
404
+ describe 'expires' do
405
+ setup do
406
+ mock_app {
407
+ get '/' do
408
+ expires 60, :public, :no_cache
409
+ 'Hello World'
410
+ end
411
+ }
412
+ end
413
+
414
+ it 'sets the Cache-Control header' do
415
+ get '/'
416
+ assert_equal ['public', 'no-cache', 'max-age=60'], response['Cache-Control'].split(', ')
417
+ end
418
+
419
+ it 'sets the Expires header' do
420
+ get '/'
421
+ assert_not_nil response['Expires']
422
+ end
423
+ end
424
+
425
+ describe 'last_modified' do
426
+ setup do
427
+ now = Time.now
428
+ mock_app {
429
+ get '/' do
430
+ body { 'Hello World' }
431
+ last_modified now
432
+ 'Boo!'
433
+ end
434
+ }
435
+ @now = now
436
+ end
437
+
438
+ it 'sets the Last-Modified header to a valid RFC 2616 date value' do
439
+ get '/'
440
+ assert_equal @now.httpdate, response['Last-Modified']
441
+ end
442
+
443
+ it 'returns a body when conditional get misses' do
444
+ get '/'
445
+ assert_equal 200, status
446
+ assert_equal 'Boo!', body
447
+ end
448
+
449
+ it 'halts when a conditional GET matches' do
450
+ get '/', {}, { 'HTTP_IF_MODIFIED_SINCE' => @now.httpdate }
451
+ assert_equal 304, status
452
+ assert_equal '', body
453
+ end
454
+
455
+ it 'ignores nil' do
456
+ mock_app {
457
+ get '/' do last_modified nil; 200; end
458
+ }
459
+
460
+ get '/'
461
+ assert ! response['Last-Modified']
462
+ end
463
+ end
464
+
465
+ describe 'etag' do
466
+ setup do
467
+ mock_app {
468
+ get '/' do
469
+ body { 'Hello World' }
470
+ etag 'FOO'
471
+ 'Boo!'
472
+ end
473
+ }
474
+ end
475
+
476
+ it 'sets the ETag header' do
477
+ get '/'
478
+ assert_equal '"FOO"', response['ETag']
479
+ end
480
+
481
+ it 'returns a body when conditional get misses' do
482
+ get '/'
483
+ assert_equal 200, status
484
+ assert_equal 'Boo!', body
485
+ end
486
+
487
+ it 'halts when a conditional GET matches' do
488
+ get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"FOO"' }
489
+ assert_equal 304, status
490
+ assert_equal '', body
491
+ end
492
+
493
+ it 'should handle multiple ETag values in If-None-Match header' do
494
+ get '/', {}, { 'HTTP_IF_NONE_MATCH' => '"BAR", *' }
495
+ assert_equal 304, status
496
+ assert_equal '', body
497
+ end
498
+
499
+ it 'uses a weak etag with the :weak option' do
500
+ mock_app {
501
+ get '/' do
502
+ etag 'FOO', :weak
503
+ "that's weak, dude."
504
+ end
505
+ }
506
+ get '/'
507
+ assert_equal 'W/"FOO"', response['ETag']
508
+ end
509
+ end
510
+
511
+ describe 'back' do
512
+ it "makes redirecting back pretty" do
513
+ mock_app {
514
+ get '/foo' do
515
+ redirect back
516
+ end
517
+ }
518
+
519
+ get '/foo', {}, 'HTTP_REFERER' => 'http://github.com'
520
+ assert redirect?
521
+ assert_equal "http://github.com", response.location
522
+ end
523
+ end
524
+
525
+ module ::HelperOne; def one; '1'; end; end
526
+ module ::HelperTwo; def two; '2'; end; end
527
+
528
+ describe 'Adding new helpers' do
529
+ it 'takes a list of modules to mix into the app' do
530
+ mock_app {
531
+ helpers ::HelperOne, ::HelperTwo
532
+
533
+ get '/one' do
534
+ one
535
+ end
536
+
537
+ get '/two' do
538
+ two
539
+ end
540
+ }
541
+
542
+ get '/one'
543
+ assert_equal '1', body
544
+
545
+ get '/two'
546
+ assert_equal '2', body
547
+ end
548
+
549
+ it 'takes a block to mix into the app' do
550
+ mock_app {
551
+ helpers do
552
+ def foo
553
+ 'foo'
554
+ end
555
+ end
556
+
557
+ get '/' do
558
+ foo
559
+ end
560
+ }
561
+
562
+ get '/'
563
+ assert_equal 'foo', body
564
+ end
565
+
566
+ it 'evaluates the block in class context so that methods can be aliased' do
567
+ mock_app {
568
+ helpers do
569
+ alias_method :h, :escape_html
570
+ end
571
+
572
+ get '/' do
573
+ h('42 < 43')
574
+ end
575
+ }
576
+
577
+ get '/'
578
+ assert ok?
579
+ assert_equal '42 &lt; 43', body
580
+ end
581
+ end
582
+ end