roda 3.2.0 → 3.3.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.
@@ -4,7 +4,7 @@ class Roda
4
4
  RodaMajorVersion = 3
5
5
 
6
6
  # The minor version of Roda, updated for new feature releases of Roda.
7
- RodaMinorVersion = 2
7
+ RodaMinorVersion = 3
8
8
 
9
9
  # The patch version of Roda, updated only for bug fixes from the last
10
10
  # feature release.
@@ -33,6 +33,24 @@ describe "public plugin" do
33
33
  body('/about/_test.erb').must_equal File.read('spec/views/about/_test.erb')
34
34
  end
35
35
 
36
+ it "keeps existing :root option if loaded a second time" do
37
+ app(:bare) do
38
+ plugin :public, :root=>'spec/views'
39
+ plugin :public
40
+
41
+ route do |r|
42
+ r.public
43
+ end
44
+ end
45
+
46
+ body('/about/_test.erb').must_equal File.read('spec/views/about/_test.erb')
47
+ end
48
+
49
+ it "assumes public directory as default :root option" do
50
+ app(:public){}
51
+ app.opts[:public_root].must_equal File.expand_path('public')
52
+ end
53
+
36
54
  it "handles serving gzip files in gzip mode if client supports gzip" do
37
55
  app(:bare) do
38
56
  plugin :public, :root=>'spec/views', :gzip=>true
