roda 3.17.0 → 3.18.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.
- checksums.yaml +4 -4
- data/CHANGELOG +48 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +22 -4
- data/doc/release_notes/3.18.0.txt +170 -0
- data/lib/roda.rb +249 -26
- data/lib/roda/plugins/_after_hook.rb +4 -26
- data/lib/roda/plugins/_before_hook.rb +30 -2
- data/lib/roda/plugins/branch_locals.rb +2 -2
- data/lib/roda/plugins/class_level_routing.rb +9 -7
- data/lib/roda/plugins/default_headers.rb +15 -1
- data/lib/roda/plugins/default_status.rb +9 -10
- data/lib/roda/plugins/direct_call.rb +38 -0
- data/lib/roda/plugins/error_email.rb +1 -1
- data/lib/roda/plugins/error_handler.rb +37 -11
- data/lib/roda/plugins/hooks.rb +28 -30
- data/lib/roda/plugins/mail_processor.rb +16 -11
- data/lib/roda/plugins/mailer.rb +1 -1
- data/lib/roda/plugins/middleware.rb +13 -3
- data/lib/roda/plugins/multi_route.rb +3 -3
- data/lib/roda/plugins/named_templates.rb +4 -4
- data/lib/roda/plugins/path.rb +13 -8
- data/lib/roda/plugins/render.rb +2 -2
- data/lib/roda/plugins/route_block_args.rb +4 -3
- data/lib/roda/plugins/route_csrf.rb +9 -4
- data/lib/roda/plugins/sessions.rb +2 -1
- data/lib/roda/plugins/shared_vars.rb +1 -1
- data/lib/roda/plugins/static_routing.rb +7 -17
- data/lib/roda/plugins/status_handler.rb +5 -3
- data/lib/roda/plugins/view_options.rb +2 -2
- data/lib/roda/version.rb +1 -1
- data/spec/define_roda_method_spec.rb +257 -0
- data/spec/plugin/class_level_routing_spec.rb +0 -27
- data/spec/plugin/default_headers_spec.rb +7 -0
- data/spec/plugin/default_status_spec.rb +31 -1
- data/spec/plugin/direct_call_spec.rb +28 -0
- data/spec/plugin/error_handler_spec.rb +27 -0
- data/spec/plugin/hooks_spec.rb +21 -0
- data/spec/plugin/middleware_spec.rb +108 -36
- data/spec/plugin/multi_route_spec.rb +12 -0
- data/spec/plugin/route_csrf_spec.rb +27 -0
- data/spec/plugin/sessions_spec.rb +26 -1
- data/spec/plugin/static_routing_spec.rb +25 -3
- data/spec/plugin/status_handler_spec.rb +17 -0
- data/spec/route_spec.rb +39 -0
- data/spec/spec_helper.rb +2 -2
- metadata +9 -3
|
@@ -161,31 +161,4 @@ describe "class_level_routing plugin" do
|
|
|
161
161
|
|
|
162
162
|
proc{app.on{}}.must_raise
|
|
163
163
|
end
|
|
164
|
-
|
|
165
|
-
it 'works with route_block_args plugin' do
|
|
166
|
-
app(:bare) do
|
|
167
|
-
plugin :class_level_routing
|
|
168
|
-
plugin :route_block_args do
|
|
169
|
-
[request.path]
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
root do |path|
|
|
173
|
-
"root-#{path}"
|
|
174
|
-
end
|
|
175
|
-
|
|
176
|
-
get do |path|
|
|
177
|
-
"GET-#{path}"
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
route do |path|
|
|
181
|
-
request.get('foo') do
|
|
182
|
-
path
|
|
183
|
-
end
|
|
184
|
-
end
|
|
185
|
-
end
|
|
186
|
-
|
|
187
|
-
body.must_equal 'root-/'
|
|
188
|
-
body('/a').must_equal 'GET-/a'
|
|
189
|
-
body('/foo').must_equal '/foo'
|
|
190
|
-
end
|
|
191
164
|
end
|
|
@@ -72,4 +72,11 @@ describe "default_headers plugin" do
|
|
|
72
72
|
|
|
73
73
|
req[1].must_equal h
|
|
74
74
|
end
|
|
75
|
+
|
|
76
|
+
it "should offer default_headers method on class and response instance" do
|
|
77
|
+
h = {'Content-Type'=>'text/json', 'Foo'=>'bar'}
|
|
78
|
+
app.plugin :default_headers, h
|
|
79
|
+
app.default_headers.must_equal h
|
|
80
|
+
app::RodaResponse.new.default_headers.must_equal h
|
|
81
|
+
end
|
|
75
82
|
end
|
|
@@ -14,7 +14,7 @@ describe "default_status plugin" do
|
|
|
14
14
|
status.must_equal 201
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
it "should
|
|
17
|
+
it "should exec the plugin block in the context of the instance" do
|
|
18
18
|
app(:bare) do
|
|
19
19
|
plugin :default_status do
|
|
20
20
|
200 + @body[0].length
|
|
@@ -62,4 +62,34 @@ describe "default_status plugin" do
|
|
|
62
62
|
it "should raise if not given a block" do
|
|
63
63
|
proc{app(:default_status)}.must_raise Roda::RodaError
|
|
64
64
|
end
|
|
65
|
+
|
|
66
|
+
[true, false].each do |warn_arity|
|
|
67
|
+
send(warn_arity ? :deprecated : :it, "works with blocks with invalid arity") do
|
|
68
|
+
app(:bare) do
|
|
69
|
+
opts[:check_arity] = :warn if warn_arity
|
|
70
|
+
plugin :default_status do |r|
|
|
71
|
+
201
|
|
72
|
+
end
|
|
73
|
+
route do |r|
|
|
74
|
+
r.halt response.finish_with_body([])
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
status.must_equal 201
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
it "does not work with blocks with invalid arity if :check_arity app option is false" do
|
|
83
|
+
app(:bare) do
|
|
84
|
+
opts[:check_arity] = false
|
|
85
|
+
plugin :default_status do |r|
|
|
86
|
+
201
|
|
87
|
+
end
|
|
88
|
+
route do |r|
|
|
89
|
+
r.halt response.finish_with_body([])
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
proc{status}.must_raise ArgumentError
|
|
94
|
+
end
|
|
65
95
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
require_relative "../spec_helper"
|
|
2
|
+
|
|
3
|
+
describe "direct_call plugin" do
|
|
4
|
+
it "should have .call skip middleware" do
|
|
5
|
+
app{'123'}
|
|
6
|
+
app.use(Class.new do
|
|
7
|
+
def initialize(_) end
|
|
8
|
+
def call(env) [200, {}, ['321']] end
|
|
9
|
+
end)
|
|
10
|
+
body.must_equal '321'
|
|
11
|
+
app.plugin :direct_call
|
|
12
|
+
body.must_equal '123'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
deprecated "should work when #call is overridden" do
|
|
16
|
+
app.class_eval do
|
|
17
|
+
def call; super end
|
|
18
|
+
route{'123'}
|
|
19
|
+
end
|
|
20
|
+
app.use(Class.new do
|
|
21
|
+
def initialize(_) end
|
|
22
|
+
def call(env) [200, {}, ['321']] end
|
|
23
|
+
end)
|
|
24
|
+
body.must_equal '321'
|
|
25
|
+
app.plugin :direct_call
|
|
26
|
+
body.must_equal '123'
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -24,6 +24,33 @@ describe "error_handler plugin" do
|
|
|
24
24
|
status.must_equal 500
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
+
deprecated "works if #call is overridden" do
|
|
28
|
+
app(:bare) do
|
|
29
|
+
plugin :error_handler
|
|
30
|
+
|
|
31
|
+
def call
|
|
32
|
+
super
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
error do |e|
|
|
36
|
+
e.message
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
route do |r|
|
|
40
|
+
r.on "a" do
|
|
41
|
+
"found"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
raise ArgumentError, "bad idea"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
body("/a").must_equal 'found'
|
|
49
|
+
status("/a").must_equal 200
|
|
50
|
+
body.must_equal 'bad idea'
|
|
51
|
+
status.must_equal 500
|
|
52
|
+
end
|
|
53
|
+
|
|
27
54
|
it "executes on SyntaxError exceptions" do
|
|
28
55
|
app(:bare) do
|
|
29
56
|
plugin :error_handler
|
data/spec/plugin/hooks_spec.rb
CHANGED
|
@@ -44,6 +44,15 @@ describe "hooks plugin" do
|
|
|
44
44
|
@a.must_equal [[200, 'baz', ['bar']]]
|
|
45
45
|
end
|
|
46
46
|
|
|
47
|
+
it "works when freezing the app" do
|
|
48
|
+
app.freeze
|
|
49
|
+
s, h, b = req
|
|
50
|
+
s.must_equal 201
|
|
51
|
+
h['foo'].must_equal 'baz'
|
|
52
|
+
b.join.must_equal 'bar'
|
|
53
|
+
@a.must_equal [[200, 'baz', ['bar']]]
|
|
54
|
+
end
|
|
55
|
+
|
|
47
56
|
it "after hooks are still called if an exception is raised" do
|
|
48
57
|
a = @a
|
|
49
58
|
@app.before do
|
|
@@ -128,4 +137,16 @@ describe "hooks plugin" do
|
|
|
128
137
|
body('/a').must_equal "error"
|
|
129
138
|
body('/b').must_equal "error"
|
|
130
139
|
end
|
|
140
|
+
|
|
141
|
+
deprecated "should work if #call is overridden" do
|
|
142
|
+
app.class_eval do
|
|
143
|
+
def call; super end
|
|
144
|
+
end
|
|
145
|
+
app.route(&app.route_block)
|
|
146
|
+
s, h, b = req
|
|
147
|
+
s.must_equal 201
|
|
148
|
+
h['foo'].must_equal 'baz'
|
|
149
|
+
b.join.must_equal 'bar'
|
|
150
|
+
@a.must_equal [[200, 'baz', ['bar']]]
|
|
151
|
+
end
|
|
131
152
|
end
|
|
@@ -1,53 +1,62 @@
|
|
|
1
1
|
require_relative "../spec_helper"
|
|
2
2
|
|
|
3
3
|
describe "middleware plugin" do
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
4
|
+
[false, true].each do |def_call|
|
|
5
|
+
meth = def_call ? :deprecated : :it
|
|
6
|
+
send meth, "turns Roda app into middlware" do
|
|
7
|
+
a2 = app(:bare) do
|
|
8
|
+
plugin :middleware
|
|
9
|
+
|
|
10
|
+
if def_call
|
|
11
|
+
def call
|
|
12
|
+
super
|
|
13
|
+
end
|
|
11
14
|
end
|
|
12
|
-
|
|
13
|
-
|
|
15
|
+
|
|
16
|
+
route do |r|
|
|
17
|
+
r.is "a" do
|
|
18
|
+
"a2"
|
|
19
|
+
end
|
|
20
|
+
r.post "b" do
|
|
21
|
+
"b2"
|
|
22
|
+
end
|
|
14
23
|
end
|
|
15
24
|
end
|
|
16
|
-
end
|
|
17
25
|
|
|
18
|
-
|
|
19
|
-
|
|
26
|
+
a3 = app(:bare) do
|
|
27
|
+
plugin :middleware
|
|
20
28
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
route do |r|
|
|
30
|
+
r.get "a" do
|
|
31
|
+
"a3"
|
|
32
|
+
end
|
|
33
|
+
r.get "b" do
|
|
34
|
+
"b3"
|
|
35
|
+
end
|
|
27
36
|
end
|
|
28
37
|
end
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
app(:bare) do
|
|
32
|
-
use a3
|
|
33
|
-
use a2
|
|
34
38
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
39
|
+
app(:bare) do
|
|
40
|
+
use a3
|
|
41
|
+
use a2
|
|
42
|
+
|
|
43
|
+
route do |r|
|
|
44
|
+
r.is "a" do
|
|
45
|
+
"a1"
|
|
46
|
+
end
|
|
47
|
+
r.is "b" do
|
|
48
|
+
"b1"
|
|
49
|
+
end
|
|
41
50
|
end
|
|
42
51
|
end
|
|
43
|
-
end
|
|
44
52
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
body('/a').must_equal 'a3'
|
|
54
|
+
body('/b').must_equal 'b3'
|
|
55
|
+
body('/a', 'REQUEST_METHOD'=>'POST').must_equal 'a2'
|
|
56
|
+
body('/b', 'REQUEST_METHOD'=>'POST').must_equal 'b2'
|
|
57
|
+
body('/a', 'REQUEST_METHOD'=>'PATCH').must_equal 'a2'
|
|
58
|
+
body('/b', 'REQUEST_METHOD'=>'PATCH').must_equal 'b1'
|
|
59
|
+
end
|
|
51
60
|
end
|
|
52
61
|
|
|
53
62
|
it "makes it still possible to use the Roda app normally" do
|
|
@@ -57,6 +66,21 @@ describe "middleware plugin" do
|
|
|
57
66
|
body.must_equal 'a'
|
|
58
67
|
end
|
|
59
68
|
|
|
69
|
+
deprecated "makes it still possible to use the Roda app normally when #call is overwritten" do
|
|
70
|
+
app(:bare) do
|
|
71
|
+
plugin :middleware
|
|
72
|
+
def call
|
|
73
|
+
super
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
route do
|
|
77
|
+
"a"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
app
|
|
81
|
+
body.must_equal 'a'
|
|
82
|
+
end
|
|
83
|
+
|
|
60
84
|
it "makes middleware always use a subclass of the app" do
|
|
61
85
|
app(:middleware) do |r|
|
|
62
86
|
r.get{opts[:a]}
|
|
@@ -161,4 +185,52 @@ describe "middleware plugin" do
|
|
|
161
185
|
end
|
|
162
186
|
body.must_equal 'bazbar'
|
|
163
187
|
end
|
|
188
|
+
|
|
189
|
+
it "works with the route_block_args block when loaded before" do
|
|
190
|
+
app(:bare) do
|
|
191
|
+
plugin :middleware
|
|
192
|
+
plugin :route_block_args do
|
|
193
|
+
[request.path, response]
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
route do |path, res|
|
|
197
|
+
request.get 'a' do
|
|
198
|
+
res.write(path + '2')
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
a = app
|
|
203
|
+
|
|
204
|
+
app(:bare) do
|
|
205
|
+
use a
|
|
206
|
+
route{|r| 'b'}
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
body('/a').must_equal '/a2'
|
|
210
|
+
body('/x').must_equal 'b'
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
it "works with the route_block_args block when loaded after" do
|
|
214
|
+
app(:bare) do
|
|
215
|
+
plugin :route_block_args do
|
|
216
|
+
[request.path, response]
|
|
217
|
+
end
|
|
218
|
+
plugin :middleware
|
|
219
|
+
|
|
220
|
+
route do |path, res|
|
|
221
|
+
request.get 'a' do
|
|
222
|
+
res.write(path + '2')
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
a = app
|
|
227
|
+
|
|
228
|
+
app(:bare) do
|
|
229
|
+
use a
|
|
230
|
+
route{|r| 'b'}
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
body('/a').must_equal '/a2'
|
|
234
|
+
body('/x').must_equal 'b'
|
|
235
|
+
end
|
|
164
236
|
end
|
|
@@ -170,6 +170,18 @@ describe "multi_route plugin" do
|
|
|
170
170
|
end
|
|
171
171
|
end
|
|
172
172
|
|
|
173
|
+
describe "multi_route plugin" do
|
|
174
|
+
it "r.multi_route handles loading the same route more than once" do
|
|
175
|
+
app(:multi_route) do |r|
|
|
176
|
+
r.multi_route
|
|
177
|
+
end
|
|
178
|
+
app.route('foo'){'bar'}
|
|
179
|
+
body('/foo').must_equal 'bar'
|
|
180
|
+
app.route('foo'){'baz'}
|
|
181
|
+
body('/foo').must_equal 'baz'
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
173
185
|
describe "multi_route plugin" do
|
|
174
186
|
it "r.multi_route raises error for invalid namespace" do
|
|
175
187
|
app(:multi_route) do |r|
|
|
@@ -109,6 +109,25 @@ describe "route_csrf plugin" do
|
|
|
109
109
|
body("/foo", "REQUEST_METHOD"=>'POST', 'rack.input'=>StringIO.new).must_equal '/foo2'
|
|
110
110
|
end
|
|
111
111
|
|
|
112
|
+
it "allows plugin block to integrate with route_block_args plugin" do
|
|
113
|
+
app(:bare) do
|
|
114
|
+
send(*DEFAULT_SESSION_ARGS)
|
|
115
|
+
plugin :route_block_args do
|
|
116
|
+
[request, request.path, response]
|
|
117
|
+
end
|
|
118
|
+
plugin(:route_csrf){|r, path, res| res.write(path); res.write('2')}
|
|
119
|
+
route do |r|
|
|
120
|
+
check_csrf!
|
|
121
|
+
r.post('foo'){'f'}
|
|
122
|
+
r.get "token", String do |s|
|
|
123
|
+
csrf_token("/#{s}")
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
body("/foo", "REQUEST_METHOD"=>'POST', 'rack.input'=>StringIO.new("_csrf=#{Rack::Utils.escape(body("/token/foo"))}")).must_equal 'f'
|
|
128
|
+
body("/foo", "REQUEST_METHOD"=>'POST', 'rack.input'=>StringIO.new).must_equal '/foo2'
|
|
129
|
+
end
|
|
130
|
+
|
|
112
131
|
it "raises Error if configuring plugin with invalid :csrf_failure option" do
|
|
113
132
|
route_csrf_app(:csrf_failure=>:foo)
|
|
114
133
|
proc{body("/foo", "REQUEST_METHOD"=>'POST', 'rack.input'=>StringIO.new)}.must_raise Roda::RodaError
|
|
@@ -118,6 +137,14 @@ describe "route_csrf plugin" do
|
|
|
118
137
|
proc{route_csrf_app(:block=>proc{|r| r.path + '2'}, :csrf_failure=>:raise)}.must_raise Roda::RodaError
|
|
119
138
|
end
|
|
120
139
|
|
|
140
|
+
deprecated "supports check_csrf! :csrf_failure option as a Proc" do
|
|
141
|
+
pr = proc{env['BAD'] == '1' ? 't' : 'f'}
|
|
142
|
+
route_csrf_app{check_csrf!(:csrf_failure=>pr); ''}
|
|
143
|
+
token = body("/token/foo")
|
|
144
|
+
body("SKIP"=>"1", "BAD"=>'1', "REQUEST_METHOD"=>'POST', 'rack.input'=>StringIO.new("_csrf=#{Rack::Utils.escape(token)}")).must_equal 't'
|
|
145
|
+
body("SKIP"=>"1", "BAD"=>'0', "REQUEST_METHOD"=>'POST', 'rack.input'=>StringIO.new("_csrf=#{Rack::Utils.escape(token)}")).must_equal 'f'
|
|
146
|
+
end
|
|
147
|
+
|
|
121
148
|
it "supports valid_csrf? method" do
|
|
122
149
|
route_csrf_app{valid_csrf?.to_s}
|
|
123
150
|
body("/a", "REQUEST_METHOD"=>'POST', 'rack.input'=>StringIO.new("_csrf=#{Rack::Utils.escape(body("/token/a"))}")).must_equal 'true'
|
|
@@ -87,7 +87,32 @@ describe "sessions plugin" do
|
|
|
87
87
|
body('/g/foo').must_equal 'bar'
|
|
88
88
|
|
|
89
89
|
_, h, b = req('/sc')
|
|
90
|
-
|
|
90
|
+
|
|
91
|
+
# Parameters can come in any order, and only the final parameter may omit the ;
|
|
92
|
+
['roda.session=', 'max-age=0', 'path=/'].each do |param|
|
|
93
|
+
h['Set-Cookie'].must_match /#{Regexp.escape(param)}(;|\z)/
|
|
94
|
+
end
|
|
95
|
+
h['Set-Cookie'].must_match /expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/
|
|
96
|
+
|
|
97
|
+
b.must_equal ['c']
|
|
98
|
+
|
|
99
|
+
errors.must_equal []
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "removes session cookie even when max-age and expires are in cookie options" do
|
|
103
|
+
app.plugin :sessions, :cookie_options=>{:max_age=>'1000', :expires=>Time.now+1000}
|
|
104
|
+
body('/s/foo/bar').must_equal 'bar'
|
|
105
|
+
sct = body('/sct').to_i
|
|
106
|
+
body('/g/foo').must_equal 'bar'
|
|
107
|
+
|
|
108
|
+
_, h, b = req('/sc')
|
|
109
|
+
|
|
110
|
+
# Parameters can come in any order, and only the final parameter may omit the ;
|
|
111
|
+
['roda.session=', 'max-age=0', 'path=/'].each do |param|
|
|
112
|
+
h['Set-Cookie'].must_match /#{Regexp.escape(param)}(;|\z)/
|
|
113
|
+
end
|
|
114
|
+
h['Set-Cookie'].must_match /expires=Thu, 01 Jan 1970 00:00:00 (-0000|GMT)(;|\z)/
|
|
115
|
+
|
|
91
116
|
b.must_equal ['c']
|
|
92
117
|
|
|
93
118
|
errors.must_equal []
|