roda 3.17.0 → 3.18.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +48 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +22 -4
  5. data/doc/release_notes/3.18.0.txt +170 -0
  6. data/lib/roda.rb +249 -26
  7. data/lib/roda/plugins/_after_hook.rb +4 -26
  8. data/lib/roda/plugins/_before_hook.rb +30 -2
  9. data/lib/roda/plugins/branch_locals.rb +2 -2
  10. data/lib/roda/plugins/class_level_routing.rb +9 -7
  11. data/lib/roda/plugins/default_headers.rb +15 -1
  12. data/lib/roda/plugins/default_status.rb +9 -10
  13. data/lib/roda/plugins/direct_call.rb +38 -0
  14. data/lib/roda/plugins/error_email.rb +1 -1
  15. data/lib/roda/plugins/error_handler.rb +37 -11
  16. data/lib/roda/plugins/hooks.rb +28 -30
  17. data/lib/roda/plugins/mail_processor.rb +16 -11
  18. data/lib/roda/plugins/mailer.rb +1 -1
  19. data/lib/roda/plugins/middleware.rb +13 -3
  20. data/lib/roda/plugins/multi_route.rb +3 -3
  21. data/lib/roda/plugins/named_templates.rb +4 -4
  22. data/lib/roda/plugins/path.rb +13 -8
  23. data/lib/roda/plugins/render.rb +2 -2
  24. data/lib/roda/plugins/route_block_args.rb +4 -3
  25. data/lib/roda/plugins/route_csrf.rb +9 -4
  26. data/lib/roda/plugins/sessions.rb +2 -1
  27. data/lib/roda/plugins/shared_vars.rb +1 -1
  28. data/lib/roda/plugins/static_routing.rb +7 -17
  29. data/lib/roda/plugins/status_handler.rb +5 -3
  30. data/lib/roda/plugins/view_options.rb +2 -2
  31. data/lib/roda/version.rb +1 -1
  32. data/spec/define_roda_method_spec.rb +257 -0
  33. data/spec/plugin/class_level_routing_spec.rb +0 -27
  34. data/spec/plugin/default_headers_spec.rb +7 -0
  35. data/spec/plugin/default_status_spec.rb +31 -1
  36. data/spec/plugin/direct_call_spec.rb +28 -0
  37. data/spec/plugin/error_handler_spec.rb +27 -0
  38. data/spec/plugin/hooks_spec.rb +21 -0
  39. data/spec/plugin/middleware_spec.rb +108 -36
  40. data/spec/plugin/multi_route_spec.rb +12 -0
  41. data/spec/plugin/route_csrf_spec.rb +27 -0
  42. data/spec/plugin/sessions_spec.rb +26 -1
  43. data/spec/plugin/static_routing_spec.rb +25 -3
  44. data/spec/plugin/status_handler_spec.rb +17 -0
  45. data/spec/route_spec.rb +39 -0
  46. data/spec/spec_helper.rb +2 -2
  47. 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 instance_exec the plugin block" do
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
@@ -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
- it "turns Roda app into middlware" do
5
- a2 = app(:bare) do
6
- plugin :middleware
7
-
8
- route do |r|
9
- r.is "a" do
10
- "a2"
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
- r.post "b" do
13
- "b2"
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
- a3 = app(:bare) do
19
- plugin :middleware
26
+ a3 = app(:bare) do
27
+ plugin :middleware
20
28
 
21
- route do |r|
22
- r.get "a" do
23
- "a3"
24
- end
25
- r.get "b" do
26
- "b3"
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
- route do |r|
36
- r.is "a" do
37
- "a1"
38
- end
39
- r.is "b" do
40
- "b1"
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
- body('/a').must_equal 'a3'
46
- body('/b').must_equal 'b3'
47
- body('/a', 'REQUEST_METHOD'=>'POST').must_equal 'a2'
48
- body('/b', 'REQUEST_METHOD'=>'POST').must_equal 'b2'
49
- body('/a', 'REQUEST_METHOD'=>'PATCH').must_equal 'a2'
50
- body('/b', 'REQUEST_METHOD'=>'PATCH').must_equal 'b1'
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
- h['Set-Cookie'].must_include "roda.session=; max-age=0; expires=Thu, 01 Jan 1970 00:00:00"
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 []