sinatra-hashfix 0.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.
@@ -0,0 +1,11 @@
1
+
2
+ == 0.1.1 (January 14, 2010)
3
+
4
+ * Fixed nested symbol routes - eddanger (Michael Wood)
5
+
6
+ * Renamed tests and updated README.txt for clarify
7
+
8
+ == 0.1.0 (January 4, 2010)
9
+
10
+ * Initial release
11
+
@@ -0,0 +1,49 @@
1
+ = Sinatra::Hashfix - Use HashWithIndifferentAccess for Sinatra params
2
+
3
+ Get your hash fix! Many people have been confused by Sinatra's partial implementation
4
+ of a Rails style params[] hash:
5
+
6
+ http://groups.google.com/group/sinatrarb/browse_thread/thread/af4b40e610d4daf/bc953ca6d118a882
7
+
8
+ This gem replaces the default Sinatra params hash with HashWithIndifferentAccess (from ActiveSupport).
9
+
10
+ == Installation
11
+
12
+ You know this tune:
13
+
14
+ gem install sinatra-hashfix
15
+
16
+ If you are using a classic (one-file) Sinatra app, just add:
17
+
18
+ require 'sinatra/hashfix'
19
+
20
+ If you are using a modular Sinatra::Base app, you must also add:
21
+
22
+ register Sinatra::Hashfix
23
+
24
+ To the top of your application class.
25
+
26
+ == Example
27
+
28
+ Request:
29
+
30
+ /my/route?foo=1
31
+
32
+ Without:
33
+
34
+ params[:foo] # 1
35
+ params.has_key?(:foo) # false
36
+ params.has_key?('foo') # true
37
+
38
+ With:
39
+
40
+ params[:foo] # 1
41
+ params.has_key?(:foo) # true
42
+ params.has_key?('foo') # true
43
+
44
+ It's the little things in life that make me happy.
45
+
46
+ == Author
47
+
48
+ Copyright (c) 2010 {Nate Wiger}[http://nate.wiger.org]. All Rights Reserved.
49
+ Released under the {Artistic License}[http://www.opensource.org/licenses/artistic-license-2.0.php].
@@ -0,0 +1,16 @@
1
+ require 'active_support/core_ext/hash'
2
+ module Sinatra
3
+ module Hashfix
4
+ def self.registered(app)
5
+ app.send :include, InstanceMethods
6
+ end
7
+
8
+ module InstanceMethods
9
+ def indifferent_params(params)
10
+ params = HashWithIndifferentAccess.new(params)
11
+ end
12
+ end
13
+ end
14
+
15
+ register Hashfix
16
+ end
@@ -0,0 +1,64 @@
1
+ require "test/unit"
2
+
3
+ # Test::Unit loads a default test if the suite is empty, and the only
4
+ # purpose of that test is to fail. As having empty contexts is a common
5
+ # practice, we decided to overwrite TestSuite#empty? in order to
6
+ # allow them. Having a failure when no tests have been defined seems
7
+ # counter-intuitive.
8
+ class Test::Unit::TestSuite
9
+ unless method_defined?(:empty?)
10
+ def empty?
11
+ false
12
+ end
13
+ end
14
+ end
15
+
16
+ # We added setup, test and context as class methods, and the instance
17
+ # method setup now iterates on the setup blocks. Note that all setup
18
+ # blocks must be defined with the block syntax. Adding a setup instance
19
+ # method defeats the purpose of this library.
20
+ class Test::Unit::TestCase
21
+ def self.setup(&block)
22
+ setup_blocks << block
23
+ end
24
+
25
+ def setup
26
+ self.class.setup_blocks.each do |block|
27
+ instance_eval(&block)
28
+ end
29
+ end
30
+
31
+ def self.context(name, &block)
32
+ subclass = Class.new(self.superclass)
33
+ subclass.setup_blocks.unshift(*setup_blocks)
34
+ subclass.class_eval(&block)
35
+ const_set(context_name(name), subclass)
36
+ end
37
+
38
+ def self.test(name, &block)
39
+ define_method(test_name(name), &block)
40
+ end
41
+
42
+ class << self
43
+ alias_method :should, :test
44
+ alias_method :describe, :context
45
+ end
46
+
47
+ private
48
+
49
+ def self.setup_blocks
50
+ @setup_blocks ||= []
51
+ end
52
+
53
+ def self.context_name(name)
54
+ "Test#{sanitize_name(name).gsub(/(^| )(\w)/) { $2.upcase }}".to_sym
55
+ end
56
+
57
+ def self.test_name(name)
58
+ "test_#{sanitize_name(name).gsub(/\s+/,'_')}".to_sym
59
+ end
60
+
61
+ def self.sanitize_name(name)
62
+ name.gsub(/\W+/, ' ').strip
63
+ end
64
+ end
@@ -0,0 +1,82 @@
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
+ require 'sinatra/hashfix'
20
+
21
+ class Sinatra::Base
22
+ # Allow assertions in request context
23
+ include Test::Unit::Assertions
24
+ end
25
+
26
+ # App that includes resource/member helpers
27
+ class ResourceApp < Sinatra::Base
28
+ register Sinatra::Hashfix
29
+ end
30
+
31
+ Sinatra::Base.set :environment, :test
32
+
33
+ class Test::Unit::TestCase
34
+ include Rack::Test::Methods
35
+
36
+ class << self
37
+ alias_method :it, :test
38
+ end
39
+
40
+ alias_method :response, :last_response
41
+
42
+ setup do
43
+ Sinatra::Base.set :environment, :test
44
+ end
45
+
46
+ # Sets up a Sinatra::Base subclass defined with the block
47
+ # given. Used in setup or individual spec methods to establish
48
+ # the application.
49
+ def mock_app(base=ResourceApp, &block)
50
+ @app = Sinatra.new(base, &block)
51
+ end
52
+
53
+ def app
54
+ Rack::Lint.new(@app)
55
+ end
56
+
57
+ def body
58
+ response.body.to_s
59
+ end
60
+
61
+ # Delegate other missing methods to response.
62
+ def method_missing(name, *args, &block)
63
+ if response && response.respond_to?(name)
64
+ response.send(name, *args, &block)
65
+ else
66
+ super
67
+ end
68
+ end
69
+
70
+ # Also check response since we delegate there.
71
+ def respond_to?(symbol, include_private=false)
72
+ super || (response && response.respond_to?(symbol, include_private))
73
+ end
74
+
75
+ # Do not output warnings for the duration of the block.
76
+ def silence_warnings
77
+ $VERBOSE, v = nil, $VERBOSE
78
+ yield
79
+ ensure
80
+ $VERBOSE = v
81
+ end
82
+ end
@@ -0,0 +1,872 @@
1
+ require File.dirname(__FILE__) + '/helper'
2
+
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
23
+
24
+ class RoutingTest < Test::Unit::TestCase
25
+ %w[get put post delete].each do |verb|
26
+ it "defines #{verb.upcase} request handlers with #{verb}" do
27
+ mock_app {
28
+ send verb, '/hello' do
29
+ 'Hello World'
30
+ end
31
+ }
32
+
33
+ request = Rack::MockRequest.new(@app)
34
+ response = request.request(verb.upcase, '/hello', {})
35
+ assert response.ok?
36
+ assert_equal 'Hello World', response.body
37
+ end
38
+ end
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
+
55
+ it "404s when no route satisfies the request" do
56
+ mock_app {
57
+ get('/foo') { }
58
+ }
59
+ get '/bar'
60
+ assert_equal 404, status
61
+ end
62
+
63
+ # Not valid for older Sinatras
64
+ # it "404s and sets X-Cascade header when no route satisfies the request" do
65
+ # mock_app {
66
+ # get('/foo') { }
67
+ # }
68
+ # get '/bar'
69
+ # assert_equal 404, status
70
+ # assert_equal 'pass', response.headers['X-Cascade']
71
+ # end
72
+
73
+ it "overrides the content-type in error handlers" do
74
+ mock_app {
75
+ before { content_type 'text/plain' }
76
+ error Sinatra::NotFound do
77
+ content_type "text/html"
78
+ "<h1>Not Found</h1>"
79
+ end
80
+ }
81
+
82
+ get '/foo'
83
+ assert_equal 404, status
84
+ assert_equal 'text/html', response["Content-Type"]
85
+ assert_equal "<h1>Not Found</h1>", response.body
86
+ end
87
+
88
+ it 'takes multiple definitions of a route' do
89
+ mock_app {
90
+ user_agent(/Foo/)
91
+ get '/foo' do
92
+ 'foo'
93
+ end
94
+
95
+ get '/foo' do
96
+ 'not foo'
97
+ end
98
+ }
99
+
100
+ get '/foo', {}, 'HTTP_USER_AGENT' => 'Foo'
101
+ assert ok?
102
+ assert_equal 'foo', body
103
+
104
+ get '/foo'
105
+ assert ok?
106
+ assert_equal 'not foo', body
107
+ end
108
+
109
+ it "exposes params with indifferent hash" do
110
+ mock_app {
111
+ get '/:foo' do
112
+ assert_equal 'bar', params['foo']
113
+ assert_equal 'bar', params[:foo]
114
+ assert params.is_a?(HashWithIndifferentAccess)
115
+ assert params.has_key?(:foo)
116
+ 'well, alright'
117
+ end
118
+ }
119
+ get '/bar'
120
+ assert_equal 'well, alright', body
121
+ end
122
+
123
+ it "merges named params and query string params in params" do
124
+ mock_app {
125
+ get '/:foo' do
126
+ assert_equal 'bar', params['foo']
127
+ assert_equal 'bar', params[:foo]
128
+ assert_equal 'biz', params['baz']
129
+ assert_equal 'biz', params[:baz]
130
+ assert_equal 'taco', params['bazzle'][:fart]
131
+ assert_equal 'taco', params[:bazzle]['fart']
132
+ assert params[:bazzle].has_key?('fart')
133
+ assert params['bazzle'].has_key?(:fart)
134
+ assert params[:bazzle].has_key?(:fart)
135
+ assert params[:bazzle].is_a?(HashWithIndifferentAccess)
136
+ end
137
+ }
138
+ get '/bar?baz=biz&bazzle[fart]=taco'
139
+ assert ok?
140
+ end
141
+
142
+ it "supports named params like /hello/:person" do
143
+ mock_app {
144
+ get '/hello/:person' do
145
+ "Hello #{params['person']}"
146
+ end
147
+ }
148
+ get '/hello/Frank'
149
+ assert_equal 'Hello Frank', body
150
+ end
151
+
152
+ it "supports optional named params like /?:foo?/?:bar?" do
153
+ mock_app {
154
+ get '/?:foo?/?:bar?' do
155
+ "foo=#{params[:foo]};bar=#{params[:bar]}"
156
+ end
157
+ }
158
+
159
+ get '/hello/world'
160
+ assert ok?
161
+ assert_equal "foo=hello;bar=world", body
162
+
163
+ get '/hello'
164
+ assert ok?
165
+ assert_equal "foo=hello;bar=", body
166
+
167
+ get '/'
168
+ assert ok?
169
+ assert_equal "foo=;bar=", body
170
+ end
171
+
172
+ it "supports single splat params like /*" do
173
+ mock_app {
174
+ get '/*' do
175
+ assert params['splat'].kind_of?(Array)
176
+ params['splat'].join "\n"
177
+ end
178
+ }
179
+
180
+ get '/foo'
181
+ assert_equal "foo", body
182
+
183
+ get '/foo/bar/baz'
184
+ assert_equal "foo/bar/baz", body
185
+ end
186
+
187
+ it "supports mixing multiple splat params like /*/foo/*/*" do
188
+ mock_app {
189
+ get '/*/foo/*/*' do
190
+ assert params['splat'].kind_of?(Array)
191
+ params['splat'].join "\n"
192
+ end
193
+ }
194
+
195
+ get '/bar/foo/bling/baz/boom'
196
+ assert_equal "bar\nbling\nbaz/boom", body
197
+
198
+ get '/bar/foo/baz'
199
+ assert not_found?
200
+ end
201
+
202
+ it "supports mixing named and splat params like /:foo/*" do
203
+ mock_app {
204
+ get '/:foo/*' do
205
+ assert_equal 'foo', params['foo']
206
+ assert_equal ['bar/baz'], params['splat']
207
+ end
208
+ }
209
+
210
+ get '/foo/bar/baz'
211
+ assert ok?
212
+ end
213
+
214
+ it "matches a dot ('.') as part of a named param" do
215
+ mock_app {
216
+ get '/:foo/:bar' do
217
+ params[:foo]
218
+ end
219
+ }
220
+
221
+ get '/user@example.com/name'
222
+ assert_equal 200, response.status
223
+ assert_equal 'user@example.com', body
224
+ end
225
+
226
+ it "matches a literal dot ('.') outside of named params" do
227
+ mock_app {
228
+ get '/:file.:ext' do
229
+ assert_equal 'pony', params[:file]
230
+ assert_equal 'jpg', params[:ext]
231
+ 'right on'
232
+ end
233
+ }
234
+
235
+ get '/pony.jpg'
236
+ assert_equal 200, response.status
237
+ assert_equal 'right on', body
238
+ end
239
+
240
+ it "literally matches . in paths" do
241
+ route_def '/test.bar'
242
+
243
+ get '/test.bar'
244
+ assert ok?
245
+ get 'test0bar'
246
+ assert not_found?
247
+ end
248
+
249
+ it "literally matches $ in paths" do
250
+ route_def '/test$/'
251
+
252
+ get '/test$/'
253
+ assert ok?
254
+ end
255
+
256
+ it "literally matches + in paths" do
257
+ route_def '/te+st/'
258
+
259
+ get '/te%2Bst/'
260
+ assert ok?
261
+ get '/teeeeeeest/'
262
+ assert not_found?
263
+ end
264
+
265
+ it "literally matches () in paths" do
266
+ route_def '/test(bar)/'
267
+
268
+ get '/test(bar)/'
269
+ assert ok?
270
+ end
271
+
272
+ it "supports basic nested params" do
273
+ mock_app {
274
+ get '/hi' do
275
+ params["person"]["name"]
276
+ end
277
+ }
278
+
279
+ get "/hi?person[name]=John+Doe"
280
+ assert ok?
281
+ assert_equal "John Doe", body
282
+ end
283
+
284
+ it "exposes nested params with indifferent hash" do
285
+ mock_app {
286
+ get '/testme' do
287
+ assert_equal 'baz', params['bar']['foo']
288
+ assert_equal 'baz', params['bar'][:foo]
289
+ 'well, alright'
290
+ end
291
+ }
292
+ get '/testme?bar[foo]=baz'
293
+ assert_equal 'well, alright', body
294
+ end
295
+
296
+ it "supports deeply nested params" do
297
+ expected_params = {
298
+ "emacs" => {
299
+ "map" => { "goto-line" => "M-g g" },
300
+ "version" => "22.3.1"
301
+ },
302
+ "browser" => {
303
+ "firefox" => {"engine" => {"name"=>"spidermonkey", "version"=>"1.7.0"}},
304
+ "chrome" => {"engine" => {"name"=>"V8", "version"=>"1.0"}}
305
+ },
306
+ "paste" => {"name"=>"hello world", "syntax"=>"ruby"}
307
+ }
308
+ mock_app {
309
+ get '/foo' do
310
+ assert_equal expected_params, params
311
+ 'looks good'
312
+ end
313
+ }
314
+ get '/foo', expected_params
315
+ assert ok?
316
+ assert_equal 'looks good', body
317
+ end
318
+
319
+ it "preserves non-nested params" do
320
+ mock_app {
321
+ get '/foo' do
322
+ assert_equal "2", params["article_id"]
323
+ assert_equal "awesome", params['comment']['body']
324
+ assert_nil params['comment[body]']
325
+ 'looks good'
326
+ end
327
+ }
328
+
329
+ get '/foo?article_id=2&comment[body]=awesome'
330
+ assert ok?
331
+ assert_equal 'looks good', body
332
+ end
333
+
334
+ it "matches paths that include spaces encoded with %20" do
335
+ mock_app {
336
+ get '/path with spaces' do
337
+ 'looks good'
338
+ end
339
+ }
340
+
341
+ get '/path%20with%20spaces'
342
+ assert ok?
343
+ assert_equal 'looks good', body
344
+ end
345
+
346
+ it "matches paths that include spaces encoded with +" do
347
+ mock_app {
348
+ get '/path with spaces' do
349
+ 'looks good'
350
+ end
351
+ }
352
+
353
+ get '/path+with+spaces'
354
+ assert ok?
355
+ assert_equal 'looks good', body
356
+ end
357
+
358
+ it "URL decodes named parameters and splats" do
359
+ mock_app {
360
+ get '/:foo/*' do
361
+ assert_equal 'hello world', params['foo']
362
+ assert_equal ['how are you'], params['splat']
363
+ nil
364
+ end
365
+ }
366
+
367
+ get '/hello%20world/how%20are%20you'
368
+ assert ok?
369
+ end
370
+
371
+ it 'supports regular expressions' do
372
+ mock_app {
373
+ get(/^\/foo...\/bar$/) do
374
+ 'Hello World'
375
+ end
376
+ }
377
+
378
+ get '/foooom/bar'
379
+ assert ok?
380
+ assert_equal 'Hello World', body
381
+ end
382
+
383
+ it 'makes regular expression captures available in params[:captures]' do
384
+ mock_app {
385
+ get(/^\/fo(.*)\/ba(.*)/) do
386
+ assert_equal ['orooomma', 'f'], params[:captures]
387
+ 'right on'
388
+ end
389
+ }
390
+
391
+ get '/foorooomma/baf'
392
+ assert ok?
393
+ assert_equal 'right on', body
394
+ end
395
+
396
+ it 'supports regular expression look-alike routes' do
397
+ mock_app {
398
+ get(RegexpLookAlike.new) do
399
+ assert_equal 'this', params[:one]
400
+ assert_equal 'is', params[:two]
401
+ assert_equal 'a', params[:three]
402
+ assert_equal 'test', params[:four]
403
+ 'right on'
404
+ end
405
+ }
406
+
407
+ get '/this/is/a/test/'
408
+ assert ok?
409
+ assert_equal 'right on', body
410
+ end
411
+
412
+ it 'raises a TypeError when pattern is not a String or Regexp' do
413
+ assert_raise(TypeError) {
414
+ mock_app { get(42){} }
415
+ }
416
+ end
417
+
418
+ it "returns response immediately on halt" do
419
+ mock_app {
420
+ get '/' do
421
+ halt 'Hello World'
422
+ 'Boo-hoo World'
423
+ end
424
+ }
425
+
426
+ get '/'
427
+ assert ok?
428
+ assert_equal 'Hello World', body
429
+ end
430
+
431
+ it "halts with a response tuple" do
432
+ mock_app {
433
+ get '/' do
434
+ halt 295, {'Content-Type' => 'text/plain'}, 'Hello World'
435
+ end
436
+ }
437
+
438
+ get '/'
439
+ assert_equal 295, status
440
+ assert_equal 'text/plain', response['Content-Type']
441
+ assert_equal 'Hello World', body
442
+ end
443
+
444
+ it "halts with an array of strings" do
445
+ mock_app {
446
+ get '/' do
447
+ halt %w[Hello World How Are You]
448
+ end
449
+ }
450
+
451
+ get '/'
452
+ assert_equal 'HelloWorldHowAreYou', body
453
+ end
454
+
455
+ it "transitions to the next matching route on pass" do
456
+ mock_app {
457
+ get '/:foo' do
458
+ pass
459
+ 'Hello Foo'
460
+ end
461
+
462
+ get '/*' do
463
+ assert !params.include?('foo')
464
+ 'Hello World'
465
+ end
466
+ }
467
+
468
+ get '/bar'
469
+ assert ok?
470
+ assert_equal 'Hello World', body
471
+ end
472
+
473
+ it "transitions to 404 when passed and no subsequent route matches" do
474
+ mock_app {
475
+ get '/:foo' do
476
+ pass
477
+ 'Hello Foo'
478
+ end
479
+ }
480
+
481
+ get '/bar'
482
+ assert not_found?
483
+ end
484
+
485
+ # it "transitions to 404 and sets X-Cascade header when passed and no subsequent route matches" do
486
+ # mock_app {
487
+ # get '/:foo' do
488
+ # pass
489
+ # 'Hello Foo'
490
+ # end
491
+ #
492
+ # get '/bar' do
493
+ # 'Hello Bar'
494
+ # end
495
+ # }
496
+ #
497
+ # get '/foo'
498
+ # assert not_found?
499
+ # assert_equal 'pass', response.headers['X-Cascade']
500
+ # end
501
+ #
502
+ # it "uses optional block passed to pass as route block if no other route is found" do
503
+ # mock_app {
504
+ # get "/" do
505
+ # pass do
506
+ # "this"
507
+ # end
508
+ # "not this"
509
+ # end
510
+ # }
511
+ #
512
+ # get "/"
513
+ # assert ok?
514
+ # assert "this", body
515
+ # end
516
+
517
+ it "passes when matching condition returns false" do
518
+ mock_app {
519
+ condition { params[:foo] == 'bar' }
520
+ get '/:foo' do
521
+ 'Hello World'
522
+ end
523
+ }
524
+
525
+ get '/bar'
526
+ assert ok?
527
+ assert_equal 'Hello World', body
528
+
529
+ get '/foo'
530
+ assert not_found?
531
+ end
532
+
533
+ it "does not pass when matching condition returns nil" do
534
+ mock_app {
535
+ condition { nil }
536
+ get '/:foo' do
537
+ 'Hello World'
538
+ end
539
+ }
540
+
541
+ get '/bar'
542
+ assert ok?
543
+ assert_equal 'Hello World', body
544
+ end
545
+
546
+ it "passes to next route when condition calls pass explicitly" do
547
+ mock_app {
548
+ condition { pass unless params[:foo] == 'bar' }
549
+ get '/:foo' do
550
+ 'Hello World'
551
+ end
552
+ }
553
+
554
+ get '/bar'
555
+ assert ok?
556
+ assert_equal 'Hello World', body
557
+
558
+ get '/foo'
559
+ assert not_found?
560
+ end
561
+
562
+ it "passes to the next route when host_name does not match" do
563
+ mock_app {
564
+ host_name 'example.com'
565
+ get '/foo' do
566
+ 'Hello World'
567
+ end
568
+ }
569
+ get '/foo'
570
+ assert not_found?
571
+
572
+ get '/foo', {}, { 'HTTP_HOST' => 'example.com' }
573
+ assert_equal 200, status
574
+ assert_equal 'Hello World', body
575
+ end
576
+
577
+ it "passes to the next route when user_agent does not match" do
578
+ mock_app {
579
+ user_agent(/Foo/)
580
+ get '/foo' do
581
+ 'Hello World'
582
+ end
583
+ }
584
+ get '/foo'
585
+ assert not_found?
586
+
587
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
588
+ assert_equal 200, status
589
+ assert_equal 'Hello World', body
590
+ end
591
+
592
+ it "makes captures in user agent pattern available in params[:agent]" do
593
+ mock_app {
594
+ user_agent(/Foo (.*)/)
595
+ get '/foo' do
596
+ 'Hello ' + params[:agent].first
597
+ end
598
+ }
599
+ get '/foo', {}, { 'HTTP_USER_AGENT' => 'Foo Bar' }
600
+ assert_equal 200, status
601
+ assert_equal 'Hello Bar', body
602
+ end
603
+
604
+ it "filters by accept header" do
605
+ mock_app {
606
+ get '/', :provides => :xml do
607
+ request.env['HTTP_ACCEPT']
608
+ end
609
+ }
610
+
611
+ get '/', {}, { 'HTTP_ACCEPT' => 'application/xml' }
612
+ assert ok?
613
+ assert_equal 'application/xml', body
614
+ assert_equal 'application/xml', response.headers['Content-Type']
615
+
616
+ get '/', {}, { :accept => 'text/html' }
617
+ assert !ok?
618
+ end
619
+
620
+ it "allows multiple mime types for accept header" do
621
+ types = ['image/jpeg', 'image/pjpeg']
622
+
623
+ mock_app {
624
+ get '/', :provides => types do
625
+ request.env['HTTP_ACCEPT']
626
+ end
627
+ }
628
+
629
+ types.each do |type|
630
+ get '/', {}, { 'HTTP_ACCEPT' => type }
631
+ assert ok?
632
+ assert_equal type, body
633
+ assert_equal type, response.headers['Content-Type']
634
+ end
635
+ end
636
+
637
+ it 'degrades gracefully when optional accept header is not provided' do
638
+ mock_app {
639
+ get '/', :provides => :xml do
640
+ request.env['HTTP_ACCEPT']
641
+ end
642
+ get '/' do
643
+ 'default'
644
+ end
645
+ }
646
+ get '/'
647
+ assert ok?
648
+ assert_equal 'default', body
649
+ end
650
+
651
+ it 'passes a single url param as block parameters when one param is specified' do
652
+ mock_app {
653
+ get '/:foo' do |foo|
654
+ assert_equal 'bar', foo
655
+ end
656
+ }
657
+
658
+ get '/bar'
659
+ assert ok?
660
+ end
661
+
662
+ it 'passes multiple params as block parameters when many are specified' do
663
+ mock_app {
664
+ get '/:foo/:bar/:baz' do |foo, bar, baz|
665
+ assert_equal 'abc', foo
666
+ assert_equal 'def', bar
667
+ assert_equal 'ghi', baz
668
+ end
669
+ }
670
+
671
+ get '/abc/def/ghi'
672
+ assert ok?
673
+ end
674
+
675
+ it 'passes regular expression captures as block parameters' do
676
+ mock_app {
677
+ get(/^\/fo(.*)\/ba(.*)/) do |foo, bar|
678
+ assert_equal 'orooomma', foo
679
+ assert_equal 'f', bar
680
+ 'looks good'
681
+ end
682
+ }
683
+
684
+ get '/foorooomma/baf'
685
+ assert ok?
686
+ assert_equal 'looks good', body
687
+ end
688
+
689
+ it "supports mixing multiple splat params like /*/foo/*/* as block parameters" do
690
+ mock_app {
691
+ get '/*/foo/*/*' do |foo, bar, baz|
692
+ assert_equal 'bar', foo
693
+ assert_equal 'bling', bar
694
+ assert_equal 'baz/boom', baz
695
+ 'looks good'
696
+ end
697
+ }
698
+
699
+ get '/bar/foo/bling/baz/boom'
700
+ assert ok?
701
+ assert_equal 'looks good', body
702
+ end
703
+
704
+ it 'raises an ArgumentError with block arity > 1 and too many values' do
705
+ mock_app {
706
+ get '/:foo/:bar/:baz' do |foo, bar|
707
+ 'quux'
708
+ end
709
+ }
710
+
711
+ assert_raise(ArgumentError) { get '/a/b/c' }
712
+ end
713
+
714
+ it 'raises an ArgumentError with block param arity > 1 and too few values' do
715
+ mock_app {
716
+ get '/:foo/:bar' do |foo, bar, baz|
717
+ 'quux'
718
+ end
719
+ }
720
+
721
+ assert_raise(ArgumentError) { get '/a/b' }
722
+ end
723
+
724
+ it 'succeeds if no block parameters are specified' do
725
+ mock_app {
726
+ get '/:foo/:bar' do
727
+ 'quux'
728
+ end
729
+ }
730
+
731
+ get '/a/b'
732
+ assert ok?
733
+ assert_equal 'quux', body
734
+ end
735
+
736
+ it 'passes all params with block param arity -1 (splat args)' do
737
+ mock_app {
738
+ get '/:foo/:bar' do |*args|
739
+ args.join
740
+ end
741
+ }
742
+
743
+ get '/a/b'
744
+ assert ok?
745
+ assert_equal 'ab', body
746
+ end
747
+
748
+ it 'allows custom route-conditions to be set via route options' do
749
+ protector = Module.new {
750
+ def protect(*args)
751
+ condition {
752
+ unless authorize(params["user"], params["password"])
753
+ halt 403, "go away"
754
+ end
755
+ }
756
+ end
757
+ }
758
+
759
+ mock_app {
760
+ register protector
761
+
762
+ helpers do
763
+ def authorize(username, password)
764
+ username == "foo" && password == "bar"
765
+ end
766
+ end
767
+
768
+ get "/", :protect => true do
769
+ "hey"
770
+ end
771
+ }
772
+
773
+ get "/"
774
+ assert forbidden?
775
+ assert_equal "go away", body
776
+
777
+ get "/", :user => "foo", :password => "bar"
778
+ assert ok?
779
+ assert_equal "hey", body
780
+ end
781
+
782
+ # NOTE Block params behaves differently under 1.8 and 1.9. Under 1.8, block
783
+ # param arity is lax: declaring a mismatched number of block params results
784
+ # in a warning. Under 1.9, block param arity is strict: mismatched block
785
+ # arity raises an ArgumentError.
786
+
787
+ if RUBY_VERSION >= '1.9'
788
+
789
+ it 'raises an ArgumentError with block param arity 1 and no values' do
790
+ mock_app {
791
+ get '/foo' do |foo|
792
+ 'quux'
793
+ end
794
+ }
795
+
796
+ assert_raise(ArgumentError) { get '/foo' }
797
+ end
798
+
799
+ it 'raises an ArgumentError with block param arity 1 and too many values' do
800
+ mock_app {
801
+ get '/:foo/:bar/:baz' do |foo|
802
+ 'quux'
803
+ end
804
+ }
805
+
806
+ assert_raise(ArgumentError) { get '/a/b/c' }
807
+ end
808
+
809
+ else
810
+
811
+ it 'does not raise an ArgumentError with block param arity 1 and no values' do
812
+ mock_app {
813
+ get '/foo' do |foo|
814
+ 'quux'
815
+ end
816
+ }
817
+
818
+ silence_warnings { get '/foo' }
819
+ assert ok?
820
+ assert_equal 'quux', body
821
+ end
822
+
823
+ it 'does not raise an ArgumentError with block param arity 1 and too many values' do
824
+ mock_app {
825
+ get '/:foo/:bar/:baz' do |foo|
826
+ 'quux'
827
+ end
828
+ }
829
+
830
+ silence_warnings { get '/a/b/c' }
831
+ assert ok?
832
+ assert_equal 'quux', body
833
+ end
834
+
835
+ end
836
+
837
+ it "matches routes defined in superclasses" do
838
+ base = Class.new(Sinatra::Base)
839
+ base.get('/foo') { 'foo in baseclass' }
840
+
841
+ mock_app(base) {
842
+ get('/bar') { 'bar in subclass' }
843
+ }
844
+
845
+ get '/foo'
846
+ assert ok?
847
+ assert_equal 'foo in baseclass', body
848
+
849
+ get '/bar'
850
+ assert ok?
851
+ assert_equal 'bar in subclass', body
852
+ end
853
+
854
+ # broken in old Sinatras (not our problem)
855
+ # it "matches routes in subclasses before superclasses" do
856
+ # base = Class.new(Sinatra::Base)
857
+ # base.get('/foo') { 'foo in baseclass' }
858
+ # base.get('/bar') { 'bar in baseclass' }
859
+ #
860
+ # mock_app(base) {
861
+ # get('/foo') { 'foo in subclass' }
862
+ # }
863
+ #
864
+ # get '/foo'
865
+ # assert ok?
866
+ # assert_equal 'foo in subclass', body
867
+ #
868
+ # get '/bar'
869
+ # assert ok?
870
+ # assert_equal 'bar in baseclass', body
871
+ # end
872
+ end
@@ -0,0 +1,44 @@
1
+ #
2
+ # Sinatra app
3
+ #
4
+ # It is put in a separate file to make sure we are getting a realistic test
5
+ #
6
+ require 'sinatra/base'
7
+ require 'sinatra/resources'
8
+
9
+ class Application < Sinatra::Base
10
+ set :app_file, __FILE__
11
+ register Sinatra::Resources
12
+
13
+ resource :users do
14
+ get do
15
+ 'yo'
16
+ end
17
+
18
+ member do
19
+ get do
20
+ 'hi'
21
+ end
22
+
23
+ get :recent do |id|
24
+ "recent: #{id}"
25
+ end
26
+ end
27
+
28
+ end
29
+
30
+ resource :forums do
31
+ resource :admin do
32
+ get do
33
+ 'woot'
34
+ end
35
+
36
+ post do
37
+ 'success'
38
+ end
39
+ end
40
+
41
+ end
42
+ end
43
+
44
+ Application.run!
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sinatra-hashfix
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Nate Wiger
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-21 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: sinatra
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ - 9
30
+ - 0
31
+ version: 0.9.0
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ description: Changes Sinatra params hash to use HashWithIndifferentAccess
35
+ email: nate@wiger.org
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files:
41
+ - CHANGELOG.rdoc
42
+ - README.rdoc
43
+ files:
44
+ - lib/sinatra/hashfix.rb
45
+ - test/contest.rb
46
+ - test/helper.rb
47
+ - test/sinatra_hashfix_test.rb
48
+ - test/test_app.rb
49
+ - CHANGELOG.rdoc
50
+ - README.rdoc
51
+ has_rdoc: true
52
+ homepage: http://github.com/nateware/sinatra-hashfix
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options:
57
+ - --title
58
+ - Sinatra::Hashfix -- Get your hash fix!
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ segments:
73
+ - 0
74
+ version: "0"
75
+ requirements:
76
+ - sinatra, v0.9.0 or greater
77
+ rubyforge_project: sinatra-hashfix
78
+ rubygems_version: 1.3.6
79
+ signing_key:
80
+ specification_version: 3
81
+ summary: Changes Sinatra params hash to use HashWithIndifferentAccess
82
+ test_files: []
83
+