roda-cj 0.9.1

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