roda 0.9.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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +3 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README.rdoc +709 -0
  5. data/Rakefile +124 -0
  6. data/lib/roda.rb +608 -0
  7. data/lib/roda/plugins/all_verbs.rb +48 -0
  8. data/lib/roda/plugins/default_headers.rb +50 -0
  9. data/lib/roda/plugins/error_handler.rb +69 -0
  10. data/lib/roda/plugins/flash.rb +62 -0
  11. data/lib/roda/plugins/h.rb +24 -0
  12. data/lib/roda/plugins/halt.rb +79 -0
  13. data/lib/roda/plugins/header_matchers.rb +57 -0
  14. data/lib/roda/plugins/hooks.rb +106 -0
  15. data/lib/roda/plugins/indifferent_params.rb +47 -0
  16. data/lib/roda/plugins/middleware.rb +88 -0
  17. data/lib/roda/plugins/multi_route.rb +77 -0
  18. data/lib/roda/plugins/not_found.rb +62 -0
  19. data/lib/roda/plugins/pass.rb +34 -0
  20. data/lib/roda/plugins/render.rb +217 -0
  21. data/lib/roda/plugins/streaming.rb +165 -0
  22. data/spec/composition_spec.rb +19 -0
  23. data/spec/env_spec.rb +11 -0
  24. data/spec/integration_spec.rb +63 -0
  25. data/spec/matchers_spec.rb +658 -0
  26. data/spec/module_spec.rb +29 -0
  27. data/spec/opts_spec.rb +42 -0
  28. data/spec/plugin/all_verbs_spec.rb +29 -0
  29. data/spec/plugin/default_headers_spec.rb +63 -0
  30. data/spec/plugin/error_handler_spec.rb +67 -0
  31. data/spec/plugin/flash_spec.rb +59 -0
  32. data/spec/plugin/h_spec.rb +13 -0
  33. data/spec/plugin/halt_spec.rb +62 -0
  34. data/spec/plugin/header_matchers_spec.rb +61 -0
  35. data/spec/plugin/hooks_spec.rb +97 -0
  36. data/spec/plugin/indifferent_params_spec.rb +13 -0
  37. data/spec/plugin/middleware_spec.rb +52 -0
  38. data/spec/plugin/multi_route_spec.rb +98 -0
  39. data/spec/plugin/not_found_spec.rb +99 -0
  40. data/spec/plugin/pass_spec.rb +23 -0
  41. data/spec/plugin/render_spec.rb +148 -0
  42. data/spec/plugin/streaming_spec.rb +52 -0
  43. data/spec/plugin_spec.rb +61 -0
  44. data/spec/redirect_spec.rb +24 -0
  45. data/spec/request_spec.rb +55 -0
  46. data/spec/response_spec.rb +131 -0
  47. data/spec/session_spec.rb +35 -0
  48. data/spec/spec_helper.rb +89 -0
  49. data/spec/version_spec.rb +8 -0
  50. metadata +148 -0
