roda 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG +3 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +709 -0
- data/Rakefile +124 -0
- data/lib/roda.rb +608 -0
- data/lib/roda/plugins/all_verbs.rb +48 -0
- data/lib/roda/plugins/default_headers.rb +50 -0
- data/lib/roda/plugins/error_handler.rb +69 -0
- data/lib/roda/plugins/flash.rb +62 -0
- data/lib/roda/plugins/h.rb +24 -0
- data/lib/roda/plugins/halt.rb +79 -0
- data/lib/roda/plugins/header_matchers.rb +57 -0
- data/lib/roda/plugins/hooks.rb +106 -0
- data/lib/roda/plugins/indifferent_params.rb +47 -0
- data/lib/roda/plugins/middleware.rb +88 -0
- data/lib/roda/plugins/multi_route.rb +77 -0
- data/lib/roda/plugins/not_found.rb +62 -0
- data/lib/roda/plugins/pass.rb +34 -0
- data/lib/roda/plugins/render.rb +217 -0
- data/lib/roda/plugins/streaming.rb +165 -0
- data/spec/composition_spec.rb +19 -0
- data/spec/env_spec.rb +11 -0
- data/spec/integration_spec.rb +63 -0
- data/spec/matchers_spec.rb +658 -0
- data/spec/module_spec.rb +29 -0
- data/spec/opts_spec.rb +42 -0
- data/spec/plugin/all_verbs_spec.rb +29 -0
- data/spec/plugin/default_headers_spec.rb +63 -0
- data/spec/plugin/error_handler_spec.rb +67 -0
- data/spec/plugin/flash_spec.rb +59 -0
- data/spec/plugin/h_spec.rb +13 -0
- data/spec/plugin/halt_spec.rb +62 -0
- data/spec/plugin/header_matchers_spec.rb +61 -0
- data/spec/plugin/hooks_spec.rb +97 -0
- data/spec/plugin/indifferent_params_spec.rb +13 -0
- data/spec/plugin/middleware_spec.rb +52 -0
- data/spec/plugin/multi_route_spec.rb +98 -0
- data/spec/plugin/not_found_spec.rb +99 -0
- data/spec/plugin/pass_spec.rb +23 -0
- data/spec/plugin/render_spec.rb +148 -0
- data/spec/plugin/streaming_spec.rb +52 -0
- data/spec/plugin_spec.rb +61 -0
- data/spec/redirect_spec.rb +24 -0
- data/spec/request_spec.rb +55 -0
- data/spec/response_spec.rb +131 -0
- data/spec/session_spec.rb +35 -0
- data/spec/spec_helper.rb +89 -0
- data/spec/version_spec.rb +8 -0
- metadata +148 -0
data/spec/module_spec.rb
ADDED
@@ -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
|
data/spec/opts_spec.rb
ADDED
@@ -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 == '<form>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
|