sinatra-hashfix 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+