@@ -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
@@ -0,0 +1,42 @@
1
+ require File.expand_path("spec_helper", File.dirname(__FILE__))
2
+
3
+ describe "opts" do
4
+ it "is inheritable and allows overriding" do
5
+ c = Class.new(Roda)
6
+ c.opts[:foo] = "bar"
7
+ c.opts[:foo].should == "bar"
8
+
9
+ sc = Class.new(c)
10
+ sc.opts[:foo].should == "bar"
11
+
12
+ sc.opts[:foo] = "baz"
13
+ sc.opts[:foo].should == "baz"
14
+ c.opts[:foo].should == "bar"
15
+ end
16
+
17
+ it "should be available as an instance methods" do
18
+ app(:bare) do
19
+ opts[:hello] = "Hello World"
20
+
21
+ route do |r|
22
+ r.on do
23
+ opts[:hello]
24
+ end
25
+ end
26
+ end
27
+
28
+ body.should == "Hello World"
29
+ end
30
+
31
+ it "should only shallow clone by default" do
32
+ c = Class.new(Roda)
33
+ c.opts[:foo] = "bar"
34
+ c.opts[:foo].should == "bar"
35
+
36
+ sc = Class.new(c)
37
+ sc.opts[:foo].replace("baz")
38
+
39
+ sc.opts[:foo].should == "baz"
40
+ c.opts[:foo].should == "baz"
41
+ end
42
+ end
@@ -0,0 +1,29 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "all_verbs plugin" do
4
+ it "adds method for each http verb" do
5
+ app(:all_verbs) do |r|
6
+ r.delete{'d'}
7
+ r.head{'h'}
8
+ r.options{'o'}
9
+ r.patch{'pa'}
10
+ r.put{'pu'}
11
+ r.trace{'t'}
12
+ if Rack::Request.method_defined?(:link?)
13
+ r.link{'l'}
14
+ r.unlink{'u'}
15
+ end
16
+ end
17
+
18
+ body('REQUEST_METHOD'=>'DELETE').should == 'd'
19
+ body('REQUEST_METHOD'=>'HEAD').should == 'h'
20
+ body('REQUEST_METHOD'=>'OPTIONS').should == 'o'
21
+ body('REQUEST_METHOD'=>'PATCH').should == 'pa'
22
+ body('REQUEST_METHOD'=>'PUT').should == 'pu'
23
+ body('REQUEST_METHOD'=>'TRACE').should == 't'
24
+ if Rack::Request.method_defined?(:link?)
25
+ body('REQUEST_METHOD'=>'LINK').should == 'l'
26
+ body('REQUEST_METHOD'=>'UNLINK').should == 'u'
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,63 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "default_headers plugin" do
4
+ it "sets the default headers to use for the response" do
5
+ h = {'Content-Type'=>'text/json', 'Foo'=>'bar'}
6
+
7
+ app(:bare) do
8
+ plugin :default_headers, h
9
+ route do |r|
10
+ end
11
+ end
12
+
13
+ req[1].should == h
14
+ req[1].should_not equal(h)
15
+ end
16
+
17
+ it "should not override existing default headers" do
18
+ h = {'Content-Type'=>'text/json', 'Foo'=>'bar'}
19
+
20
+ app(:bare) do
21
+ plugin :default_headers, h
22
+ plugin :default_headers
23
+
24
+ route do |r|
25
+ end
26
+ end
27
+
28
+ req[1].should == h
29
+ end
30
+
31
+ it "should allow modifying the default headers at a later point" do
32
+ h = {'Content-Type'=>'text/json', 'Foo'=>'bar'}
33
+
34
+ app(:bare) do
35
+ plugin :default_headers
36
+ default_headers['Content-Type'] = 'text/json'
37
+ default_headers['Foo'] = 'bar'
38
+
39
+ route do |r|
40
+ end
41
+ end
42
+
43
+ req[1].should == h
44
+ end
45
+
46
+ it "should work correctly in subclasses" do
47
+ h = {'Content-Type'=>'text/json', 'Foo'=>'bar'}
48
+
49
+ app(:bare) do
50
+ plugin :default_headers
51
+ default_headers['Content-Type'] = 'text/json'
52
+ default_headers['Foo'] = 'bar'
53
+
54
+ route do |r|
55
+ end
56
+ end
57
+
58
+ @app = Class.new(@app)
59
+ @app.route{}
60
+
61
+ req[1].should == h
62
+ end
63
+ end
@@ -0,0 +1,67 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "error_handler plugin" do
4
+ it "executes only if error raised" do
5
+ app(:bare) do
6
+ plugin :error_handler
7
+
8
+ error do |e|
9
+ e.message
10
+ end
11
+
12
+ route do |r|
13
+ r.on "a" do
14
+ "found"
15
+ end
16
+
17
+ raise ArgumentError, "bad idea"
18
+ end
19
+ end
20
+
21
+ body("/a").should == 'found'
22
+ status("/a").should == 200
23
+ body.should == 'bad idea'
24
+ status.should == 500
25
+ end
26
+
27
+ it "can override status inside error block" do
28
+ app(:bare) do
29
+ plugin :error_handler do |e|
30
+ response.status = 501
31
+ e.message
32
+ end
33
+
34
+ route do |r|
35
+ raise ArgumentError, "bad idea"
36
+ end
37
+ end
38
+
39
+ status.should == 501
40
+ end
41
+
42
+ it "can set error via the plugin block" do
43
+ app(:bare) do
44
+ plugin :error_handler do |e|
45
+ e.message
46
+ end
47
+
48
+ route do |r|
49
+ raise ArgumentError, "bad idea"
50
+ end
51
+ end
52
+
53
+ body.should == 'bad idea'
54
+ end
55
+
56
+ it "has default error handler also raise" do
57
+ app(:bare) do
58
+ plugin :error_handler
59
+
60
+ route do |r|
61
+ raise ArgumentError, "bad idea"
62
+ end
63
+ end
64
+
65
+ proc{req}.should raise_error(ArgumentError)
66
+ end
67
+ end
@@ -0,0 +1,59 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ begin
4
+ require 'sinatra/flash/hash'
5
+ rescue LoadError
6
+ warn "sinatra-flash not installed, skipping flash plugin test"
7
+ else
8
+ describe "flash plugin" do
9
+ it "flash.now[] sets flash for current page" do
10
+ app(:bare) do
11
+ use Rack::Session::Cookie, :secret => "1"
12
+ plugin :flash
13
+
14
+ route do |r|
15
+ r.on do
16
+ flash.now['a'] = 'b'
17
+ flash['a']
18
+ end
19
+ end
20
+ end
21
+
22
+ body.should == 'b'
23
+ end
24
+
25
+ it "flash[] sets flash for next page" do
26
+ app(:bare) do
27
+ use Rack::Session::Cookie, :secret => "1"
28
+ plugin :flash
29
+
30
+ route do |r|
31
+ r.on 'a' do
32
+ "c#{flash['a']}"
33
+ end
34
+
35
+ r.on do
36
+ flash['a'] = "b#{flash['a']}"
37
+ flash['a'] || ''
38
+ end
39
+ end
40
+ end
41
+
42
+ env = proc{|h| h['Set-Cookie'] ? {'HTTP_COOKIE'=>h['Set-Cookie'].sub("; path=/; HttpOnly", '')} : {}}
43
+ _, h, b = req
44
+ b.join.should == ''
45
+ _, h, b = req(env[h])
46
+ b.join.should == 'b'
47
+ _, h, b = req(env[h])
48
+ b.join.should == 'bb'
49
+ _, h, b = req('/a', env[h])
50
+ b.join.should == 'cbbb'
51
+ _, h, b = req(env[h])
52
+ b.join.should == ''
53
+ _, h, b = req(env[h])
54
+ b.join.should == 'b'
55
+ _, h, b = req(env[h])
56
+ b.join.should == 'bb'
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,13 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "h plugin" do
4
+ it "adds h method for html escaping" do
5
+ app(:h) do |r|
6
+ r.on do
7
+ h("<form>") + h(:form)
8
+ end
9
+ end
10
+
11
+ body.should == '&lt;form&gt;form'
12
+ end
13
+ end
@@ -0,0 +1,62 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "halt plugin" do
4
+ it "should still have halt return rack response as argument given it as argument" do
5
+ app(:halt) do |r|
6
+ r.halt [200, {}, ['foo']]
7
+ end
8
+
9
+ body.should == "foo"
10
+ end
11
+
12
+ it "should consider string argument as response body" do
13
+ app(:halt) do |r|
14
+ r.halt "foo"
15
+ end
16
+
17
+ body.should == "foo"
18
+ end
19
+
20
+ it "should consider integer argument as response status" do
21
+ app(:halt) do |r|
22
+ r.halt 300
23
+ end
24
+
25
+ status.should == 300
26
+ end
27
+
28
+ it "should consider 2 arguments as response status and body" do
29
+ app(:halt) do |r|
30
+ r.halt 300, "foo"
31
+ end
32
+
33
+ status.should == 300
34
+ body.should == "foo"
35
+ end
36
+
37
+ it "should consider 3 arguments as response" do
38
+ app(:halt) do |r|
39
+ r.halt 300, {'a'=>'b'}, "foo"
40
+ end
41
+
42
+ status.should == 300
43
+ header('a').should == 'b'
44
+ body.should == "foo"
45
+ end
46
+
47
+ it "should raise an error for too many arguments" do
48
+ app(:halt) do |r|
49
+ r.halt 300, {'a'=>'b'}, "foo", 1
50
+ end
51
+
52
+ proc{req}.should raise_error(Roda::RodaError)
53
+ end
54
+
55
+ it "should raise an error for single argument not integer, String, or Array" do
56
+ app(:halt) do |r|
57
+ r.halt('a'=>'b')
58
+ end
59
+
60
+ proc{req}.should raise_error(Roda::RodaError)
61
+ end
62
+ end
@@ -0,0 +1,61 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "accept matcher" do
4
+ it "should accept mimetypes and set response Content-Type" do
5
+ app(:header_matchers) do |r|
6
+ r.on :accept=>"application/xml" do
7
+ response["Content-Type"]
8
+ end
9
+ end
10
+
11
+ body("HTTP_ACCEPT" => "application/xml").should == "application/xml"
12
+ status.should == 404
13
+ end
14
+ end
15
+
16
+ describe "header matcher" do
17
+ it "should match if header present" do
18
+ app(:header_matchers) do |r|
19
+ r.on :header=>"http-accept" do
20
+ "bar"
21
+ end
22
+ end
23
+
24
+ body("HTTP_ACCEPT" => "application/xml").should == "bar"
25
+ status.should == 404
26
+ end
27
+ end
28
+
29
+ describe "host matcher" do
30
+ it "should match a host" do
31
+ app(:header_matchers) do |r|
32
+ r.on :host=>"example.com" do
33
+ "worked"
34
+ end
35
+ end
36
+
37
+ body("HTTP_HOST" => "example.com").should == 'worked'
38
+ status("HTTP_HOST" => "foo.com").should == 404
39
+ end
40
+
41
+ it "should match a host with a regexp" do
42
+ app(:header_matchers) do |r|
43
+ r.on :host=>/example/ do
44
+ "worked"
45
+ end
46
+ end
47
+
48
+ body("HTTP_HOST" => "example.com").should == 'worked'
49
+ status("HTTP_HOST" => "foo.com").should == 404
50
+ end
51
+
52
+ it "doesn't yield HOST" do
53
+ app(:header_matchers) do |r|
54
+ r.on :host=>"example.com" do |*args|
55
+ args.size.to_s
56
+ end
57
+ end
58
+
59
+ body("HTTP_HOST" => "example.com").should == '0'
60
+ end
61
+ end
@@ -0,0 +1,97 @@
1
+ require File.expand_path("spec_helper", File.dirname(File.dirname(__FILE__)))
2
+
3
+ describe "hooks plugin" do
4
+ before do
5
+ a = @a = []
6
+ app(:bare) do
7
+ plugin :hooks
8
+
9
+ before do
10
+ response['foo'] = 'bar'
11
+ end
12
+
13
+ after do |r|
14
+ if r
15
+ a << [r[0], r[1]['foo'], r[2]]
16
+ r[0] += 1
17
+ else
18
+ a << r
19
+ end
20
+ end
21
+
22
+ route do |r|
23
+ r.is "" do
24
+ f = response['foo']
25
+ response['foo'] = 'baz'
26
+ f
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ it "adds before and after hooks for running code before and after requests" do
33
+ s, h, b = req
34
+ s.should == 201
35
+ h['foo'].should == 'baz'
36
+ b.join.should == 'bar'
37
+ @a.should == [[200, 'baz', ['bar']]]
38
+ end
39
+
40
+ it "multiple plugin calls do not override existing hooks" do
41
+ app.plugin :hooks
42
+ s, h, b = req
43
+ s.should == 201
44
+ h['foo'].should == 'baz'
45
+ b.join.should == 'bar'
46
+ @a.should == [[200, 'baz', ['bar']]]
47
+ end
48
+
49
+ it "after hooks are still called if an exception is raised" do
50
+ a = @a
51
+ @app.before do
52
+ raise Roda::RodaError, "foo"
53
+ end
54
+
55
+ @app.after do |r|
56
+ a << r
57
+ a << $!
58
+ end
59
+
60
+ proc{req}.should raise_error(Roda::RodaError)
61
+ a.pop.should be_a_kind_of(Roda::RodaError)
62
+ a.pop.should == nil
63
+ end
64
+
65
+ it "handles multiple before and after blocks correctly" do
66
+ a = @a
67
+ @app.before do
68
+ response['bar'] = "foo#{response['foo']}"
69
+ end
70
+
71
+ @app.after do |r|
72
+ a << r[1]['bar']
73
+ r[0] *= 2
74
+ end
75
+
76
+ s, h, b = req
77
+ s.should == 402
78
+ h['foo'].should == 'baz'
79
+ h['bar'].should == 'foo'
80
+ b.join.should == 'bar'
81
+ a.should == [[200, 'baz', ['bar']], 'foo']
82
+ end
83
+
84
+ it "copies before and after blocks when subclassing" do
85
+ @app = Class.new(@app)
86
+ @app.route do |r|
87
+ r.on do
88
+ "foo"
89
+ end
90
+ end
91
+ s, h, b = req
92
+ s.should == 201
93
+ h['foo'].should == 'bar'
94
+ b.join.should == 'foo'
95
+ @a.should == [[200, 'bar', ['foo']]]
96
+ end
97
+ end