roda-cj 0.9.1

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 (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +13 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +715 -0
  5. data/Rakefile +124 -0
  6. data/lib/roda/plugins/all_verbs.rb +48 -0
  7. data/lib/roda/plugins/default_headers.rb +50 -0
  8. data/lib/roda/plugins/error_handler.rb +69 -0
  9. data/lib/roda/plugins/flash.rb +108 -0
  10. data/lib/roda/plugins/h.rb +24 -0
  11. data/lib/roda/plugins/halt.rb +79 -0
  12. data/lib/roda/plugins/header_matchers.rb +57 -0
  13. data/lib/roda/plugins/hooks.rb +106 -0
  14. data/lib/roda/plugins/indifferent_params.rb +47 -0
  15. data/lib/roda/plugins/middleware.rb +88 -0
  16. data/lib/roda/plugins/multi_route.rb +77 -0
  17. data/lib/roda/plugins/not_found.rb +62 -0
  18. data/lib/roda/plugins/pass.rb +34 -0
  19. data/lib/roda/plugins/render.rb +217 -0
  20. data/lib/roda/plugins/streaming.rb +165 -0
  21. data/lib/roda/version.rb +3 -0
  22. data/lib/roda.rb +610 -0
  23. data/spec/composition_spec.rb +19 -0
  24. data/spec/env_spec.rb +11 -0
  25. data/spec/integration_spec.rb +63 -0
  26. data/spec/matchers_spec.rb +683 -0
  27. data/spec/module_spec.rb +29 -0
  28. data/spec/opts_spec.rb +42 -0
  29. data/spec/plugin/all_verbs_spec.rb +29 -0
  30. data/spec/plugin/default_headers_spec.rb +63 -0
  31. data/spec/plugin/error_handler_spec.rb +67 -0
  32. data/spec/plugin/flash_spec.rb +123 -0
  33. data/spec/plugin/h_spec.rb +13 -0
  34. data/spec/plugin/halt_spec.rb +62 -0
  35. data/spec/plugin/header_matchers_spec.rb +61 -0
  36. data/spec/plugin/hooks_spec.rb +97 -0
  37. data/spec/plugin/indifferent_params_spec.rb +13 -0
  38. data/spec/plugin/middleware_spec.rb +52 -0
  39. data/spec/plugin/multi_route_spec.rb +98 -0
  40. data/spec/plugin/not_found_spec.rb +99 -0
  41. data/spec/plugin/pass_spec.rb +23 -0
  42. data/spec/plugin/render_spec.rb +148 -0
  43. data/spec/plugin/streaming_spec.rb +52 -0
  44. data/spec/plugin_spec.rb +61 -0
  45. data/spec/redirect_spec.rb +24 -0
  46. data/spec/request_spec.rb +55 -0
  47. data/spec/response_spec.rb +131 -0
  48. data/spec/session_spec.rb +35 -0
  49. data/spec/spec_helper.rb +89 -0
  50. data/spec/version_spec.rb +8 -0
  51. metadata +136 -0
@@ -0,0 +1,683 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe "capturing" do
4
+ it "doesn't yield the verb" do
5
+ app do |r|
6
+ r.get do |*args|
7
+ args.size.to_s
8
+ end
9
+ end
10
+
11
+ body.should == '0'
12
+ end
13
+
14
+ it "doesn't yield the path" do
15
+ app do |r|
16
+ r.get "home" do |*args|
17
+ args.size.to_s
18
+ end
19
+ end
20
+
21
+ body('/home').should == '0'
22
+ end
23
+
24
+ it "yields the segment" do
25
+ app do |r|
26
+ r.get "user", :id do |id|
27
+ id
28
+ end
29
+ end
30
+
31
+ body("/user/johndoe").should == 'johndoe'
32
+ end
33
+
34
+ it "yields a number" do
35
+ app do |r|
36
+ r.get "user", :id do |id|
37
+ id
38
+ end
39
+ end
40
+
41
+ body("/user/101").should == '101'
42
+ end
43
+
44
+ it "yields a segment per nested block" do
45
+ app do |r|
46
+ r.on :one do |one|
47
+ r.on :two do |two|
48
+ r.on :three do |three|
49
+ one + two + three
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ body("/one/two/three").should == "onetwothree"
56
+ end
57
+
58
+ it "regex captures in regex format" do
59
+ app do |r|
60
+ r.get %r{posts/(\d+)-(.*)} do |id, slug|
61
+ id + slug
62
+ end
63
+ end
64
+
65
+ body("/posts/123-postal-service").should == "123postal-service"
66
+ end
67
+ end
68
+
69
+ describe "r.is" do
70
+ it "ensures the patch is matched fully" do
71
+ app do |r|
72
+ r.is "" do
73
+ "+1"
74
+ end
75
+ end
76
+
77
+ body.should == '+1'
78
+ status('//').should == 404
79
+ end
80
+
81
+ it "handles no arguments" do
82
+ app do |r|
83
+ r.on "" do
84
+ r.is do
85
+ "+1"
86
+ end
87
+ end
88
+ end
89
+
90
+ body.should == '+1'
91
+ status('//').should == 404
92
+ end
93
+
94
+ it "matches strings" do
95
+ app do |r|
96
+ r.is "123" do
97
+ "+1"
98
+ end
99
+ end
100
+
101
+ body("/123").should == '+1'
102
+ status("/123/").should == 404
103
+ end
104
+
105
+ it "matches regexps" do
106
+ app do |r|
107
+ r.is /(\w+)/ do |id|
108
+ id
109
+ end
110
+ end
111
+
112
+ body("/123").should == '123'
113
+ status("/123/").should == 404
114
+ end
115
+
116
+ it "matches segments" do
117
+ app do |r|
118
+ r.is :id do |id|
119
+ id
120
+ end
121
+ end
122
+
123
+ body("/123").should == '123'
124
+ status("/123/").should == 404
125
+ end
126
+ end
127
+
128
+ describe "matchers" do
129
+ it "should handle string with embedded param" do
130
+ app do |r|
131
+ r.on "posts/:id" do |id|
132
+ id
133
+ end
134
+ end
135
+
136
+ body('/posts/123').should == '123'
137
+ status('/post/123').should == 404
138
+ end
139
+
140
+ it "should handle multiple params in single string" do
141
+ app do |r|
142
+ r.on "u/:uid/posts/:id" do |uid, id|
143
+ uid + id
144
+ end
145
+ end
146
+
147
+ body("/u/jdoe/posts/123").should == 'jdoe123'
148
+ status("/u/jdoe/pots/123").should == 404
149
+ end
150
+
151
+ it "should escape regexp metacharaters in string" do
152
+ app do |r|
153
+ r.on "u/:uid/posts?/:id" do |uid, id|
154
+ uid + id
155
+ end
156
+ end
157
+
158
+ body("/u/jdoe/posts?/123").should == 'jdoe123'
159
+ status("/u/jdoe/post/123").should == 404
160
+ end
161
+
162
+ it "should handle colons by themselves" do
163
+ app do |r|
164
+ r.on "u/:/:uid/posts/::id" do |uid, id|
165
+ uid + id
166
+ end
167
+ end
168
+
169
+ body("/u/:/jdoe/posts/:123").should == 'jdoe123'
170
+ status("/u/a/jdoe/post/b123").should == 404
171
+ end
172
+
173
+ it "should handle regexes and nesting" do
174
+ app do |r|
175
+ r.on(/u\/(\w+)/) do |uid|
176
+ r.on(/posts\/(\d+)/) do |id|
177
+ uid + id
178
+ end
179
+ end
180
+ end
181
+
182
+ body("/u/jdoe/posts/123").should == 'jdoe123'
183
+ status("/u/jdoe/pots/123").should == 404
184
+ end
185
+
186
+ it "should handle regex nesting colon param style" do
187
+ app do |r|
188
+ r.on(/u:(\w+)/) do |uid|
189
+ r.on(/posts:(\d+)/) do |id|
190
+ uid + id
191
+ end
192
+ end
193
+ end
194
+
195
+ body("/u:jdoe/posts:123").should == 'jdoe123'
196
+ status("/u:jdoe/poss:123").should == 404
197
+ end
198
+
199
+ it "symbol matching" do
200
+ app do |r|
201
+ r.on "user", :id do |uid|
202
+ r.on "posts", :pid do |id|
203
+ uid + id
204
+ end
205
+ end
206
+ end
207
+
208
+ body("/user/jdoe/posts/123").should == 'jdoe123'
209
+ status("/user/jdoe/pots/123").should == 404
210
+ end
211
+
212
+ it "paths and numbers" do
213
+ app do |r|
214
+ r.on "about" do
215
+ r.on :one, :two do |one, two|
216
+ one + two
217
+ end
218
+ end
219
+ end
220
+
221
+ body("/about/1/2").should == '12'
222
+ status("/about/1").should == 404
223
+ end
224
+
225
+ it "paths and decimals" do
226
+ app do |r|
227
+ r.on "about" do
228
+ r.on(/(\d+)/) do |one|
229
+ one
230
+ end
231
+ end
232
+ end
233
+
234
+ body("/about/1").should == '1'
235
+ status("/about/1.2").should == 404
236
+ end
237
+
238
+ it "should allow arrays to match any value" do
239
+ app do |r|
240
+ r.on [/(\d+)/, /\d+(bar)?/] do |id|
241
+ id
242
+ end
243
+ end
244
+
245
+ body('/123').should == '123'
246
+ body('/123bar').should == 'bar'
247
+ status('/123bard').should == 404
248
+ end
249
+
250
+ it "should have array capture match string if match" do
251
+ app do |r|
252
+ r.on %w'p q' do |id|
253
+ id
254
+ end
255
+ end
256
+
257
+ body('/p').should == 'p'
258
+ body('/q').should == 'q'
259
+ status('/r').should == 404
260
+ end
261
+ end
262
+
263
+ describe "r.on" do
264
+ it "executes on no arguments" do
265
+ app do |r|
266
+ r.on do
267
+ "+1"
268
+ end
269
+ end
270
+
271
+ body.should == '+1'
272
+ end
273
+
274
+ it "executes on true" do
275
+ app do |r|
276
+ r.on true do
277
+ "+1"
278
+ end
279
+ end
280
+
281
+ body.should == '+1'
282
+ end
283
+
284
+ it "executes on non-false" do
285
+ app do |r|
286
+ r.on "123" do
287
+ "+1"
288
+ end
289
+ end
290
+
291
+ body("/123").should == '+1'
292
+ end
293
+
294
+ it "ensures SCRIPT_NAME and PATH_INFO are reverted" do
295
+ app do |r|
296
+ r.on lambda { r.env["SCRIPT_NAME"] = "/hello"; false } do
297
+ "Unreachable"
298
+ end
299
+
300
+ r.on do
301
+ r.env["SCRIPT_NAME"] + ':' + r.env["PATH_INFO"]
302
+ end
303
+ end
304
+
305
+ body("/hello").should == ':/hello'
306
+ end
307
+
308
+ it "doesn't mutate SCRIPT_NAME or PATH_INFO after request is returned" do
309
+ app do |r|
310
+ r.on 'login', 'foo' do
311
+ "Unreachable"
312
+ end
313
+
314
+ r.on do
315
+ r.env["SCRIPT_NAME"] + ':' + r.env["PATH_INFO"]
316
+ end
317
+ end
318
+
319
+ pi, sn = '/login', ''
320
+ env = {"REQUEST_METHOD" => "GET", "PATH_INFO" => pi, "SCRIPT_NAME" => sn}
321
+ app.call(env)[2].join.should == ":/login"
322
+ env["PATH_INFO"].should equal(pi)
323
+ env["SCRIPT_NAME"].should equal(sn)
324
+ end
325
+
326
+ it "skips consecutive matches" do
327
+ app do |r|
328
+ r.on do
329
+ "foo"
330
+ end
331
+
332
+ r.on do
333
+ "bar"
334
+ end
335
+ end
336
+
337
+ body.should == "foo"
338
+ end
339
+
340
+ it "finds first match available" do
341
+ app do |r|
342
+ r.on false do
343
+ "foo"
344
+ end
345
+
346
+ r.on do
347
+ "bar"
348
+ end
349
+ end
350
+
351
+ body.should == "bar"
352
+ end
353
+
354
+ it "reverts a half-met matcher" do
355
+ app do |r|
356
+ r.on "post", false do
357
+ "Should be unmet"
358
+ end
359
+
360
+ r.on do
361
+ r.env["SCRIPT_NAME"] + ':' + r.env["PATH_INFO"]
362
+ end
363
+ end
364
+
365
+ body("/hello").should == ':/hello'
366
+ end
367
+
368
+ it "doesn't write to body if body already written to" do
369
+ app do |r|
370
+ r.on do
371
+ response.write "a"
372
+ "b"
373
+ end
374
+ end
375
+
376
+ body.should == 'a'
377
+ end
378
+ end
379
+
380
+ describe "param! matcher" do
381
+ it "should yield a param only if given and not empty" do
382
+ app do |r|
383
+ r.get "signup", :param! => "email" do |email|
384
+ email
385
+ end
386
+
387
+ r.on do
388
+ "No email"
389
+ end
390
+ end
391
+
392
+ io = StringIO.new
393
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "email=john@doe.com").should == 'john@doe.com'
394
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "").should == 'No email'
395
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "email=").should == 'No email'
396
+ end
397
+ end
398
+
399
+ describe "param matcher" do
400
+ it "should yield a param only if given" do
401
+ app do |r|
402
+ r.get "signup", :param=>"email" do |email|
403
+ email
404
+ end
405
+
406
+ r.on do
407
+ "No email"
408
+ end
409
+ end
410
+
411
+ io = StringIO.new
412
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "email=john@doe.com").should == 'john@doe.com'
413
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "").should == 'No email'
414
+ body("/signup", "rack.input" => io, "QUERY_STRING" => "email=").should == ''
415
+ end
416
+ end
417
+
418
+ describe "path matchers" do
419
+ it "one level path" do
420
+ app do |r|
421
+ r.on "about" do
422
+ "About"
423
+ end
424
+ end
425
+
426
+ body('/about').should == "About"
427
+ status("/abot").should == 404
428
+ end
429
+
430
+ it "two level nested paths" do
431
+ app do |r|
432
+ r.on "about" do
433
+ r.on "1" do
434
+ "+1"
435
+ end
436
+
437
+ r.on "2" do
438
+ "+2"
439
+ end
440
+ end
441
+ end
442
+
443
+ body('/about/1').should == "+1"
444
+ body('/about/2').should == "+2"
445
+ status('/about/3').should == 404
446
+ end
447
+
448
+ it "two level inlined paths" do
449
+ app do |r|
450
+ r.on "a/b" do
451
+ "ab"
452
+ end
453
+ end
454
+
455
+ body('/a/b').should == "ab"
456
+ status('/a/d').should == 404
457
+ end
458
+
459
+ it "a path with some regex captures" do
460
+ app do |r|
461
+ r.on /user(\d+)/ do |uid|
462
+ uid
463
+ end
464
+ end
465
+
466
+ body('/user123').should == "123"
467
+ status('/useradf').should == 404
468
+ end
469
+
470
+ it "matching the root with a string" do
471
+ app do |r|
472
+ r.is "" do
473
+ "Home"
474
+ end
475
+ end
476
+
477
+ body.should == 'Home'
478
+ status("//").should == 404
479
+ status("/foo").should == 404
480
+ end
481
+
482
+ it "matching the root with the root method" do
483
+ app do |r|
484
+ r.root do
485
+ "Home"
486
+ end
487
+ end
488
+
489
+ body.should == 'Home'
490
+ status("//").should == 404
491
+ status("/foo").should == 404
492
+ end
493
+
494
+ it "matching the root with the root method and request method symbol" do
495
+ app do |r|
496
+ r.root(:get) do
497
+ "Home"
498
+ end
499
+ end
500
+
501
+ body.should == 'Home'
502
+ status("//").should == 404
503
+ status('REQUEST_METHOD'=>"POST").should == 404
504
+ end
505
+ end
506
+
507
+ describe "root/empty segment matching" do
508
+ it "matching an empty segment" do
509
+ app do |r|
510
+ r.on "" do
511
+ r.path
512
+ end
513
+ end
514
+
515
+ body.should == '/'
516
+ status("/foo").should == 404
517
+ end
518
+
519
+ it "nested empty segments" do
520
+ app do |r|
521
+ r.on "" do
522
+ r.on "" do
523
+ r.on "1" do
524
+ r.path
525
+ end
526
+ end
527
+ end
528
+ end
529
+
530
+ body("///1").should == '///1'
531
+ status("/1").should == 404
532
+ status("//1").should == 404
533
+ end
534
+
535
+ it "/events/? scenario" do
536
+ a = app do |r|
537
+ r.on "" do
538
+ "Hooray"
539
+ end
540
+
541
+ r.is do
542
+ "Foo"
543
+ end
544
+ end
545
+
546
+ app(:new) do |r|
547
+ r.on "events" do
548
+ r.run a
549
+ end
550
+ end
551
+
552
+ body("/events").should == 'Foo'
553
+ body("/events/").should == 'Hooray'
554
+ status("/events/foo").should == 404
555
+ end
556
+ end
557
+
558
+ describe "segment handling" do
559
+ before do
560
+ app do |r|
561
+ r.on "post" do
562
+ r.on :id do |id|
563
+ id
564
+ end
565
+ end
566
+ end
567
+ end
568
+
569
+ it "matches numeric ids" do
570
+ body('/post/1').should == '1'
571
+ end
572
+
573
+ it "matches decimal numbers" do
574
+ body('/post/1.1').should == '1.1'
575
+ end
576
+
577
+ it "matches slugs" do
578
+ body('/post/my-blog-post-about-cuba').should == 'my-blog-post-about-cuba'
579
+ end
580
+
581
+ it "matches only the first segment available" do
582
+ body('/post/one/two/three').should == 'one'
583
+ end
584
+ end
585
+
586
+ describe "request verb methods" do
587
+ it "executes if verb matches" do
588
+ app do |r|
589
+ r.get do
590
+ "g"
591
+ end
592
+ r.post do
593
+ "p"
594
+ end
595
+ end
596
+
597
+ body.should == 'g'
598
+ body('REQUEST_METHOD'=>'POST').should == 'p'
599
+ end
600
+
601
+ it "requires exact match if given arguments" do
602
+ app do |r|
603
+ r.get "" do
604
+ "g"
605
+ end
606
+ r.post "" do
607
+ "p"
608
+ end
609
+ end
610
+
611
+ body.should == 'g'
612
+ body('REQUEST_METHOD'=>'POST').should == 'p'
613
+ status("/a").should == 404
614
+ status("/a", 'REQUEST_METHOD'=>'POST').should == 404
615
+ end
616
+
617
+ it "does not require exact match if given arguments" do
618
+ app do |r|
619
+ r.get do
620
+ r.is "" do
621
+ "g"
622
+ end
623
+
624
+ "get"
625
+ end
626
+ r.post do
627
+ r.is "" do
628
+ "p"
629
+ end
630
+
631
+ "post"
632
+ end
633
+ end
634
+
635
+ body.should == 'g'
636
+ body('REQUEST_METHOD'=>'POST').should == 'p'
637
+ body("/a").should == 'get'
638
+ body("/a", 'REQUEST_METHOD'=>'POST').should == 'post'
639
+ end
640
+ end
641
+
642
+ describe "extension matcher" do
643
+ it "should match given file extensions" do
644
+ app do |r|
645
+ r.on "styles" do
646
+ r.on :extension=>"css" do |file|
647
+ file
648
+ end
649
+ end
650
+ end
651
+
652
+ body("/styles/reset.css").should == 'reset'
653
+ status("/styles/reset.bar").should == 404
654
+ end
655
+ end
656
+
657
+ describe "method matcher" do
658
+ it "should match given request types" do
659
+ app do |r|
660
+ r.is "", :method=>:get do
661
+ "foo"
662
+ end
663
+ r.is "", :method=>[:patch, :post] do
664
+ "bar"
665
+ end
666
+ end
667
+
668
+ body("REQUEST_METHOD"=>"GET").should == 'foo'
669
+ body("REQUEST_METHOD"=>"PATCH").should == 'bar'
670
+ body("REQUEST_METHOD"=>"POST").should == 'bar'
671
+ status("REQUEST_METHOD"=>"DELETE").should == 404
672
+ end
673
+ end
674
+
675
+ describe "route block that returns string" do
676
+ it "should be treated as if an on block returned string" do
677
+ app do |r|
678
+ "+1"
679
+ end
680
+
681
+ body.should == '+1'
682
+ end
683
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe "Roda.request_module and .response_module" do
4
+ it "should include given module in request or response class" do
5
+ app(:bare) do
6
+ request_module(Module.new{def h; halt response.finish end})
7
+ response_module(Module.new{def finish; [1, {}, []] end})
8
+
9
+ route do |r|
10
+ r.h
11
+ end
12
+ end
13
+
14
+ req.should == [1, {}, []]
15
+ end
16
+
17
+ it "should accept blocks and turn them into modules" do
18
+ app(:bare) do
19
+ request_module{def h; halt response.finish end}
20
+ response_module{def finish; [1, {}, []] end}
21
+
22
+ route do |r|
23
+ r.h
24
+ end
25
+ end
26
+
27
+ req.should == [1, {}, []]
28
+ end
29
+ end