rack-jet_router 1.0.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,27 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "rack-jet_router"
5
+ spec.version = '$Release: 1.0.0 $'.split()[1]
6
+ spec.authors = ["makoto kuwata"]
7
+ spec.email = ["kwa(at)kuwata-lab.com"]
8
+
9
+ spec.summary = "Super-fast router class for Rack"
10
+ spec.description = <<'END'
11
+ Super-fast router class for Rack application, derived from Keight.rb.
12
+ END
13
+ spec.homepage = "https://github.com/kwatch/rack-jet_router"
14
+ spec.license = "MIT-License"
15
+
16
+ spec.files = Dir[*%w[
17
+ README.md MIT-LICENSE Rakefile rack-jet_router.gemspec
18
+ lib/**/*.rb
19
+ test/test_helper.rb test/**/*_test.rb
20
+ ]]
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.required_ruby_version = '>= 2.0'
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "minitest"
26
+ spec.add_development_dependency "minitest-ok"
27
+ end
@@ -0,0 +1,618 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ ###
4
+ ### $Release: 1.0.0 $
5
+ ### $Copyright: copyright(c) 2015 kuwata-lab.com all rights reserved $
6
+ ### $License: MIT License $
7
+ ###
8
+
9
+ require_relative '../test_helper'
10
+
11
+
12
+ describe Rack::JetRouter do
13
+
14
+ welcome_app = proc {|env| [200, {}, ["welcome_app"]]}
15
+ #
16
+ book_list_api = proc {|env| [200, {}, ["book_list_api"]]}
17
+ book_create_api = proc {|env| [200, {}, ["book_create_api"]]}
18
+ book_new_api = proc {|env| [200, {}, ["book_new_api"]]}
19
+ book_show_api = proc {|env| [200, {}, ["book_show_api"]]}
20
+ book_update_api = proc {|env| [200, {}, ["book_update_api"]]}
21
+ book_delete_api = proc {|env| [200, {}, ["book_delete_api"]]}
22
+ book_edit_api = proc {|env| [200, {}, ["book_edit_api"]]}
23
+ #
24
+ comment_create_api = proc {|env| [200, {}, ["comment_create_api"]]}
25
+ comment_update_api = proc {|env| [200, {}, ["comment_update_api"]]}
26
+ #
27
+ admin_book_list_app = proc {|env| [200, {}, ["admin_book_list_app"]]}
28
+ admin_book_create_app = proc {|env| [200, {}, ["admin_book_create_app"]]}
29
+ admin_book_new_app = proc {|env| [200, {}, ["admin_book_new_app"]]}
30
+ admin_book_show_app = proc {|env| [200, {}, ["admin_book_show_app"]]}
31
+ admin_book_update_app = proc {|env| [200, {}, ["admin_book_update_app"]]}
32
+ admin_book_delete_app = proc {|env| [200, {}, ["admin_book_delete_app"]]}
33
+ admin_book_edit_app = proc {|env| [200, {}, ["admin_book_edit_app"]]}
34
+ #
35
+ whole_urlpath_mapping = [
36
+ ['/' , welcome_app],
37
+ ['/index.html' , welcome_app],
38
+ ['/api' , [
39
+ ['/books' , [
40
+ ['' , book_list_api],
41
+ ['/new' , book_new_api],
42
+ ['/:id' , book_show_api],
43
+ ['/:id/edit' , book_edit_api],
44
+ ]],
45
+ ['/books/:book_id/comments', [
46
+ ['' , comment_create_api],
47
+ ['/:comment_id' , comment_update_api],
48
+ ]],
49
+ ]],
50
+ ['/admin' , [
51
+ ['/books' , [
52
+ ['' , {:GET=>admin_book_list_app, :POST=>admin_book_create_app}],
53
+ ['/:id' , {:GET=>admin_book_show_app, :PUT=>admin_book_update_app, :DELETE=>admin_book_delete_app}],
54
+ ]],
55
+ ]],
56
+ ]
57
+ #
58
+ jet_router = Rack::JetRouter.new(whole_urlpath_mapping)
59
+ #
60
+ def new_env(req_method, req_path, opts={})
61
+ opts[:method] = req_method.to_s
62
+ env = ::Rack::MockRequest.env_for(req_path, opts)
63
+ return env
64
+ end
65
+
66
+
67
+ describe '#compile_urlpath_pattern()' do
68
+
69
+ it "[!joozm] escapes metachars with backslash in text part." do
70
+ jet_router.instance_exec(self) do |_|
71
+ _.ok {compile_urlpath_pattern('/foo.html')} == ['/foo\.html', nil]
72
+ end
73
+ end
74
+
75
+ it "[!rpezs] converts '/books/:id' into '/books/([^./]+)'." do
76
+ jet_router.instance_exec(self) do |_|
77
+ _.ok {compile_urlpath_pattern('/books/:id')} == ['/books/([^./]+)', ['id']]
78
+ end
79
+ end
80
+
81
+ it "[!4dcsa] converts '/index(.:format)' into '/index(?:\.([^./]+))?'." do
82
+ jet_router.instance_exec(self) do |_|
83
+ _.ok {compile_urlpath_pattern('/index(.:format)')} == ['/index(?:\.([^./]+))?', ['format']]
84
+ end
85
+ end
86
+
87
+ it "[!1d5ya] rethrns compiled string and nil when no urlpath parameters nor parens." do
88
+ jet_router.instance_exec(self) do |_|
89
+ _.ok {compile_urlpath_pattern('/index')} == ['/index', nil]
90
+ end
91
+ end
92
+
93
+ it "[!of1zq] returns compiled string and urlpath param names when urlpath param or parens exist." do
94
+ jet_router.instance_exec(self) do |_|
95
+ _.ok {compile_urlpath_pattern('/books/:id')} == ['/books/([^./]+)', ['id']]
96
+ _.ok {compile_urlpath_pattern('/books/:id(.:format)')} == ['/books/([^./]+)(?:\.([^./]+))?', ['id', 'format']]
97
+ _.ok {compile_urlpath_pattern('/index(.html)')} == ['/index(?:\.html)?', []]
98
+ end
99
+ end
100
+
101
+ end
102
+
103
+
104
+ describe '#compile_mapping()' do
105
+
106
+ it "[!xzo7k] returns regexp, hash, and array." do
107
+ mapping = [
108
+ ['/', welcome_app],
109
+ ['/books/:id' , book_show_api],
110
+ ]
111
+ expected = '
112
+ \A
113
+ (?:
114
+ /books/[^./]+(\z)
115
+ )
116
+ \z
117
+ '.gsub(/\s+/, '')
118
+ jet_router.instance_exec(self) do |_|
119
+ rexp, dict, list = compile_mapping(mapping)
120
+ _.ok {rexp} == Regexp.new(expected)
121
+ _.ok {dict} == {
122
+ '/' => welcome_app,
123
+ }
124
+ _.ok {list} == [
125
+ [%r'\A/books/([^./]+)\z', ['id'], book_show_api],
126
+ ]
127
+ end
128
+ end
129
+
130
+ it "[!gfxgr] deletes unnecessary grouping." do
131
+ mapping = [
132
+ ['/' , welcome_app],
133
+ ['/api/books' , book_list_api],
134
+ ]
135
+ expected = '
136
+ \A
137
+ \z
138
+ '.gsub(/\s+/, '')
139
+ jet_router.instance_exec(self) do |_|
140
+ rexp, dict, list = compile_mapping(mapping)
141
+ _.ok {rexp} == Regexp.new(expected)
142
+ _.ok {dict} == {
143
+ '/' => welcome_app,
144
+ '/api/books' => book_list_api,
145
+ }
146
+ _.ok {list} == [
147
+ ]
148
+ end
149
+ end
150
+
151
+ it "[!pv2au] deletes unnecessary urlpath regexp." do
152
+ mapping = [
153
+ ['/' , welcome_app],
154
+ ['/api', [
155
+ ['/books', [
156
+ ['' , book_list_api],
157
+ ['/new' , book_new_api],
158
+ ]],
159
+ ['/books2', [
160
+ ['/:id' , book_show_api],
161
+ ]],
162
+ ]],
163
+ ]
164
+ expected = '
165
+ \A
166
+ (?:
167
+ /api
168
+ (?:
169
+ /books2
170
+ (?:/[^./]+(\z))
171
+ )
172
+ )
173
+ \z
174
+ '.gsub(/\s+/, '')
175
+ jet_router.instance_exec(self) do |_|
176
+ rexp, dict, list = compile_mapping(mapping)
177
+ _.ok {rexp} == Regexp.new(expected)
178
+ _.ok {dict} == {
179
+ '/' => welcome_app,
180
+ '/api/books' => book_list_api,
181
+ '/api/books/new' => book_new_api,
182
+ }
183
+ _.ok {list} == [
184
+ [%r'\A/api/books2/([^./]+)\z', ['id'], book_show_api],
185
+ ]
186
+ end
187
+ end
188
+
189
+ it "[!l63vu] handles urlpath pattern as fixed when no urlpath params." do
190
+ mapping = [
191
+ ['/api/books' , book_list_api],
192
+ ]
193
+ expected = '
194
+ \A
195
+ \z
196
+ '.gsub(/\s+/, '')
197
+ jet_router.instance_exec(self) do |_|
198
+ rexp, dict, list = compile_mapping(mapping)
199
+ _.ok {rexp} == Regexp.new(expected)
200
+ _.ok {dict} == {
201
+ '/api/books' => book_list_api,
202
+ }
203
+ _.ok {list} == [
204
+ ]
205
+ end
206
+ end
207
+
208
+ it "[!vfytw] handles urlpath pattern as variable when urlpath param exists." do
209
+ mapping = [
210
+ ['/api/books/:id' , book_show_api],
211
+ ]
212
+ expected = '
213
+ \A
214
+ (?:
215
+ /api/books/[^./]+(\z)
216
+ )
217
+ \z
218
+ '.gsub(/\s+/, '')
219
+ jet_router.instance_exec(self) do |_|
220
+ rexp, dict, list = compile_mapping(mapping)
221
+ _.ok {rexp} == Regexp.new(expected)
222
+ _.ok {dict} == {
223
+ }
224
+ _.ok {list} == [
225
+ [%r'\A/api/books/([^./]+)\z', ['id'], book_show_api],
226
+ ]
227
+ end
228
+ end
229
+
230
+ it "[!2ktpf] handles end-point." do
231
+ mapping = [
232
+ ['/' , welcome_app],
233
+ ['/api/books' , book_list_api],
234
+ ['/api/books/:id' , book_show_api],
235
+ ]
236
+ expected = '
237
+ \A
238
+ (?:
239
+ /api/books/[^./]+(\z)
240
+ )
241
+ \z
242
+ '.gsub(/\s+/, '')
243
+ jet_router.instance_exec(self) do |_|
244
+ rexp, dict, list = compile_mapping(mapping)
245
+ _.ok {rexp} == Regexp.new(expected)
246
+ _.ok {dict} == {
247
+ '/' => welcome_app,
248
+ '/api/books' => book_list_api,
249
+ }
250
+ _.ok {list} == [
251
+ [%r'\A/api/books/([^./]+)\z', ['id'], book_show_api],
252
+ ]
253
+ end
254
+ end
255
+
256
+ it "[!ospaf] accepts nested mapping." do
257
+ mapping = [
258
+ ['/admin', [
259
+ ['/api', [
260
+ ['/books', [
261
+ ['', book_list_api],
262
+ ['/:id', book_show_api],
263
+ ]],
264
+ ]],
265
+ ]],
266
+ ]
267
+ expected = '
268
+ \A
269
+ (?:
270
+ /admin
271
+ (?:
272
+ /api
273
+ (?:
274
+ /books
275
+ (?:/[^./]+(\z))
276
+ )
277
+ )
278
+ )
279
+ \z
280
+ '.gsub(/\s+/, '')
281
+ jet_router.instance_exec(self) do |_|
282
+ rexp, dict, list = compile_mapping(mapping)
283
+ _.ok {rexp} == Regexp.new(expected)
284
+ _.ok {dict} == {
285
+ '/admin/api/books' => book_list_api,
286
+ }
287
+ _.ok {list} == [
288
+ [%r'\A/admin/api/books/([^./]+)\z', ['id'], book_show_api],
289
+ ]
290
+ end
291
+ end
292
+
293
+ describe "[!guhdc] if mapping dict is specified..." do
294
+
295
+ it "[!r7cmk] converts keys into string." do
296
+ mapping = [
297
+ ['/books', {:GET=>book_list_api, :POST=>book_create_api}]
298
+ ]
299
+ Rack::JetRouter.new([]).instance_exec(self) do |_|
300
+ rexp, dict, list = compile_mapping(mapping)
301
+ _.ok {dict['/books']} == {'GET'=>book_list_api, 'POST'=>book_create_api}
302
+ end
303
+ end
304
+
305
+ it "[!z9kww] allows 'ANY' as request method." do
306
+ mapping = [
307
+ ['/books', {'ANY'=>book_list_api, 'POST'=>book_create_api}]
308
+ ]
309
+ Rack::JetRouter.new([]).instance_exec(self) do |_|
310
+ rexp, dict, list = compile_mapping(mapping)
311
+ _.ok {dict['/books']} == {'ANY'=>book_list_api, 'POST'=>book_create_api}
312
+ end
313
+ end
314
+
315
+ it "[!k7sme] raises error when unknown request method specified." do
316
+ mapping = [
317
+ ['/books', {"UNLOCK"=>book_list_api}]
318
+ ]
319
+ Rack::JetRouter.new([]).instance_exec(self) do |_|
320
+ pr = proc { compile_mapping(mapping) }
321
+ _.ok {pr}.raise?(ArgumentError, '"UNLOCK": unknown request method.')
322
+ end
323
+ end
324
+
325
+ end
326
+
327
+ end
328
+
329
+
330
+ describe '#error_not_found()' do
331
+
332
+ it "[!mlruv] returns 404 response." do
333
+ expected = [404, {"Content-Type"=>"text/plain"}, ["404 Not Found"]]
334
+ env = new_env('GET', '/xxx')
335
+ jet_router.instance_exec(self) do |_|
336
+ _.ok {error_not_found(env)} == expected
337
+ end
338
+ end
339
+
340
+ end
341
+
342
+
343
+ describe '#error_not_allowed()' do
344
+
345
+ it "[!mjigf] returns 405 response." do
346
+ expected = [405, {"Content-Type"=>"text/plain"}, ["405 Method Not Allowed"]]
347
+ env = new_env('POST', '/')
348
+ jet_router.instance_exec(self) do |_|
349
+ _.ok {error_not_allowed(env)} == expected
350
+ end
351
+ end
352
+
353
+ end
354
+
355
+
356
+ describe '#initialize()' do
357
+
358
+ it "[!u2ff4] compiles urlpath mapping." do
359
+ jet_router.instance_exec(self) do |_|
360
+ expected = '
361
+ \A
362
+ (?:
363
+ /api
364
+ (?:
365
+ /books
366
+ (?:/[^./]+(\z)|/[^./]+/edit(\z))
367
+ |
368
+ /books/[^./]+/comments
369
+ (?:(\z)|/[^./]+(\z))
370
+ )
371
+ |
372
+ /admin
373
+ (?:/books
374
+ (?:/[^./]+(\z))
375
+ )
376
+ )
377
+ \z
378
+ '.gsub(/\s+/, '')
379
+ _.ok {@urlpath_rexp} == Regexp.new(expected)
380
+ _.ok {@fixed_urlpath_dict} == {
381
+ '/' => welcome_app,
382
+ '/index.html' => welcome_app,
383
+ '/api/books' => book_list_api,
384
+ '/api/books/new' => book_new_api,
385
+ '/admin/books' => {
386
+ 'GET'=>admin_book_list_app,
387
+ 'POST'=>admin_book_create_app,
388
+ },
389
+ }
390
+ _.ok {@variable_urlpath_list} == [
391
+ [%r'\A/api/books/([^./]+)\z', ['id'], book_show_api],
392
+ [%r'\A/api/books/([^./]+)/edit\z', ['id'], book_edit_api],
393
+ [%r'\A/api/books/([^./]+)/comments\z', ['book_id'], comment_create_api],
394
+ [%r'\A/api/books/([^./]+)/comments/([^./]+)\z', ['book_id', 'comment_id'], comment_update_api],
395
+ [%r'\A/admin/books/([^./]+)\z', ['id'], {'GET' => admin_book_show_app,
396
+ 'PUT' => admin_book_update_app,
397
+ 'DELETE' => admin_book_delete_app}],
398
+ ]
399
+ end
400
+ end
401
+
402
+ end
403
+
404
+
405
+ describe '#find()' do
406
+
407
+ it "[!ijqws] returns mapped object and urlpath parameter values when urlpath found." do
408
+ ret = jet_router.find('/api/books/123')
409
+ ok {ret} == [book_show_api, {"id"=>"123"}]
410
+ end
411
+
412
+ it "[!vpdzn] returns nil when urlpath not found." do
413
+ ok {jet_router.find('/api')} == nil
414
+ ok {jet_router.find('/api/book')} == nil
415
+ ok {jet_router.find('/api/books/')} == nil
416
+ end
417
+
418
+ it "[!24khb] finds in fixed urlpaths at first." do
419
+ ok {jet_router.find('/')} == [welcome_app, nil]
420
+ ok {jet_router.find('/api/books')} == [book_list_api, nil]
421
+ dict = {'GET'=>admin_book_list_app, 'POST'=>admin_book_create_app}
422
+ ok {jet_router.find('/admin/books')} == [dict, nil]
423
+ end
424
+
425
+ it "[!iwyzd] urlpath param value is nil when found in fixed urlpaths." do
426
+ obj, vars = jet_router.find('/')
427
+ ok {vars} == nil
428
+ obj, vars = jet_router.find('/api/books')
429
+ ok {vars} == nil
430
+ end
431
+
432
+ it "[!upacd] finds in variable urlpath cache if it is enabled." do
433
+ mapping = [
434
+ ['/api/books/:id', book_show_api],
435
+ ]
436
+ r = Rack::JetRouter.new(mapping, urlpath_cache_size: 3)
437
+ pair = r.find('/api/books/123')
438
+ ok {pair} == [book_show_api, {"id"=>"123"}]
439
+ r.instance_exec(self) do |_|
440
+ _.ok {@variable_urlpath_cache} == {'/api/books/123'=>pair}
441
+ #
442
+ @variable_urlpath_cache['/api/books/999'] = [book_list_api, {"ID"=>"111"}]
443
+ end
444
+ pair = r.find('/api/books/999')
445
+ ok {pair} == [book_list_api, {"ID"=>"111"}]
446
+ end
447
+
448
+ it "[!84inr] caches result when variable urlpath cache enabled." do
449
+ mapping = [
450
+ ['/books/:id', book_show_api],
451
+ ]
452
+ r = Rack::JetRouter.new(mapping, urlpath_cache_size: 3)
453
+ #
454
+ pair1 = r.find('/books/1'); ok {pair1} == [book_show_api, {"id"=>"1"}]
455
+ pair2 = r.find('/books/2'); ok {pair2} == [book_show_api, {"id"=>"2"}]
456
+ pair3 = r.find('/books/3'); ok {pair3} == [book_show_api, {"id"=>"3"}]
457
+ r.instance_exec(self) do |_|
458
+ _.ok {@variable_urlpath_cache} == {
459
+ '/books/1'=>pair1,
460
+ '/books/2'=>pair2,
461
+ '/books/3'=>pair3,
462
+ }
463
+ end
464
+ #
465
+ pair4 = r.find('/books/4'); ok {pair4} == [book_show_api, {"id"=>"4"}]
466
+ r.instance_exec(self) do |_|
467
+ _.ok {@variable_urlpath_cache} == {
468
+ '/books/2'=>pair2,
469
+ '/books/3'=>pair3,
470
+ '/books/4'=>pair4,
471
+ }
472
+ end
473
+ end
474
+
475
+ it "[!1zx7t] variable urlpath cache is based on LRU." do
476
+ mapping = [
477
+ ['/books/:id', book_show_api],
478
+ ]
479
+ r = Rack::JetRouter.new(mapping, urlpath_cache_size: 3)
480
+ #
481
+ pair1 = r.find('/books/1')
482
+ pair2 = r.find('/books/2')
483
+ pair3 = r.find('/books/3')
484
+ pair4 = r.find('/books/4')
485
+ r.instance_exec(self) do |_|
486
+ _.ok {@variable_urlpath_cache} == {
487
+ '/books/2'=>pair2,
488
+ '/books/3'=>pair3,
489
+ '/books/4'=>pair4,
490
+ }
491
+ end
492
+ #
493
+ ok {r.find('/books/3')} == pair3
494
+ r.instance_exec(self) do |_|
495
+ _.ok {@variable_urlpath_cache} == {
496
+ '/books/2'=>pair2,
497
+ '/books/4'=>pair4,
498
+ '/books/3'=>pair3,
499
+ }
500
+ end
501
+ #
502
+ ok {r.find('/books/1')} == pair1
503
+ r.instance_exec(self) do |_|
504
+ _.ok {@variable_urlpath_cache} == {
505
+ '/books/4'=>pair4,
506
+ '/books/3'=>pair3,
507
+ '/books/1'=>pair1,
508
+ }
509
+ end
510
+ end
511
+
512
+ end
513
+
514
+
515
+ describe '#call()' do
516
+
517
+ it "[!hse47] invokes app mapped to request urlpath." do
518
+ ok {jet_router.call(new_env(:GET, '/api/books/123'))} == [200, {}, ["book_show_api"]]
519
+ ok {jet_router.call(new_env(:PUT, '/admin/books/123'))} == [200, {}, ["admin_book_update_app"]]
520
+ end
521
+
522
+ it "[!fpw8x] finds mapped app according to env['PATH_INFO']." do
523
+ ok {jet_router.call(new_env(:GET, '/api/books'))} == [200, {}, ["book_list_api"]]
524
+ ok {jet_router.call(new_env(:GET, '/api/books/123'))} == [200, {}, ["book_show_api"]]
525
+ end
526
+
527
+ it "[!wxt2g] guesses correct urlpath and redirects to it automaticaly when request path not found." do
528
+ headers = {"Content-Type"=>"text/plain", "Location"=>"/api/books"}
529
+ content = "Redirect to /api/books"
530
+ ok {jet_router.call(new_env(:GET, '/api/books/'))} == [301, headers, [content]]
531
+ #
532
+ headers = {"Content-Type"=>"text/plain", "Location"=>"/api/books/78"}
533
+ content = "Redirect to /api/books/78"
534
+ ok {jet_router.call(new_env(:GET, '/api/books/78/'))} == [301, headers, [content]]
535
+ end
536
+
537
+ it "[!3vsua] doesn't redict automatically when request path is '/'." do
538
+ r = Rack::JetRouter.new([['/api/books', book_list_api]])
539
+ ok {r.call(new_env(:GET, '/'))} == [404, {"Content-Type"=>"text/plain"}, ["404 Not Found"]]
540
+ end
541
+
542
+ it "[!30x0k] returns 404 when request urlpath not found." do
543
+ expected = [404, {"Content-Type"=>"text/plain"}, ["404 Not Found"]]
544
+ ok {jet_router.call(new_env(:GET, '/xxx'))} == expected
545
+ ok {jet_router.call(new_env(:GET, '/api/book'))} == expected
546
+ end
547
+
548
+ describe "[!gclbs] if mapped object is a Hash..." do
549
+
550
+ it "[!p1fzn] invokes app mapped to request method." do
551
+ ok {jet_router.call(new_env(:GET, '/admin/books'))} == [200, {}, ["admin_book_list_app"]]
552
+ ok {jet_router.call(new_env(:POST, '/admin/books'))} == [200, {}, ["admin_book_create_app"]]
553
+ ok {jet_router.call(new_env(:GET, '/admin/books/123'))} == [200, {}, ["admin_book_show_app"]]
554
+ ok {jet_router.call(new_env(:PUT, '/admin/books/123'))} == [200, {}, ["admin_book_update_app"]]
555
+ ok {jet_router.call(new_env(:DELETE, '/admin/books/123'))} == [200, {}, ["admin_book_delete_app"]]
556
+ end
557
+
558
+ it "[!5m64a] returns 405 when request method is not allowed." do
559
+ expected = [405, {"Content-Type"=>"text/plain"}, ["405 Method Not Allowed"]]
560
+ ok {jet_router.call(new_env(:PUT, '/admin/books'))} == expected
561
+ ok {jet_router.call(new_env(:FOOBAR, '/admin/books'))} == expected
562
+ end
563
+
564
+ it "[!ys1e2] uses GET method when HEAD is not mapped." do
565
+ ok {jet_router.call(new_env(:HEAD, '/admin/books'))} == [200, {}, ["admin_book_list_app"]]
566
+ ok {jet_router.call(new_env(:HEAD, '/admin/books/123'))} == [200, {}, ["admin_book_show_app"]]
567
+ end
568
+
569
+ it "[!2hx6j] try ANY method when request method is not mapped." do
570
+ mapping = [
571
+ ['/admin/books', {:ANY=>admin_book_list_app}]
572
+ ]
573
+ r = Rack::JetRouter.new(mapping)
574
+ expected = [200, {}, ["admin_book_list_app"]]
575
+ ok {r.call(new_env(:GET, '/admin/books'))} == expected
576
+ ok {r.call(new_env(:POST, '/admin/books'))} == expected
577
+ ok {r.call(new_env(:PUT, '/admin/books'))} == expected
578
+ ok {r.call(new_env(:DELETE, '/admin/books'))} == expected
579
+ end
580
+
581
+ end
582
+
583
+ it "[!2c32f] stores urlpath parameters as env['rack.urlpath_params']." do
584
+ env = new_env(:GET, '/api/books')
585
+ jet_router.call(env)
586
+ ok {env['rack.urlpath_params']} == nil
587
+ env = new_env(:GET, '/api/books/123')
588
+ jet_router.call(env)
589
+ ok {env['rack.urlpath_params']} == {"id"=>"123"}
590
+ env = new_env(:GET, '/api/books/123/comments/999')
591
+ jet_router.call(env)
592
+ ok {env['rack.urlpath_params']} == {"book_id"=>"123", "comment_id"=>"999"}
593
+ #
594
+ env = new_env(:GET, '/admin/books')
595
+ jet_router.call(env)
596
+ ok {env['rack.urlpath_params']} == nil
597
+ env = new_env(:GET, '/admin/books/123')
598
+ jet_router.call(env)
599
+ ok {env['rack.urlpath_params']} == {"id"=>"123"}
600
+ end
601
+
602
+ end
603
+
604
+
605
+ describe 'REQUEST_METHODS' do
606
+
607
+ it "[!haggu] contains available request methods." do
608
+ Rack::JetRouter::REQUEST_METHODS.each do |k, v|
609
+ ok {k}.is_a?(String)
610
+ ok {v}.is_a?(Symbol)
611
+ ok {v.to_s} == k
612
+ end
613
+ end
614
+
615
+ end
616
+
617
+
618
+ end