@@ -0,0 +1,1215 @@
1
+ require_relative "../spec_helper"
2
+ require 'tempfile'
3
+
4
+ describe "typecast_params plugin" do
5
+ def tp(arg='a=1&b[]=2&b[]=3&c[d]=4&c[e]=5&f=&g[]=&h[i]=')
6
+ @tp.call(arg)
7
+ end
8
+
9
+ def error
10
+ yield
11
+ rescue @tp_error => e
12
+ e
13
+ end
14
+
15
+ before do
16
+ res = nil
17
+ app(:typecast_params) do |r|
18
+ res = typecast_params
19
+ nil
20
+ end
21
+
22
+ @tp = lambda do |params|
23
+ req('QUERY_STRING'=>params, 'rack.input'=>StringIO.new)
24
+ res
25
+ end
26
+
27
+ @tp_error = Roda::RodaPlugins::TypecastParams::Error
28
+ end
29
+
30
+ it ".new should raise error if params is not a hash" do
31
+ lambda{Roda::RodaPlugins::TypecastParams::Params.new('a')}.must_raise @tp_error
32
+ end
33
+
34
+ it ".new should raise for non String/Array args passed to conversion method" do
35
+ lambda{tp.any({})}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
36
+ lambda{tp.any(Object.new)}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
37
+ lambda{tp.any(:a)}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
38
+ end
39
+
40
+ it "#present should return whether the key is in the obj if given String" do
41
+ tp.present?('a').must_equal true
42
+ tp.present?('b').must_equal true
43
+ tp.present?('c').must_equal true
44
+ tp.present?('d').must_equal false
45
+ end
46
+
47
+ it "#present should return whether all keys are in the obj if given an Array" do
48
+ tp.present?(%w'a b c').must_equal true
49
+ tp.present?(%w'a b c d').must_equal false
50
+ end
51
+
52
+ it "#present should raise if given an unexpected object" do
53
+ lambda{tp.present?(:a)}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
54
+ lambda{tp.present?([:a])}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
55
+ lambda{tp.present?([['a']])}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
56
+ end
57
+
58
+ it "conversion methods should only support one level deep array of keys" do
59
+ lambda{tp.any([['a']])}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
60
+ end
61
+
62
+ it "#any should not do any conversion" do
63
+ tp.any('a').must_equal '1'
64
+ tp.any('b').must_equal ["2", "3"]
65
+ tp.any('c').must_equal('d'=>'4', 'e'=>'5')
66
+ tp.any('d').must_be_nil
67
+ tp.any(%w'g h').must_equal([[''], {'i'=>''}])
68
+
69
+ tp.any!('a').must_equal '1'
70
+ tp.any!(%w'a').must_equal %w'1'
71
+ lambda{tp.any!('d')}.must_raise @tp_error
72
+ lambda{tp.any!(%w'd j')}.must_raise @tp_error
73
+
74
+ lambda{tp.array(:any, 'a')}.must_raise @tp_error
75
+ tp.array(:any, 'b').must_equal ["2", "3"]
76
+ lambda{tp.array(:any, 'c')}.must_raise @tp_error
77
+ tp.array(:any, 'd').must_be_nil
78
+ tp.array(:any, %w'g').must_equal([['']])
79
+ lambda{tp.array(:any, 'h')}.must_raise @tp_error
80
+
81
+ tp.array!(:any, 'b').must_equal ["2", "3"]
82
+ lambda{tp.array!(:any, 'd')}.must_raise @tp_error
83
+ tp.array!(:any, %w'g').must_equal([['']])
84
+ end
85
+
86
+ it "#str should require strings" do
87
+ tp.str('a').must_equal '1'
88
+ lambda{tp.str('b')}.must_raise @tp_error
89
+ lambda{tp.str('c')}.must_raise @tp_error
90
+ lambda{tp.str(%w'b c')}.must_raise @tp_error
91
+ tp.str('d').must_be_nil
92
+ tp.str('f').must_equal ''
93
+ lambda{tp.str('g')}.must_raise @tp_error
94
+ lambda{tp.str('h')}.must_raise @tp_error
95
+
96
+ tp.str!('a').must_equal '1'
97
+ lambda{tp.str!('d')}.must_raise @tp_error
98
+ tp.str!('f').must_equal ''
99
+
100
+ lambda{tp.array(:str, 'a')}.must_raise @tp_error
101
+ tp.array(:str, 'b').must_equal ["2", "3"]
102
+ lambda{tp.array(:str, 'c')}.must_raise @tp_error
103
+ tp.array(:str, 'd').must_be_nil
104
+ tp.array(:str, 'g').must_equal [""]
105
+ lambda{tp.array(:str, 'h')}.must_raise @tp_error
106
+
107
+ tp.array!(:str, 'b').must_equal ["2", "3"]
108
+ lambda{tp.array!(:str, 'd')}.must_raise @tp_error
109
+ tp.array!(:str, 'g').must_equal [""]
110
+ end
111
+
112
+ it "#nonempty_str should require nonempty strings" do
113
+ tp.nonempty_str('a').must_equal '1'
114
+ tp('a=%201').nonempty_str('a').must_equal ' 1'
115
+ tp('a=1%20').nonempty_str('a').must_equal '1 '
116
+ tp('a=%201%20').nonempty_str('a').must_equal ' 1 '
117
+ tp('a=%20').nonempty_str('a').must_be_nil
118
+ lambda{tp.nonempty_str('b')}.must_raise @tp_error
119
+ lambda{tp.nonempty_str('c')}.must_raise @tp_error
120
+ tp.nonempty_str('d').must_be_nil
121
+ tp.nonempty_str('f').must_be_nil
122
+ lambda{tp.nonempty_str('g')}.must_raise @tp_error
123
+ lambda{tp.nonempty_str('h')}.must_raise @tp_error
124
+
125
+ tp.nonempty_str!('a').must_equal '1'
126
+ lambda{tp.nonempty_str!('d')}.must_raise @tp_error
127
+ lambda{tp.nonempty_str!('f')}.must_raise @tp_error
128
+
129
+ lambda{tp.array(:nonempty_str, 'a')}.must_raise @tp_error
130
+ tp.array(:nonempty_str, 'b').must_equal ["2", "3"]
131
+ lambda{tp.array(:nonempty_str, 'c')}.must_raise @tp_error
132
+ tp.array(:nonempty_str, 'd').must_be_nil
133
+ tp.array(:nonempty_str, 'g').must_equal [nil]
134
+ lambda{tp.array(:nonempty_str, 'h')}.must_raise @tp_error
135
+
136
+ tp.array!(:nonempty_str, 'b').must_equal ["2", "3"]
137
+ lambda{tp.array!(:nonempty_str, 'd')}.must_raise @tp_error
138
+ lambda{tp.array!(:nonempty_str, 'g')}.must_raise @tp_error
139
+ end
140
+
141
+ it "#bool should convert to boolean" do
142
+ tp('a=0').bool('a').must_equal false
143
+ tp('a=f').bool('a').must_equal false
144
+ tp('a=false').bool('a').must_equal false
145
+ tp('a=FALSE').bool('a').must_equal false
146
+ tp('a=F').bool('a').must_equal false
147
+ tp('a=n').bool('a').must_equal false
148
+ tp('a=no').bool('a').must_equal false
149
+ tp('a=N').bool('a').must_equal false
150
+ tp('a=NO').bool('a').must_equal false
151
+ tp('a=off').bool('a').must_equal false
152
+ tp('a=OFF').bool('a').must_equal false
153
+
154
+ tp('a=1').bool('a').must_equal true
155
+ tp('a=t').bool('a').must_equal true
156
+ tp('a=true').bool('a').must_equal true
157
+ tp('a=TRUE').bool('a').must_equal true
158
+ tp('a=T').bool('a').must_equal true
159
+ tp('a=y').bool('a').must_equal true
160
+ tp('a=yes').bool('a').must_equal true
161
+ tp('a=Y').bool('a').must_equal true
162
+ tp('a=YES').bool('a').must_equal true
163
+ tp('a=on').bool('a').must_equal true
164
+ tp('a=ON').bool('a').must_equal true
165
+
166
+ tp.bool('a').must_equal true
167
+ lambda{tp.bool('b')}.must_raise @tp_error
168
+ lambda{tp.bool('c')}.must_raise @tp_error
169
+ tp.bool('d').must_be_nil
170
+ tp.bool('f').must_be_nil
171
+
172
+ tp.bool!('a').must_equal true
173
+ lambda{tp.bool!('d')}.must_raise @tp_error
174
+ lambda{tp.bool!('f')}.must_raise @tp_error
175
+
176
+ lambda{tp.array(:bool, 'a')}.must_raise @tp_error
177
+ tp('b[]=1&b[]=0').array(:bool, 'b').must_equal [true, false]
178
+ lambda{tp('b[]=1&b[]=a').array(:bool, 'b')}.must_raise @tp_error
179
+ lambda{tp.array(:bool, 'c')}.must_raise @tp_error
180
+ tp.array(:bool, 'd').must_be_nil
181
+ tp.array(:bool, 'g').must_equal [nil]
182
+ lambda{tp.array(:bool, 'h')}.must_raise @tp_error
183
+
184
+ tp('b[]=1&b[]=0').array!(:bool, 'b').must_equal [true, false]
185
+ lambda{tp.array!(:bool, 'd')}.must_raise @tp_error
186
+ lambda{tp.array!(:bool, 'g')}.must_raise @tp_error
187
+ end
188
+
189
+ it "#int should convert to integer" do
190
+ tp('a=-1').int('a').must_equal -1
191
+ tp('a=0').int('a').must_equal 0
192
+ tp('a=a').int('a').must_equal 0
193
+ tp.int('a').must_equal 1
194
+ tp.int('a').must_be_kind_of Integer
195
+ lambda{tp.int('b')}.must_raise @tp_error
196
+ lambda{tp.int('c')}.must_raise @tp_error
197
+ tp.int('d').must_be_nil
198
+ tp.int('f').must_be_nil
199
+ lambda{tp.int('g')}.must_raise @tp_error
200
+ lambda{tp.int('h')}.must_raise @tp_error
201
+
202
+ tp.int!('a').must_equal 1
203
+ lambda{tp.int!('d')}.must_raise @tp_error
204
+ lambda{tp.int!('f')}.must_raise @tp_error
205
+
206
+ lambda{tp.array(:int, 'a')}.must_raise @tp_error
207
+ tp.array(:int, 'b').must_equal [2, 3]
208
+ lambda{tp.array(:int, 'c')}.must_raise @tp_error
209
+ tp.array(:int, 'd').must_be_nil
210
+ tp.array(:int, 'g').must_equal [nil]
211
+ lambda{tp.array(:int, 'h')}.must_raise @tp_error
212
+
213
+ tp.array!(:int, 'b').must_equal [2, 3]
214
+ lambda{tp.array!(:int, 'd')}.must_raise @tp_error
215
+ lambda{tp.array!(:int, 'g')}.must_raise @tp_error
216
+ end
217
+
218
+ it "#pos_int should convert to positive integer" do
219
+ tp('a=-1').pos_int('a').must_be_nil
220
+ tp('a=0').pos_int('a').must_be_nil
221
+ tp('a=a').pos_int('a').must_be_nil
222
+ tp.pos_int('a').must_equal 1
223
+ tp.pos_int('a').must_be_kind_of Integer
224
+ lambda{tp.pos_int('b')}.must_raise @tp_error
225
+ lambda{tp.pos_int('c')}.must_raise @tp_error
226
+ tp.pos_int('d').must_be_nil
227
+ tp.pos_int('f').must_be_nil
228
+ lambda{tp.pos_int('g')}.must_raise @tp_error
229
+ lambda{tp.pos_int('h')}.must_raise @tp_error
230
+
231
+ lambda{tp('a=-1').pos_int!('a')}.must_raise @tp_error
232
+ lambda{tp('a=0').pos_int!('a')}.must_raise @tp_error
233
+ lambda{tp('a=a').pos_int!('a')}.must_raise @tp_error
234
+ tp.pos_int!('a').must_equal 1
235
+ lambda{tp.pos_int!('d')}.must_raise @tp_error
236
+ lambda{tp.pos_int!('f')}.must_raise @tp_error
237
+
238
+ lambda{tp.array(:pos_int, 'a')}.must_raise @tp_error
239
+ tp.array(:pos_int, 'b').must_equal [2, 3]
240
+ lambda{tp.array(:pos_int, 'c')}.must_raise @tp_error
241
+ tp.array(:pos_int, 'd').must_be_nil
242
+ tp.array(:pos_int, 'g').must_equal [nil]
243
+ lambda{tp.array(:pos_int, 'h')}.must_raise @tp_error
244
+
245
+ tp.array!(:pos_int, 'b').must_equal [2, 3]
246
+ lambda{tp.array!(:pos_int, 'd')}.must_raise @tp_error
247
+ lambda{tp.array!(:pos_int, 'g')}.must_raise @tp_error
248
+ end
249
+
250
+ it "#Integer should convert to integer strictly" do
251
+ tp('a=-1').Integer('a').must_equal -1
252
+ tp('a=0').Integer('a').must_equal 0
253
+ lambda{tp('a=a').Integer('a')}.must_raise @tp_error
254
+ tp.Integer('a').must_equal 1
255
+ tp.Integer('a').must_be_kind_of Integer
256
+ lambda{tp.Integer('b')}.must_raise @tp_error
257
+ lambda{tp.Integer('c')}.must_raise @tp_error
258
+ tp.Integer('d').must_be_nil
259
+ tp.Integer('f').must_be_nil
260
+ lambda{tp.Integer('g')}.must_raise @tp_error
261
+ lambda{tp.Integer('h')}.must_raise @tp_error
262
+
263
+ tp.Integer!('a').must_equal 1
264
+ lambda{tp.Integer!('d')}.must_raise @tp_error
265
+ lambda{tp.Integer!('f')}.must_raise @tp_error
266
+
267
+ lambda{tp.array(:Integer, 'a')}.must_raise @tp_error
268
+ tp.array(:Integer, 'b').must_equal [2, 3]
269
+ lambda{tp.array(:Integer, 'c')}.must_raise @tp_error
270
+ tp.array(:Integer, 'd').must_be_nil
271
+ tp.array(:Integer, 'g').must_equal [nil]
272
+ lambda{tp.array(:Integer, 'h')}.must_raise @tp_error
273
+
274
+ tp.array!(:Integer, 'b').must_equal [2, 3]
275
+ lambda{tp.array!(:Integer, 'd')}.must_raise @tp_error
276
+ lambda{tp.array!(:Integer, 'g')}.must_raise @tp_error
277
+ end
278
+
279
+ it "#float should convert to float" do
280
+ tp('a=-1').float('a').must_equal -1
281
+ tp('a=0').float('a').must_equal 0
282
+ tp('a=a').float('a').must_equal 0
283
+ tp.float('a').must_equal 1
284
+ tp.float('a').must_be_kind_of Float
285
+ lambda{tp.float('b')}.must_raise @tp_error
286
+ lambda{tp.float('c')}.must_raise @tp_error
287
+ tp.float('d').must_be_nil
288
+ tp.float('f').must_be_nil
289
+ lambda{tp.float('g')}.must_raise @tp_error
290
+ lambda{tp.float('h')}.must_raise @tp_error
291
+
292
+ tp.float!('a').must_equal 1
293
+ lambda{tp.float!('d')}.must_raise @tp_error
294
+ lambda{tp.float!('f')}.must_raise @tp_error
295
+
296
+ lambda{tp.array(:float, 'a')}.must_raise @tp_error
297
+ tp.array(:float, 'b').must_equal [2, 3]
298
+ lambda{tp.array(:float, 'c')}.must_raise @tp_error
299
+ tp.array(:float, 'd').must_be_nil
300
+ tp.array(:float, 'g').must_equal [nil]
301
+ lambda{tp.array(:float, 'h')}.must_raise @tp_error
302
+
303
+ tp.array!(:float, 'b').must_equal [2, 3]
304
+ lambda{tp.array!(:float, 'd')}.must_raise @tp_error
305
+ lambda{tp.array!(:float, 'g')}.must_raise @tp_error
306
+ end
307
+
308
+ it "#Float should convert to float strictly" do
309
+ tp('a=-1').Float('a').must_equal -1
310
+ tp('a=0').Float('a').must_equal 0
311
+ lambda{tp('a=a').Float('a')}.must_raise @tp_error
312
+ tp.Float('a').must_equal 1
313
+ tp.Float('a').must_be_kind_of Float
314
+ lambda{tp.Float('b')}.must_raise @tp_error
315
+ lambda{tp.Float('c')}.must_raise @tp_error
316
+ tp.Float('d').must_be_nil
317
+ tp.Float('f').must_be_nil
318
+ lambda{tp.Float('g')}.must_raise @tp_error
319
+ lambda{tp.Float('h')}.must_raise @tp_error
320
+
321
+ tp.Float!('a').must_equal 1
322
+ lambda{tp.Float!('d')}.must_raise @tp_error
323
+ lambda{tp.Float!('f')}.must_raise @tp_error
324
+
325
+ lambda{tp.array(:Float, 'a')}.must_raise @tp_error
326
+ tp.array(:Float, 'b').must_equal [2, 3]
327
+ lambda{tp.array(:Float, 'c')}.must_raise @tp_error
328
+ tp.array(:Float, 'd').must_be_nil
329
+ tp.array(:Float, 'g').must_equal [nil]
330
+ lambda{tp.array(:Float, 'h')}.must_raise @tp_error
331
+
332
+ tp.array!(:Float, 'b').must_equal [2, 3]
333
+ lambda{tp.array!(:Float, 'd')}.must_raise @tp_error
334
+ lambda{tp.array!(:Float, 'g')}.must_raise @tp_error
335
+ end
336
+
337
+ it "#Hash should require hashes" do
338
+ lambda{tp.Hash('a')}.must_raise @tp_error
339
+ lambda{tp.Hash('b')}.must_raise @tp_error
340
+ tp.Hash('c').must_equal('d'=>'4', 'e'=>'5')
341
+ tp.Hash('d').must_be_nil
342
+ lambda{tp.Hash('f')}.must_raise @tp_error
343
+ lambda{tp.Hash('g')}.must_raise @tp_error
344
+ tp.Hash('h').must_equal('i'=>'')
345
+
346
+ tp.Hash!('c').must_equal('d'=>'4', 'e'=>'5')
347
+ lambda{tp.Hash!('d')}.must_raise @tp_error
348
+ tp.Hash!('h').must_equal('i'=>'')
349
+
350
+ lambda{tp.array(:Hash, 'c')}.must_raise @tp_error
351
+ lambda{tp('a[][b]=2&a[]=3').array(:Hash, 'a')}.must_raise @tp_error
352
+ tp('a[][b]=2&a[][b]=3').array(:Hash, 'a').must_equal [{'b'=>'2'}, {'b'=>'3'}]
353
+ tp.array(:Hash, 'd').must_be_nil
354
+
355
+ tp('a[][b]=2&a[][b]=3').array!(:Hash, 'a').must_equal [{'b'=>'2'}, {'b'=>'3'}]
356
+ lambda{tp.array!(:Hash, 'd')}.must_raise @tp_error
357
+ end
358
+
359
+ it "#Date should parse strings into Date instances" do
360
+ tp('a=').date('a').must_be_nil
361
+ tp('a=2017-10-11').date('a').must_equal Date.new(2017, 10, 11)
362
+ tp('a=17/10/11').date('a').must_equal Date.new(2017, 10, 11)
363
+ lambda{tp.date('b')}.must_raise @tp_error
364
+ lambda{tp('a=a').date('a')}.must_raise @tp_error
365
+
366
+ lambda{tp('a=').date!('a')}.must_raise @tp_error
367
+ tp('a=2017-10-11').date!('a').must_equal Date.new(2017, 10, 11)
368
+
369
+ tp('a[]=2017-10-11&a[]=2017-10-12').array(:date, 'a').must_equal [Date.new(2017, 10, 11), Date.new(2017, 10, 12)]
370
+ tp('a[]=2017-10-11&a[]=2017-10-12').array(:date, 'b').must_be_nil
371
+
372
+ tp('a[]=2017-10-11&a[]=2017-10-12').array!(:date, 'a').must_equal [Date.new(2017, 10, 11), Date.new(2017, 10, 12)]
373
+ lambda{tp('a[]=2017-10-11&a[]=a').array!(:date, 'a')}.must_raise @tp_error
374
+ lambda{tp('a[]=2017-10-11&a[]=2017-10-12').array!(:date, 'b')}.must_raise @tp_error
375
+ end
376
+
377
+ it "#Time should parse strings into Time instances" do
378
+ tp('a=').time('a').must_be_nil
379
+ tp('a=2017-10-11%2012:13:14').time('a').must_equal Time.local(2017, 10, 11, 12, 13, 14)
380
+ tp('a=17/10/11%2012:13:14').time('a').must_equal Time.local(2017, 10, 11, 12, 13, 14)
381
+ lambda{tp.time('b')}.must_raise @tp_error
382
+ lambda{tp('a=a').time('a')}.must_raise @tp_error
383
+
384
+ lambda{tp('a=').time!('a')}.must_raise @tp_error
385
+ tp('a=2017-10-11%2012:13:14').time!('a').must_equal Time.new(2017, 10, 11, 12, 13, 14)
386
+
387
+ tp('a[]=2017-10-11%2012:13:14&a[]=2017-10-12%2012:13:14').array(:time, 'a').must_equal [Time.local(2017, 10, 11, 12, 13, 14), Time.local(2017, 10, 12, 12, 13, 14)]
388
+ tp('a[]=2017-10-11%2012:13:14&a[]=2017-10-12%2012:13:14').array(:time, 'b').must_be_nil
389
+
390
+ tp('a[]=2017-10-11%2012:13:14&a[]=2017-10-12%2012:13:14').array!(:time, 'a').must_equal [Time.local(2017, 10, 11, 12, 13, 14), Time.local(2017, 10, 12, 12, 13, 14)]
391
+ lambda{tp('a[]=2017-10-11%2012:13:14&a[]=a').array!(:time, 'a')}.must_raise @tp_error
392
+ lambda{tp('a[]=2017-10-11%2012:13:14&a[]=2017-10-12%2012:13:14').array!(:time, 'b')}.must_raise @tp_error
393
+ end
394
+
395
+ it "#DateTime should parse strings into DateTime instances" do
396
+ tp('a=').datetime('a').must_be_nil
397
+ tp('a=2017-10-11%2012:13:14').datetime('a').must_equal DateTime.new(2017, 10, 11, 12, 13, 14)
398
+ tp('a=17/10/11%2012:13:14').datetime('a').must_equal DateTime.new(2017, 10, 11, 12, 13, 14)
399
+ lambda{tp.datetime('b')}.must_raise @tp_error
400
+ lambda{tp('a=a').datetime('a')}.must_raise @tp_error
401
+
402
+ lambda{tp('a=').datetime!('a')}.must_raise @tp_error
403
+ tp('a=2017-10-11%2012:13:14').datetime!('a').must_equal DateTime.new(2017, 10, 11, 12, 13, 14)
404
+
405
+ tp('a[]=2017-10-11%2012:13:14&a[]=2017-10-12%2012:13:14').array(:datetime, 'a').must_equal [DateTime.new(2017, 10, 11, 12, 13, 14), DateTime.new(2017, 10, 12, 12, 13, 14)]
406
+ tp('a[]=2017-10-11%2012:13:14&a[]=2017-10-12%2012:13:14').array(:datetime, 'b').must_be_nil
407
+
408
+ tp('a[]=2017-10-11%2012:13:14&a[]=2017-10-12%2012:13:14').array!(:datetime, 'a').must_equal [DateTime.new(2017, 10, 11, 12, 13, 14), DateTime.new(2017, 10, 12, 12, 13, 14)]
409
+ lambda{tp('a[]=2017-10-11%2012:13:14&a[]=a').array!(:datetime, 'a')}.must_raise @tp_error
410
+ lambda{tp('a[]=2017-10-11%2012:13:14&a[]=2017-10-12%2012:13:14').array!(:datetime, 'b')}.must_raise @tp_error
411
+ end
412
+
413
+ it "#array should handle defaults" do
414
+ tp = tp('b[]=1&c[]=')
415
+ tp.array(:int, 'b', [2]).must_equal [1]
416
+ tp.array(:int, 'c', [2]).must_equal [nil]
417
+ tp.array(:int, 'd', []).must_equal []
418
+ tp.array(:int, 'e', [1]).must_equal [1]
419
+
420
+ tp('b[]=1&c[]=').array(:int, %w'b c', [2]).must_equal [[1], [nil]]
421
+ tp('b[]=1&c[]=').array(:int, %w'b d', [2]).must_equal [[1], [2]]
422
+ end
423
+
424
+ it "#array! should handle defaults" do
425
+ tp = tp('b[]=1&c[]=')
426
+ tp.array!(:int, 'b', [2]).must_equal [1]
427
+ lambda{tp.array!(:int, 'c', [2])}.must_raise @tp_error
428
+ tp.array!(:int, 'd', []).must_equal []
429
+ tp.array!(:int, 'e', [1]).must_equal [1]
430
+
431
+ lambda{tp('b[]=1&c[]=').array!(:int, %w'b c', [2])}.must_raise @tp_error
432
+ tp('b[]=1&c[]=').array!(:int, %w'b d', [2]).must_equal [[1], [2]]
433
+ end
434
+
435
+ it "#array should handle key arrays" do
436
+ tp('b[]=1&c[]=2').array(:int, %w'b c').must_equal [[1], [2]]
437
+ tp('b[]=1&c[]=').array(:int, %w'b c').must_equal [[1], [nil]]
438
+ end
439
+
440
+ it "#array! should handle key arrays" do
441
+ tp('b[]=1&c[]=2').array!(:int, %w'b c').must_equal [[1], [2]]
442
+ lambda{tp('b[]=1&c[]=').array!(:int, %w'b c')}.must_raise @tp_error
443
+ end
444
+
445
+ it "#[] should access nested values" do
446
+ tp['c'].must_be_kind_of tp.class
447
+ tp['c'].int('d').must_equal 4
448
+ tp['c'].int('e').must_equal 5
449
+ tp['c'].int(%w'd e').must_equal [4, 5]
450
+ end
451
+
452
+ it "#[] should handle deeply nested structures" do
453
+ tp('a[b][c][d][e]=1')['a']['b']['c']['d'].int('e').must_equal 1
454
+ tp('a[][b][][e]=1')['a'][0]['b'][0].int('e').must_equal 1
455
+ end
456
+
457
+ it "#[] should raise error for non-Array/Hash parameters" do
458
+ lambda{tp['a']}.must_raise @tp_error
459
+ end
460
+
461
+ it "#[] should raise error for accessing hash with integer value (thinking it is an array)" do
462
+ lambda{tp[1]}.must_raise @tp_error
463
+ end
464
+
465
+ it "#[] should raise error for accessing array with non-integer value non-Array/Hash parameters" do
466
+ lambda{tp['b']['a']}.must_raise @tp_error
467
+ end
468
+
469
+ it "#convert! should return a hash of converted parameters" do
470
+ tp = tp()
471
+ tp.convert! do |ptp|
472
+ ptp.int!('a')
473
+ ptp.array!(:int, 'b')
474
+ ptp['c'].convert! do |stp|
475
+ stp.int!(%w'd e')
476
+ end
477
+ end.must_equal("a"=>1, "b"=>[2, 3], "c"=>{"d"=>4, "e"=>5})
478
+ end
479
+
480
+ it "#convert! hash should only include changes made inside block" do
481
+ tp = tp()
482
+ tp.convert! do |ptp|
483
+ ptp.int!('a')
484
+ ptp.array!(:int, 'b')
485
+ end.must_equal("a"=>1, "b"=>[2, 3])
486
+
487
+ tp.convert! do |ptp|
488
+ ptp['c'].convert! do |stp|
489
+ stp.int!(%w'd e')
490
+ end
491
+ end.must_equal("c"=>{"d"=>4, "e"=>5})
492
+ end
493
+
494
+ it "#convert! should handle deeply nested structures" do
495
+ tp = tp('a[b][c][d][e]=1')
496
+ tp.convert! do |tp0|
497
+ tp0['a'].convert! do |tp1|
498
+ tp1['b'].convert! do |tp2|
499
+ tp2['c'].convert! do |tp3|
500
+ tp3['d'].convert! do |tp4|
501
+ tp4.int('e')
502
+ end
503
+ end
504
+ end
505
+ end
506
+ end.must_equal('a'=>{'b'=>{'c'=>{'d'=>{'e'=>1}}}})
507
+
508
+ tp = tp('a[][b][][e]=1')
509
+ tp.convert! do |tp0|
510
+ tp0['a'].convert! do |tp1|
511
+ tp1[0].convert! do |tp2|
512
+ tp2['b'].convert! do |tp3|
513
+ tp3[0].convert! do |tp4|
514
+ tp4.int('e')
515
+ end
516
+ end
517
+ end
518
+ end
519
+ end.must_equal('a'=>[{'b'=>[{'e'=>1}]}])
520
+ end
521
+
522
+ it "#convert! should handle #[] without #convert! at each level" do
523
+ tp = tp('a[b][c][d][e]=1')
524
+ tp.convert! do |tp0|
525
+ tp0['a'].convert! do |tp1|
526
+ tp1['b']['c']['d'].convert! do |tp4|
527
+ tp4.int('e')
528
+ end
529
+ end
530
+ end.must_equal('a'=>{'b'=>{'c'=>{'d'=>{'e'=>1}}}})
531
+ end
532
+
533
+ it "#convert! should handle #[] without #convert! below" do
534
+ tp = tp('a[b][c][d][e]=1')
535
+ tp.convert! do |tp0|
536
+ tp0['a']['b']['c']['d'].int('e')
537
+ end.must_equal('a'=>{'b'=>{'c'=>{'d'=>{'e'=>1}}}})
538
+ end
539
+
540
+ it "#convert! should handle multiple calls to #[] and #convert! below" do
541
+ tp = tp('a[b]=2&a[c]=3&a[d]=4&a[e]=5&a[f]=6')
542
+ tp.convert! do |tp0|
543
+ tp0['a'].int('b')
544
+ tp0['a'].convert! do |tp1|
545
+ tp1.int('c')
546
+ end
547
+ tp0['a'].int('d')
548
+ tp0['a'].convert! do |tp1|
549
+ tp1.int('e')
550
+ end
551
+ tp0['a'].int('f')
552
+ end.must_equal('a'=>{'b'=>2, 'c'=>3, 'd'=>4, 'e'=>5, 'f'=>6})
553
+ end
554
+
555
+ it "#convert! should handle defaults" do
556
+ tp.convert! do |tp0|
557
+ tp0.int('d', 12)
558
+ end.must_equal('d'=>12)
559
+
560
+ tp.convert! do |tp0|
561
+ tp0.int(%w'a d', 12)
562
+ end.must_equal('a'=>1, 'd'=>12)
563
+
564
+ tp.convert! do |tp0|
565
+ tp0.array(:int, 'g', [])
566
+ end.must_equal('g'=>[nil])
567
+
568
+ tp.convert! do |tp0|
569
+ tp0.array(:int, 'j', [])
570
+ end.must_equal('j'=>[])
571
+
572
+ tp('a[]=1&g[]=').convert! do |tp0|
573
+ tp0.array(:int, %w'a d g', [2])
574
+ end.must_equal('a'=>[1], 'd'=>[2], 'g'=>[nil])
575
+ end
576
+
577
+ it "#convert_each! should convert each entry in an array" do
578
+ tp = tp('a[][b]=1&a[][c]=2&a[][b]=3&a[][c]=4')
579
+ tp['a'].convert_each! do |tp0|
580
+ tp0.int(%w'b c')
581
+ end.must_equal [{'b'=>1, 'c'=>2}, {'b'=>3, 'c'=>4}]
582
+ end
583
+
584
+ it "#convert_each! with :keys option should convert each named entry in a hash" do
585
+ tp = tp('a[0][b]=1&a[0][c]=2&a[1][b]=3&a[1][c]=4')
586
+ tp['a'].convert_each!(:keys=>%w'0 1') do |tp0|
587
+ tp0.int(%w'b c')
588
+ end.must_equal [{'b'=>1, 'c'=>2}, {'b'=>3, 'c'=>4}]
589
+ end
590
+
591
+ it "#convert_each! with :keys option should store entries when called inside convert" do
592
+ tp('a[0][b]=1&a[0][c]=2&a[1][b]=3&a[1][c]=4').convert! do |tp|
593
+ tp['a'].convert_each!(:keys=>%w'0 1') do |tp0|
594
+ tp0.int(%w'b c')
595
+ end
596
+ end.must_equal("a"=>{"0"=>{'b'=>1, 'c'=>2}, "1"=>{'b'=>3, 'c'=>4}})
597
+ end
598
+
599
+ it "#convert_each! should raise if obj is not an array" do
600
+ lambda{tp.convert_each!{}}.must_raise @tp_error
601
+ end
602
+
603
+ it "#convert_each! should raise if obj is a array of non-hashes" do
604
+ lambda{tp['b'].convert_each!{}}.must_raise @tp_error
605
+ end
606
+
607
+ it "#convert! with :symbolize option should return a hash of converted parameters" do
608
+ tp = tp()
609
+ tp.convert!(:symbolize=>true) do |ptp|
610
+ ptp.int!('a')
611
+ ptp.array!(:int, 'b')
612
+ ptp['c'].convert! do |stp|
613
+ stp.int!(%w'd e')
614
+ end
615
+ end.must_equal(:a=>1, :b=>[2, 3], :c=>{:d=>4, :e=>5})
616
+ end
617
+
618
+ it "#convert! with :symbolize option hash should only include changes made inside block" do
619
+ tp = tp()
620
+ tp.convert!(:symbolize=>true) do |ptp|
621
+ ptp.int!('a')
622
+ ptp.array!(:int, 'b')
623
+ end.must_equal(:a=>1, :b=>[2, 3])
624
+
625
+ tp.convert!(:symbolize=>true) do |ptp|
626
+ ptp['c'].convert! do |stp|
627
+ stp.int!(%w'd e')
628
+ end
629
+ end.must_equal(:c=>{:d=>4, :e=>5})
630
+ end
631
+
632
+ it "#convert! with :symbolize option should handle deeply nested structures" do
633
+ tp = tp('a[b][c][d][e]=1')
634
+ tp.convert!(:symbolize=>true) do |tp0|
635
+ tp0['a'].convert! do |tp1|
636
+ tp1['b'].convert! do |tp2|
637
+ tp2['c'].convert! do |tp3|
638
+ tp3['d'].convert! do |tp4|
639
+ tp4.int('e')
640
+ end
641
+ end
642
+ end
643
+ end
644
+ end.must_equal(:a=>{:b=>{:c=>{:d=>{:e=>1}}}})
645
+
646
+ tp = tp('a[][b][][e]=1')
647
+ tp.convert!(:symbolize=>true) do |tp0|
648
+ tp0['a'].convert! do |tp1|
649
+ tp1[0].convert! do |tp2|
650
+ tp2['b'].convert! do |tp3|
651
+ tp3[0].convert! do |tp4|
652
+ tp4.int('e')
653
+ end
654
+ end
655
+ end
656
+ end
657
+ end.must_equal(:a=>[{:b=>[{:e=>1}]}])
658
+ end
659
+
660
+ it "#convert! with :symbolize option should handle #[] without #convert! at each level" do
661
+ tp = tp('a[b][c][d][e]=1')
662
+ tp.convert!(:symbolize=>true) do |tp0|
663
+ tp0['a'].convert! do |tp1|
664
+ tp1['b']['c']['d'].convert! do |tp4|
665
+ tp4.int('e')
666
+ end
667
+ end
668
+ end.must_equal(:a=>{:b=>{:c=>{:d=>{:e=>1}}}})
669
+ end
670
+
671
+ it "#convert! with :symbolize option should handle #[] without #convert! below" do
672
+ tp = tp('a[b][c][d][e]=1')
673
+ tp.convert!(:symbolize=>true) do |tp0|
674
+ tp0['a']['b']['c']['d'].int('e')
675
+ end.must_equal(:a=>{:b=>{:c=>{:d=>{:e=>1}}}})
676
+ end
677
+
678
+ it "#convert! with :symbolize option should handle multiple calls to #[] and #convert! below" do
679
+ tp = tp('a[b]=2&a[c]=3&a[d]=4&a[e]=5&a[f]=6')
680
+ tp.convert!(:symbolize=>true) do |tp0|
681
+ tp0['a'].int('b')
682
+ tp0['a'].convert! do |tp1|
683
+ tp1.int('c')
684
+ end
685
+ tp0['a'].int('d')
686
+ tp0['a'].convert! do |tp1|
687
+ tp1.int('e')
688
+ end
689
+ tp0['a'].int('f')
690
+ end.must_equal(:a=>{:b=>2, :c=>3, :d=>4, :e=>5, :f=>6})
691
+ end
692
+
693
+ it "#convert! with :symbolize option should handle defaults" do
694
+ tp.convert!(:symbolize=>true) do |tp0|
695
+ tp0.int('d', 12)
696
+ end.must_equal(:d=>12)
697
+
698
+ tp.convert!(:symbolize=>true) do |tp0|
699
+ tp0.int(%w'a d', 12)
700
+ end.must_equal(:a=>1, :d=>12)
701
+
702
+ tp.convert!(:symbolize=>true) do |tp0|
703
+ tp0.array(:int, 'g', [])
704
+ end.must_equal(:g=>[nil])
705
+
706
+ tp.convert!(:symbolize=>true) do |tp0|
707
+ tp0.array(:int, 'j', [])
708
+ end.must_equal(:j=>[])
709
+
710
+ tp('a[]=1&g[]=').convert!(:symbolize=>true) do |tp0|
711
+ tp0.array(:int, %w'a d g', [2])
712
+ end.must_equal(:a=>[1], :d=>[2], :g=>[nil])
713
+ end
714
+
715
+ it "#convert_each! with :symbolize option should convert each entry in an array" do
716
+ tp = tp('a[][b]=1&a[][c]=2&a[][b]=3&a[][c]=4')
717
+ tp['a'].convert_each!(:symbolize=>true) do |tp0|
718
+ tp0.int(%w'b c')
719
+ end.must_equal [{:b=>1, :c=>2}, {:b=>3, :c=>4}]
720
+ end
721
+
722
+ it "#convert_each! with :symbolize and :keys options should convert each named entry in a hash" do
723
+ tp = tp('a[0][b]=1&a[0][c]=2&a[1][b]=3&a[1][c]=4')
724
+ tp['a'].convert_each!(:keys=>%w'0 1', :symbolize=>true) do |tp0|
725
+ tp0.int(%w'b c')
726
+ end.must_equal [{:b=>1, :c=>2}, {:b=>3, :c=>4}]
727
+ end
728
+
729
+ it "#convert_each! with :symbolize and :keys options should store entries when called inside convert" do
730
+ tp('a[0][b]=1&a[0][c]=2&a[1][b]=3&a[1][c]=4').convert!(:symbolize=>true) do |tp|
731
+ tp['a'].convert_each!(:keys=>%w'0 1') do |tp0|
732
+ tp0.int(%w'b c')
733
+ end
734
+ end.must_equal(:a=>{:'0'=>{:b=>1, :c=>2}, :'1'=>{:b=>3, :c=>4}})
735
+ end
736
+
737
+ it "#convert! with :symbolize options specified at different levels should work" do
738
+ tp = tp('a[b][c][d][e]=1')
739
+ tp.convert!(:symbolize=>true) do |tp0|
740
+ tp0['a'].convert!(:symbolize=>false) do |tp1|
741
+ tp1['b'].convert!(:symbolize=>true) do |tp2|
742
+ tp2['c'].convert!(:symbolize=>false) do |tp3|
743
+ tp3['d'].convert!(:symbolize=>true) do |tp4|
744
+ tp4.int('e')
745
+ end
746
+ end
747
+ end
748
+ end
749
+ end.must_equal(:a=>{'b'=>{:c=>{'d'=>{:e=>1}}}})
750
+
751
+ tp = tp('a[][b][][e]=1')
752
+ tp.convert!(:symbolize=>true) do |tp0|
753
+ tp0['a'].convert! do |tp1|
754
+ tp1[0].convert!(:symbolize=>false) do |tp2|
755
+ tp2['b'].convert! do |tp3|
756
+ tp3[0].convert!(:symbolize=>true) do |tp4|
757
+ tp4.int('e')
758
+ end
759
+ end
760
+ end
761
+ end
762
+ end.must_equal(:a=>[{'b'=>[{:e=>1}]}])
763
+ end
764
+
765
+ it "#dig should return nested values or nil if there is no value" do
766
+ tp = tp('a[b][c][d][e]=1&b=2')
767
+ tp.dig(:int, 'a', 'b', 'c', 'd', 'e').must_equal 1
768
+ tp.dig(:int, 'b').must_equal 2
769
+ tp.dig(:int, 'a', 0, 'c', 'd', 'e').must_be_nil
770
+ tp.dig(:int, 'a', 'd', 'c', 'd', 'e').must_be_nil
771
+ tp.dig(:int, 'a', 'b', 'c', 'd', 'f').must_be_nil
772
+ tp.dig(:int, 'c').must_be_nil
773
+ tp.dig(:int, 'c', 'd').must_be_nil
774
+
775
+ tp = tp('a[][c][][e]=1')
776
+ tp.dig(:int, 'a', 0, 'c', 0, 'e').must_equal 1
777
+ tp.dig(:int, 'a', 1, 'c', 0, 'e').must_be_nil
778
+ tp.dig(:int, 'a', 'b', 'c', 0, 'e').must_be_nil
779
+ tp.dig(:int, 'a', 0, 'c', 0, 'f').must_be_nil
780
+ end
781
+
782
+ it "#dig should raise when accessing past the end of the expected structure" do
783
+ tp = tp('a[b][c][d][e]=1&b=2')
784
+ lambda{tp.dig(:int, 'a', 'b', 'c', 'd', 'e', 'f')}.must_raise @tp_error
785
+ lambda{tp.dig(:int, 'b', 'c')}.must_raise @tp_error
786
+
787
+ tp = tp('a[][c][][e]=1')
788
+ lambda{tp.dig(:int, 'a', 0, 'c', 0, 'e', 'f')}.must_raise @tp_error
789
+ end
790
+
791
+ it "#dig and #dig! should handle array keys" do
792
+ tp('a[b][c][d][e]=1&a[b][c][d][f]=2').dig(:int, 'a', 'b', 'c', 'd', %w'e f').must_equal [1, 2]
793
+ tp('a[b][c][d][e]=1&a[b][c][d][f]=').dig(:int, 'a', 'b', 'c', 'd', %w'e f').must_equal [1, nil]
794
+
795
+ tp('a[b][c][d][e]=1&a[b][c][d][f]=2').dig!(:int, 'a', 'b', 'c', 'd', %w'e f').must_equal [1, 2]
796
+ lambda{tp('a[b][c][d][e]=1&a[b][c][d][f]=').dig!(:int, 'a', 'b', 'c', 'd', %w'e f')}.must_raise @tp_error
797
+ end
798
+
799
+ it "#dig and #dig! should be able to handle arrays using an array for the type" do
800
+ tp('a[b][c][d][]=1&a[b][c][d][]=2').dig(:array, :int, 'a', 'b', 'c', 'd').must_equal [1, 2]
801
+ tp('a[b][c][d][]=1&a[b][c][d][]=').dig(:array, :int, 'a', 'b', 'c', 'd').must_equal [1, nil]
802
+
803
+ tp('a[b][c][d][]=1&a[b][c][d][]=2').dig!(:array!, :int, 'a', 'b', 'c', 'd').must_equal [1, 2]
804
+ lambda{tp('a[b][c][d][]=1&a[b][c][d][]=').dig!(:array!, :int, 'a', 'b', 'c', 'd')}.must_raise @tp_error
805
+ end
806
+
807
+ it "#dig should raise for unsupported types" do
808
+ lambda{tp.dig(:foo, 'a')}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
809
+ end
810
+
811
+ it "#dig should raise for array without subtype" do
812
+ lambda{tp.dig(:array, 'foo', 'a')}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
813
+ end
814
+
815
+
816
+ it "#dig should raise for unsupported nest values" do
817
+ lambda{tp.dig(:int, :foo, 'a')}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
818
+ lambda{tp.dig(:array, :int, :foo, 'a')}.must_raise Roda::RodaPlugins::TypecastParams::ProgrammerError
819
+ end
820
+
821
+ it "#dig! should return nested values or raise Error if thers is no value" do
822
+ tp = tp('a[b][c][d][e]=1&b=2')
823
+ tp.dig!(:int, 'a', 'b', 'c', 'd', 'e').must_equal 1
824
+ tp.dig!(:int, 'b').must_equal 2
825
+ lambda{tp.dig!(:int, 'a', 'd', 'c', 'd', 'e')}.must_raise @tp_error
826
+ lambda{tp.dig!(:int, 'a', 'b', 'c', 'd', 'f')}.must_raise @tp_error
827
+ lambda{tp.dig!(:int, 'a', 0, 'c', 'd', 'f')}.must_raise @tp_error
828
+ lambda{tp.dig!(:int, 'c')}.must_raise @tp_error
829
+ lambda{tp.dig!(:int, 'b', 'c')}.must_raise @tp_error
830
+ lambda{tp.dig!(:int, 'c', 'd')}.must_raise @tp_error
831
+ error{tp.dig!(:int, 'a', 'd', 'c', 'd', 'e')}.keys.must_equal %w'a d'
832
+ error{tp.dig!(:int, 'a', 'b', 'c', 'e', 'e')}.keys.must_equal %w'a b c e'
833
+
834
+ tp = tp('a[][c][][e]=1')
835
+ tp.dig!(:int, 'a', 0, 'c', 0, 'e').must_equal 1
836
+ lambda{tp.dig!(:int, 'a', 1, 'c', 0, 'e')}.must_raise @tp_error
837
+ lambda{tp.dig!(:int, 'a', 'b', 'c', 0, 'e')}.must_raise @tp_error
838
+ lambda{tp.dig!(:int, 'a', 0, 'c', 0, 'f')}.must_raise @tp_error
839
+ end
840
+
841
+ it "#convert! should work with dig" do
842
+ tp('a[b][c][d][e]=1').convert! do |tp|
843
+ tp.dig(:int, 'a', 'b', 'c', 'd', 'e')
844
+ end.must_equal('a'=>{'b'=>{'c'=>{'d'=>{'e'=>1}}}})
845
+ end
846
+
847
+ it "#convert! with :symbolize option should work with dig" do
848
+ tp('a[b][c][d][e]=1').convert!(:symbolize=>true) do |tp|
849
+ tp.dig(:int, 'a', 'b', 'c', 'd', 'e')
850
+ end.must_equal(:a=>{:b=>{:c=>{:d=>{:e=>1}}}})
851
+ end
852
+
853
+ it "#fetch should be the same as #[] if the key is present" do
854
+ tp.fetch('c').int('d').must_equal 4
855
+ end
856
+
857
+ it "#fetch should return nil if the key is not present and no block is given" do
858
+ tp.fetch('d').must_be_nil
859
+ end
860
+
861
+ it "#fetch should call the block if the key is not present and a block is given" do
862
+ tp.fetch('d'){1}.must_equal 1
863
+ end
864
+
865
+ it "Error#keys should be a path to the error" do
866
+ error{tp.int!('b')}.keys.must_equal ['b']
867
+ error{tp.int!(%w'b f')}.keys.must_equal ['b']
868
+ error{tp['c'].int!('f')}.keys.must_equal ['c', 'f']
869
+ error{tp('a[b][c][d][e]=1')['a']['b']['c']['d'].date('e')}.keys.must_equal %w'a b c d e'
870
+ end
871
+
872
+ it "Error#param_name should be the name of the parameter" do
873
+ error{tp.int!('b')}.param_name.must_equal 'b'
874
+ error{tp.int!(%w'b f')}.param_name.must_equal 'b'
875
+ error{tp['c'].int!('f')}.param_name.must_equal 'c[f]'
876
+ error{tp('a[b][c][d][e]=1')['a']['b']['c']['d'].date('e')}.param_name.must_equal 'a[b][c][d][e]'
877
+ error{tp('a[][c][][e]=1')['a'][0]['c'][0].date('e')}.param_name.must_equal 'a[][c][][e]'
878
+ error{tp('a[][c][][e]=1').dig(:date, 'a', 0, 'c', 0, 'e')}.param_name.must_equal 'a[][c][][e]'
879
+ end
880
+
881
+ it "Error#param_names and #reason should be correct for errors" do
882
+ e = error{tp.int!('b')}
883
+ e.param_names.must_equal ['b']
884
+ e.reason.must_equal :int
885
+
886
+ e = error{tp.int!(%w'b f')}
887
+ e.param_names.must_equal ['b']
888
+ e.reason.must_equal :int
889
+
890
+ e = error{tp['c'].int!('f')}
891
+ e.param_names.must_equal ['c[f]']
892
+ e.reason.must_equal :missing
893
+
894
+ e = error{tp('a[b][c][d][e]=1')['a']['b']['c']['d'].date('e')}
895
+ e.param_names.must_equal ['a[b][c][d][e]']
896
+ e.reason.must_equal :date
897
+
898
+ e = error{tp('a[][c][][e]=1')['a'][0]['c'][0].date('e')}
899
+ e.param_names.must_equal ['a[][c][][e]']
900
+ e.reason.must_equal :date
901
+
902
+ e = error{tp('a[][c][][e]=1').dig(:date, 'a', 0, 'c', 0, 'e')}
903
+ e.param_names.must_equal ['a[][c][][e]']
904
+ e.reason.must_equal :date
905
+
906
+ e = error{tp('a[][c][][e]=1').dig!(:date, 'a', 1, 'c', 0, 'e')}
907
+ e.param_names.must_equal ['a[]']
908
+ e.reason.must_equal :missing
909
+
910
+ e = error{tp('a[][c][][e]=1').dig!(:date, 'a', 'b', 'c', 0, 'e')}
911
+ e.param_names.must_equal ['a[b]']
912
+ e.reason.must_equal :invalid_type
913
+ end
914
+
915
+ it "Error#param_names and #all_errors should handle array submission" do
916
+ tp = tp('a[][b]=0')
917
+ e = error do
918
+ tp.convert!('a') do |tp0|
919
+ tp0.int(%w'a b c')
920
+ tp0.array(:int, %w'a b c')
921
+ end
922
+ end
923
+ e.param_names.must_equal %w'a'
924
+ e.all_errors.map(&:reason).must_equal [:invalid_type]
925
+ end
926
+
927
+ it "Error#param_names and #all_errors should include all errors raised in convert! blocks" do
928
+ tp = tp('a[][b][][e]=0')
929
+ e = error do
930
+ tp.convert! do |tp0|
931
+ tp0['a'].convert! do |tp1|
932
+ tp1[0].convert! do |tp2|
933
+ tp2['b'].convert! do |tp3|
934
+ tp3[0].convert! do |tp4|
935
+ tp4.pos_int!('e')
936
+ end
937
+ end
938
+ end
939
+ end
940
+ tp0.dig!(:pos_int, 'a', 0, 'b', 0, %w'f g')
941
+ tp0.dig!(:pos_int, 'a', 0, 'b')
942
+ tp0.int!('c')
943
+ tp0.array!(:int, %w'd e')
944
+ tp0['b']
945
+ end
946
+ end
947
+ e.param_names.must_equal %w'a[][b][][e] a[][b][][f] a[][b][][g] a[][b] c d e b'
948
+ e.all_errors.map(&:reason).must_equal [:missing, :missing, :missing, :pos_int, :missing, :missing, :missing, :missing]
949
+ end
950
+
951
+ it "Error#param_names and #all_errors should handle #[] failures by skipping the rest of the block" do
952
+ tp = tp('a[][b][][e]=0')
953
+ e = error do
954
+ tp.convert! do |tp0|
955
+ tp0['b']
956
+ tp0.int!('c')
957
+ end
958
+ end
959
+ e.param_names.must_equal %w'b'
960
+ e.all_errors.map(&:reason).must_equal [:missing]
961
+
962
+ e = error do
963
+ tp.convert! do |tp0|
964
+ tp0['a'][0].convert! do |tp1|
965
+ tp1['c']
966
+ tp1.int!('d')
967
+ end
968
+ tp0.int!('c')
969
+ end
970
+ end
971
+ e.param_names.must_equal %w'a[][c] c'
972
+ e.all_errors.map(&:reason).must_equal [:missing, :missing]
973
+ end
974
+
975
+ it "Error#param_names and #all_errorsshould handle array! with array of keys where one of the keys is not present" do
976
+ e = error do
977
+ tp('e[]=0').convert! do |tp0|
978
+ tp0.array!(:pos_int, %w'd e')
979
+ end
980
+ end
981
+ e.param_names.must_equal %w'd e'
982
+ e.all_errors.map(&:reason).must_equal [:missing, :invalid_type]
983
+ end
984
+
985
+ it "Error#param_names and #all_errors should handle keys given to convert" do
986
+ tp = tp('e[][b][][e]=0')
987
+ e = error do
988
+ tp.convert! do |tp0|
989
+ tp0.convert!(['a', 0, 'b', 0]) do |tp1|
990
+ tp1.pos_int!('e')
991
+ end
992
+ tp0.convert!('f') do |tp1|
993
+ tp1.dig!(:pos_int, 0, 'b', 0, %w'f g')
994
+ end
995
+ tp0.dig!(:pos_int, 'e', 0, 'b')
996
+ tp0.int!('c')
997
+ tp0.array!(:int, 'd')
998
+ end
999
+ end
1000
+ e.param_names.must_equal %w'a f e[][b] c d'
1001
+ e.all_errors.map(&:reason).must_equal [:missing, :missing, :pos_int, :missing, :missing]
1002
+ end
1003
+
1004
+ it "Error#param_names and #all_errors should include all errors raised in convert_each! blocks" do
1005
+ e = error do
1006
+ tp('a[][b]=0&a[][b]=1')['a'].convert_each! do |tp0|
1007
+ tp0.dig!(:pos_int, 'b', 0, 'e')
1008
+ tp0.dig!(:int, 'b', 0, %w'f g')
1009
+ tp0.int!(%w'd e')
1010
+ tp0.pos_int!('b')
1011
+ tp0['c']
1012
+ end
1013
+ end
1014
+ e.param_names.must_equal %w'a[][b] a[][b] a[][d] a[][e] a[][b] a[][c] a[][b] a[][b] a[][d] a[][e] a[][c]'
1015
+ e.all_errors.map(&:reason).must_equal [:invalid_type, :invalid_type, :missing, :missing, :missing, :missing, :invalid_type, :invalid_type, :missing, :missing, :missing]
1016
+ end
1017
+
1018
+ it "Error#param_names and #all_errors should include all errors for invalid keys used in convert_each!" do
1019
+ tp = tp('a[0][b]=1&a[0][c]=2&a[1][b]=3&a[1][c]=4')
1020
+ e = error do
1021
+ tp['a'].convert_each!(:keys=>%w'0 2 3') do |tp0|
1022
+ tp0.int(%w'b c')
1023
+ end
1024
+ end
1025
+ e.param_names.must_equal %w'a[2] a[3]'
1026
+ e.all_errors.map(&:reason).must_equal [:missing, :missing]
1027
+ end
1028
+ end
1029
+
1030
+ describe "typecast_params plugin with customized params" do
1031
+ def tp(arg='a=1&b[]=2&b[]=3&c[d]=4&c[e]=5&f=&g[]=&h[i]=')
1032
+ @tp.call(arg)
1033
+ end
1034
+
1035
+ before do
1036
+ res = nil
1037
+ app(:bare) do
1038
+ plugin :typecast_params do
1039
+ handle_type(:opp_int) do |v|
1040
+ -v.to_i
1041
+ end
1042
+ end
1043
+ plugin :typecast_params do
1044
+ handle_type(:double) do |v|
1045
+ v*2
1046
+ end
1047
+ end
1048
+
1049
+ route do |r|
1050
+ res = typecast_params
1051
+ nil
1052
+ end
1053
+ end
1054
+
1055
+ @tp = lambda do |params|
1056
+ req('QUERY_STRING'=>params, 'rack.input'=>StringIO.new)
1057
+ res
1058
+ end
1059
+
1060
+ @tp_error = Roda::RodaPlugins::TypecastParams::Error
1061
+ end
1062
+
1063
+ it "should not allow typecast params changes after freezing the app" do
1064
+ app.freeze
1065
+ lambda{app::TypecastParams.handle_type(:foo){|v| v}}.must_raise RuntimeError
1066
+ end
1067
+
1068
+ it "should pass through non-ArgumentError exceptions raised by conversion blocks" do
1069
+ app::TypecastParams.handle_type(:foo){|v| raise}
1070
+ lambda{tp.foo('a')}.must_raise RuntimeError
1071
+ end
1072
+
1073
+ it "should respect custom typecasting methods" do
1074
+ tp.opp_int('a').must_equal -1
1075
+ tp.opp_int!('a').must_equal -1
1076
+ tp.opp_int('d').must_be_nil
1077
+ lambda{tp.opp_int!('d')}.must_raise @tp_error
1078
+
1079
+ tp.array(:opp_int, 'b').must_equal [-2, -3]
1080
+ tp.array!(:opp_int, 'b').must_equal [-2, -3]
1081
+
1082
+ tp.double('a').must_equal '11'
1083
+ tp.double!('a').must_equal '11'
1084
+ tp.double('d').must_be_nil
1085
+ lambda{tp.double!('d')}.must_raise @tp_error
1086
+
1087
+ tp.array(:double, 'b').must_equal ['22', '33']
1088
+ tp.array!(:double, 'b').must_equal ['22', '33']
1089
+ end
1090
+
1091
+ it "should respect custom typecasting methods when subclassing" do
1092
+ @app = Class.new(@app)
1093
+ @app.plugin :typecast_params do
1094
+ handle_type :triple do |v|
1095
+ v * 3
1096
+ end
1097
+ end
1098
+
1099
+ tp.opp_int('a').must_equal -1
1100
+ tp.opp_int!('a').must_equal -1
1101
+ tp.opp_int('d').must_be_nil
1102
+ lambda{tp.opp_int!('d')}.must_raise @tp_error
1103
+
1104
+ tp.array(:opp_int, 'b').must_equal [-2, -3]
1105
+ tp.array!(:opp_int, 'b').must_equal [-2, -3]
1106
+
1107
+ tp.double('a').must_equal '11'
1108
+ tp.double!('a').must_equal '11'
1109
+ tp.double('d').must_be_nil
1110
+ lambda{tp.double!('d')}.must_raise @tp_error
1111
+
1112
+ tp.array(:double, 'b').must_equal ['22', '33']
1113
+ tp.array!(:double, 'b').must_equal ['22', '33']
1114
+
1115
+ tp.triple('a').must_equal '111'
1116
+ tp.triple!('a').must_equal '111'
1117
+ tp.triple('d').must_be_nil
1118
+ lambda{tp.triple!('d')}.must_raise @tp_error
1119
+
1120
+ tp.array(:triple, 'b').must_equal ['222', '333']
1121
+ tp.array!(:triple, 'b').must_equal ['222', '333']
1122
+ end
1123
+ end
1124
+
1125
+ describe "typecast_params plugin with files" do
1126
+ def tp
1127
+ @tp.call
1128
+ end
1129
+
1130
+ before do
1131
+ tempfile = @tempfile = Tempfile.new(['roda_typecast_params_spec', '.txt'])
1132
+ tempfile.write('tp_spec')
1133
+ tempfile.rewind
1134
+ res = nil
1135
+ app(:typecast_params) do |r|
1136
+ res = typecast_params
1137
+ nil
1138
+ end
1139
+ app::RodaRequest.send(:define_method, :params) do
1140
+ {'testfile'=>{:tempfile=>tempfile}, 'testfile2'=>{:tempfile=>tempfile},
1141
+ 'testfile_array'=>[{:tempfile=>tempfile}, {:tempfile=>tempfile}],
1142
+ 'a'=>{'b'=>'c', 'tempfile'=>'f'},
1143
+ 'c'=>['']}
1144
+ end
1145
+
1146
+ @tp = lambda do
1147
+ req
1148
+ res
1149
+ end
1150
+
1151
+ @tp_error = Roda::RodaPlugins::TypecastParams::Error
1152
+ end
1153
+
1154
+ it "#file should require an uploaded file" do
1155
+ tp.file('testfile').must_equal(:tempfile=>@tempfile)
1156
+ tp.file(%w'testfile testfile2').must_equal [{:tempfile=>@tempfile}, {:tempfile=>@tempfile}]
1157
+
1158
+ lambda{tp.file('a')}.must_raise @tp_error
1159
+ tp.file('b').must_be_nil
1160
+ lambda{tp.file('c')}.must_raise @tp_error
1161
+
1162
+ tp.file!('testfile').must_equal(:tempfile=>@tempfile)
1163
+ tp.file!(%w'testfile testfile2').must_equal [{:tempfile=>@tempfile}, {:tempfile=>@tempfile}]
1164
+ lambda{tp.file!('a')}.must_raise @tp_error
1165
+ lambda{tp.file!('b')}.must_raise @tp_error
1166
+ lambda{tp.file!('c')}.must_raise @tp_error
1167
+
1168
+ tp.array(:file, 'testfile_array').must_equal [{:tempfile=>@tempfile}, {:tempfile=>@tempfile}]
1169
+ lambda{tp.array(:file, 'testfile')}.must_raise @tp_error
1170
+ lambda{tp.array(:file, 'a')}.must_raise @tp_error
1171
+ tp.array(:file, 'b').must_be_nil
1172
+ lambda{tp.array(:file, 'c')}.must_raise @tp_error
1173
+
1174
+ tp.array!(:file, 'testfile_array').must_equal [{:tempfile=>@tempfile}, {:tempfile=>@tempfile}]
1175
+ lambda{tp.array!(:file, 'testfile')}.must_raise @tp_error
1176
+ lambda{tp.array!(:file, 'a')}.must_raise @tp_error
1177
+ lambda{tp.array!(:file, 'b')}.must_raise @tp_error
1178
+ lambda{tp.array!(:file, 'c')}.must_raise @tp_error
1179
+ end
1180
+ end
1181
+
1182
+ describe "typecast_params plugin with strip: :all option" do
1183
+ def tp(arg='a=+1+')
1184
+ @tp.call(arg)
1185
+ end
1186
+
1187
+
1188
+ before do
1189
+ res = nil
1190
+ app(:bare) do
1191
+ plugin :typecast_params, strip: :all
1192
+ route do |r|
1193
+ res = typecast_params
1194
+ nil
1195
+ end
1196
+ end
1197
+
1198
+ @tp = lambda do |params|
1199
+ req('QUERY_STRING'=>params, 'rack.input'=>StringIO.new)
1200
+ res
1201
+ end
1202
+
1203
+ @tp_error = Roda::RodaPlugins::TypecastParams::Error
1204
+ end
1205
+
1206
+ it "#file should require an uploaded file" do
1207
+ tp.str('a').must_equal '1'
1208
+ tp.nonempty_str('a').must_equal '1'
1209
+ tp.int('a').must_equal 1
1210
+ tp.pos_int('a').must_equal 1
1211
+ tp.Integer('a').must_equal 1
1212
+ tp.float('a').must_equal 1.0
1213
+ tp.Float('a').must_equal 1.0
1214
+ end
1215
+ end