roda 3.2.0 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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