strelka 0.0.1pre4 → 0.0.1.pre129

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.
Files changed (73) hide show
  1. data/History.rdoc +1 -1
  2. data/IDEAS.rdoc +62 -0
  3. data/Manifest.txt +38 -7
  4. data/README.rdoc +124 -5
  5. data/Rakefile +22 -6
  6. data/bin/leash +102 -157
  7. data/contrib/hoetemplate/.autotest.erb +23 -0
  8. data/contrib/hoetemplate/History.rdoc.erb +4 -0
  9. data/contrib/hoetemplate/Manifest.txt.erb +8 -0
  10. data/contrib/hoetemplate/README.rdoc.erb +17 -0
  11. data/contrib/hoetemplate/Rakefile.erb +24 -0
  12. data/contrib/hoetemplate/data/file_name/apps/file_name_app +36 -0
  13. data/contrib/hoetemplate/data/file_name/templates/layout.tmpl.erb +13 -0
  14. data/contrib/hoetemplate/data/file_name/templates/top.tmpl.erb +8 -0
  15. data/contrib/hoetemplate/lib/file_name.rb.erb +18 -0
  16. data/contrib/hoetemplate/spec/file_name_spec.rb.erb +21 -0
  17. data/data/strelka/apps/hello-world +30 -0
  18. data/lib/strelka/app/defaultrouter.rb +49 -30
  19. data/lib/strelka/app/errors.rb +121 -0
  20. data/lib/strelka/app/exclusiverouter.rb +40 -0
  21. data/lib/strelka/app/filters.rb +18 -7
  22. data/lib/strelka/app/negotiation.rb +122 -0
  23. data/lib/strelka/app/parameters.rb +171 -14
  24. data/lib/strelka/app/paramvalidator.rb +751 -0
  25. data/lib/strelka/app/plugins.rb +66 -46
  26. data/lib/strelka/app/restresources.rb +499 -0
  27. data/lib/strelka/app/router.rb +73 -0
  28. data/lib/strelka/app/routing.rb +140 -18
  29. data/lib/strelka/app/templating.rb +12 -3
  30. data/lib/strelka/app.rb +174 -24
  31. data/lib/strelka/constants.rb +0 -20
  32. data/lib/strelka/exceptions.rb +29 -0
  33. data/lib/strelka/httprequest/acceptparams.rb +377 -0
  34. data/lib/strelka/httprequest/negotiation.rb +257 -0
  35. data/lib/strelka/httprequest.rb +155 -7
  36. data/lib/strelka/httpresponse/negotiation.rb +579 -0
  37. data/lib/strelka/httpresponse.rb +140 -0
  38. data/lib/strelka/logging.rb +4 -1
  39. data/lib/strelka/mixins.rb +53 -0
  40. data/lib/strelka.rb +22 -1
  41. data/spec/data/error.tmpl +1 -0
  42. data/spec/lib/constants.rb +0 -1
  43. data/spec/lib/helpers.rb +21 -0
  44. data/spec/strelka/app/defaultrouter_spec.rb +41 -35
  45. data/spec/strelka/app/errors_spec.rb +212 -0
  46. data/spec/strelka/app/exclusiverouter_spec.rb +220 -0
  47. data/spec/strelka/app/filters_spec.rb +196 -0
  48. data/spec/strelka/app/negotiation_spec.rb +73 -0
  49. data/spec/strelka/app/parameters_spec.rb +149 -0
  50. data/spec/strelka/app/paramvalidator_spec.rb +1059 -0
  51. data/spec/strelka/app/plugins_spec.rb +26 -19
  52. data/spec/strelka/app/restresources_spec.rb +393 -0
  53. data/spec/strelka/app/router_spec.rb +63 -0
  54. data/spec/strelka/app/routing_spec.rb +183 -9
  55. data/spec/strelka/app/templating_spec.rb +1 -2
  56. data/spec/strelka/app_spec.rb +265 -32
  57. data/spec/strelka/exceptions_spec.rb +53 -0
  58. data/spec/strelka/httprequest/acceptparams_spec.rb +282 -0
  59. data/spec/strelka/httprequest/negotiation_spec.rb +246 -0
  60. data/spec/strelka/httprequest_spec.rb +204 -14
  61. data/spec/strelka/httpresponse/negotiation_spec.rb +464 -0
  62. data/spec/strelka/httpresponse_spec.rb +114 -0
  63. data/spec/strelka/mixins_spec.rb +99 -0
  64. data.tar.gz.sig +1 -0
  65. metadata +175 -79
  66. metadata.gz.sig +2 -0
  67. data/IDEAS.textile +0 -174
  68. data/data/strelka/apps/strelka-admin +0 -65
  69. data/data/strelka/apps/strelka-setup +0 -26
  70. data/data/strelka/bootstrap-config.rb +0 -34
  71. data/data/strelka/templates/admin/console.tmpl +0 -21
  72. data/data/strelka/templates/layout.tmpl +0 -30
  73. data/lib/strelka/process.rb +0 -19
@@ -0,0 +1,1059 @@
1
+ #!/usr/bin/env ruby
2
+ #encoding: utf-8
3
+
4
+ BEGIN {
5
+ require 'pathname'
6
+ basedir = Pathname.new( __FILE__ ).dirname.parent.parent.parent
7
+ $LOAD_PATH.unshift( basedir ) unless $LOAD_PATH.include?( basedir )
8
+ }
9
+
10
+ require 'date'
11
+ require 'rspec'
12
+
13
+ require 'spec/lib/helpers'
14
+
15
+ require 'strelka'
16
+ require 'strelka/app/paramvalidator'
17
+
18
+
19
+ #####################################################################
20
+ ### C O N T E X T S
21
+ #####################################################################
22
+ describe Strelka::App::ParamValidator do
23
+
24
+ TEST_PROFILE = {
25
+ :required => [ :required ],
26
+ :optional => %w{
27
+ optional number int_constraint float_constraint bool_constraint email_constraint
28
+ host_constraint regexp_w_captures regexp_w_one_capture regexp_w_named_captures
29
+ alpha_constraint alphanumeric_constraint printable_constraint proc_constraint
30
+ uri_constraint word_constraint date_constraint
31
+ },
32
+ :constraints => {
33
+ :number => /^\d+$/,
34
+ :regexp_w_captures => /(\w+)(\S+)?/,
35
+ :regexp_w_one_capture => /(\w+)/,
36
+ :regexp_w_named_captures => /(?<category>[[:upper:]]{3})-(?<sku>\d{12})/,
37
+ :int_constraint => :integer,
38
+ :float_constraint => :float,
39
+ :bool_constraint => :boolean,
40
+ :email_constraint => :email,
41
+ :uri_constraint => :uri,
42
+ :host_constraint => :hostname,
43
+ :alpha_constraint => :alpha,
44
+ :alphanumeric_constraint => :alphanumeric,
45
+ :printable_constraint => :printable,
46
+ :word_constraint => :word,
47
+ :proc_constraint => Date.method( :parse ),
48
+ :date_constraint => :date,
49
+ },
50
+ }
51
+
52
+
53
+ before( :all ) do
54
+ setup_logging( :fatal )
55
+ end
56
+
57
+ before(:each) do
58
+ @validator = Strelka::App::ParamValidator.new( TEST_PROFILE )
59
+ end
60
+
61
+ after( :all ) do
62
+ reset_logging()
63
+ end
64
+
65
+
66
+ it "starts out empty" do
67
+ @validator.should be_empty()
68
+ @validator.should_not have_args()
69
+ end
70
+
71
+ it "is no longer empty if at least one set of parameters has been validated" do
72
+ @validator.validate( {'required' => "1"} )
73
+
74
+ @validator.should_not be_empty()
75
+ @validator.should have_args()
76
+ end
77
+
78
+ it "raises an exception on an unknown constraint type" do
79
+ pending "figure out why this isn't working" do
80
+ profile = {
81
+ required: [:required],
82
+ constraints: {
83
+ required: $stderr,
84
+ }
85
+ }
86
+ val = Strelka::App::ParamValidator.new( profile )
87
+
88
+ expect {
89
+ val.validate( required: '1' )
90
+ }.to raise_error( /unknown constraint type IO/ )
91
+ end
92
+ end
93
+
94
+ # Test index operator interface
95
+ it "provides read and write access to valid args via the index operator" do
96
+ rval = nil
97
+
98
+ @validator.validate( {'required' => "1"} )
99
+ @validator[:required].should == "1"
100
+
101
+ @validator[:required] = "bar"
102
+ @validator["required"].should == "bar"
103
+ end
104
+
105
+
106
+ it "untaints valid args if told to do so" do
107
+ rval = nil
108
+ tainted_one = "1"
109
+ tainted_one.taint
110
+
111
+ @validator.validate( {'required' => 1, 'number' => tainted_one},
112
+ :untaint_all_constraints => true )
113
+
114
+ Strelka.log.debug "Validator: %p" % [@validator]
115
+
116
+ @validator[:number].should == "1"
117
+ @validator[:number].tainted?.should be_false()
118
+ end
119
+
120
+
121
+ it "untaints field names" do
122
+ rval = nil
123
+ tainted_one = "1"
124
+ tainted_one.taint
125
+
126
+ @validator.validate( {'required' => 1, 'number' => tainted_one},
127
+ :untaint_all_constraints => true )
128
+
129
+ Strelka.log.debug "Validator: %p" % [@validator]
130
+
131
+ @validator[:number].should == "1"
132
+ @validator[:number].tainted?.should be_false()
133
+ end
134
+
135
+
136
+ it "returns the capture from a regexp constraint if it has only one" do
137
+ rval = nil
138
+ params = { 'required' => 1, 'regexp_w_one_capture' => " ygdrassil " }
139
+
140
+ @validator.validate( params, :untaint_all_constraints => true )
141
+
142
+ Strelka.log.debug "Validator: %p" % [@validator]
143
+
144
+ @validator[:regexp_w_one_capture].should == 'ygdrassil'
145
+ end
146
+
147
+ it "returns the captures from a regexp constraint as an array if it has more than one" do
148
+ rval = nil
149
+ params = { 'required' => 1, 'regexp_w_captures' => " the1tree(!) " }
150
+
151
+ @validator.validate( params, :untaint_all_constraints => true )
152
+
153
+ Strelka.log.debug "Validator: %p" % [@validator]
154
+
155
+ @validator[:regexp_w_captures].should == ['the1tree', '(!)']
156
+ end
157
+
158
+ it "returns the captures from a regexp constraint with named captures as a Hash" do
159
+ rval = nil
160
+ params = { 'required' => 1, 'regexp_w_named_captures' => " JVV-886451300133 ".taint }
161
+
162
+ @validator.validate( params, :untaint_all_constraints => true )
163
+
164
+ Strelka.log.debug "Validator: %p" % [@validator]
165
+
166
+ @validator[:regexp_w_named_captures].should == {:category => 'JVV', :sku => '886451300133'}
167
+ @validator[:regexp_w_named_captures][:category].should_not be_tainted()
168
+ @validator[:regexp_w_named_captures][:sku].should_not be_tainted()
169
+ end
170
+
171
+ it "returns the captures from a regexp constraint as an array " +
172
+ "even if an optional capture doesn't match anything" do
173
+ rval = nil
174
+ params = { 'required' => 1, 'regexp_w_captures' => " the1tree " }
175
+
176
+ @validator.validate( params, :untaint_all_constraints => true )
177
+
178
+ Strelka.log.debug "Validator: %p" % [@validator]
179
+
180
+ @validator[:regexp_w_captures].should == ['the1tree', nil]
181
+ end
182
+
183
+ it "knows the names of fields that were required but missing from the parameters" do
184
+ @validator.validate( {} )
185
+
186
+ @validator.should have_errors()
187
+ @validator.should_not be_okay()
188
+
189
+ @validator.missing.should have(1).members
190
+ @validator.missing.should == ['required']
191
+ end
192
+
193
+ it "knows the names of fields that did not meet their constraints" do
194
+ params = {'number' => 'rhinoceros'}
195
+ @validator.validate( params )
196
+
197
+ @validator.should have_errors()
198
+ @validator.should_not be_okay()
199
+
200
+ @validator.invalid.should have(1).keys
201
+ @validator.invalid.keys.should == ['number']
202
+ end
203
+
204
+ it "can return a combined list of all problem parameters, which includes " +
205
+ " both missing and invalid fields" do
206
+ params = {'number' => 'rhinoceros'}
207
+ @validator.validate( params )
208
+
209
+ @validator.should have_errors()
210
+ @validator.should_not be_okay()
211
+
212
+ @validator.error_fields.should have(2).members
213
+ @validator.error_fields.should include('number')
214
+ @validator.error_fields.should include('required')
215
+ end
216
+
217
+ it "can return human descriptions of validation errors" do
218
+ params = {'number' => 'rhinoceros', 'unknown' => "1"}
219
+ @validator.validate( params )
220
+
221
+ @validator.error_messages.should have(2).members
222
+ @validator.error_messages.should include("Missing value for 'Required'")
223
+ @validator.error_messages.should include("Invalid value for 'Number'")
224
+ end
225
+
226
+ it "can include unknown fields in its human descriptions of validation errors" do
227
+ params = {'number' => 'rhinoceros', 'unknown' => "1"}
228
+ @validator.validate( params )
229
+
230
+ @validator.error_messages(true).should have(3).members
231
+ @validator.error_messages(true).should include("Missing value for 'Required'")
232
+ @validator.error_messages(true).should include("Invalid value for 'Number'")
233
+ @validator.error_messages(true).should include("Unknown parameter 'Unknown'")
234
+ end
235
+
236
+ it "can use provided descriptions of parameters when constructing human " +
237
+ "validation error messages" do
238
+ descs = {
239
+ :number => "Numeral",
240
+ :required => "Test Name",
241
+ }
242
+ params = {'number' => 'rhinoceros', 'unknown' => "1"}
243
+ @validator.validate( params, :descriptions => descs )
244
+
245
+ @validator.error_messages.should have(2).members
246
+ @validator.error_messages.should include("Missing value for 'Test Name'")
247
+ @validator.error_messages.should include("Invalid value for 'Numeral'")
248
+ end
249
+
250
+ it "can get and set the profile's descriptions directly" do
251
+ params = {'number' => 'rhinoceros', 'unknown' => "1"}
252
+
253
+ @validator.descriptions = {
254
+ number: 'Numeral',
255
+ required: 'Test Name'
256
+ }
257
+ @validator.validate( params )
258
+
259
+ @validator.descriptions.should have( 2 ).members
260
+ @validator.error_messages.should have( 2 ).members
261
+ @validator.error_messages.should include("Missing value for 'Test Name'")
262
+ @validator.error_messages.should include("Invalid value for 'Numeral'")
263
+ end
264
+
265
+ it "capitalizes the names of simple fields for descriptions" do
266
+ @validator.get_description( "required" ).should == 'Required'
267
+ end
268
+
269
+ it "splits apart underbarred field names into capitalized words for descriptions" do
270
+ @validator.get_description( "rodent_size" ).should == 'Rodent Size'
271
+ end
272
+
273
+ it "uses the key for descriptions of hash fields" do
274
+ @validator.get_description( "rodent[size]" ).should == 'Size'
275
+ end
276
+
277
+ it "uses separate capitalized words for descriptions of hash fields with underbarred keys " do
278
+ @validator.get_description( "castle[baron_id]" ).should == 'Baron Id'
279
+ end
280
+
281
+ it "coalesces simple hash fields into a hash of validated values" do
282
+ @validator.validate( {'rodent[size]' => 'unusual'}, :optional => ['rodent[size]'] )
283
+
284
+ @validator.valid.should == {'rodent' => {'size' => 'unusual'}}
285
+ end
286
+
287
+ it "coalesces complex hash fields into a nested hash of validated values" do
288
+ profile = {
289
+ :optional => [
290
+ 'recipe[ingredient][name]',
291
+ 'recipe[ingredient][cost]',
292
+ 'recipe[yield]'
293
+ ]
294
+ }
295
+ args = {
296
+ 'recipe[ingredient][name]' => 'nutmeg',
297
+ 'recipe[ingredient][cost]' => '$0.18',
298
+ 'recipe[yield]' => '2 loaves',
299
+ }
300
+
301
+ @validator.validate( args, profile )
302
+ @validator.valid.should == {
303
+ 'recipe' => {
304
+ 'ingredient' => { 'name' => 'nutmeg', 'cost' => '$0.18' },
305
+ 'yield' => '2 loaves'
306
+ }
307
+ }
308
+ end
309
+
310
+ it "untaints both keys and values in complex hash fields if untainting is turned on" do
311
+ profile = {
312
+ :required => [
313
+ 'recipe[ingredient][rarity]',
314
+ ],
315
+ :optional => [
316
+ 'recipe[ingredient][name]',
317
+ 'recipe[ingredient][cost]',
318
+ 'recipe[yield]'
319
+ ],
320
+ :constraints => {
321
+ 'recipe[ingredient][rarity]' => /^([\w\-]+)$/,
322
+ },
323
+ :untaint_all_constraints => true,
324
+ }
325
+ args = {
326
+ 'recipe[ingredient][rarity]'.taint => 'super-rare'.taint,
327
+ 'recipe[ingredient][name]'.taint => 'nutmeg'.taint,
328
+ 'recipe[ingredient][cost]'.taint => '$0.18'.taint,
329
+ 'recipe[yield]'.taint => '2 loaves'.taint,
330
+ }
331
+
332
+ @validator.validate( args, profile )
333
+
334
+ @validator.valid.should == {
335
+ 'recipe' => {
336
+ 'ingredient' => { 'name' => 'nutmeg', 'cost' => '$0.18', 'rarity' => 'super-rare' },
337
+ 'yield' => '2 loaves'
338
+ }
339
+ }
340
+
341
+ @validator.valid.keys.all? {|key| key.should_not be_tainted() }
342
+ @validator.valid.values.all? {|key| key.should_not be_tainted() }
343
+ @validator.valid['recipe'].keys.all? {|key| key.should_not be_tainted() }
344
+ @validator.valid['recipe']['ingredient'].keys.all? {|key| key.should_not be_tainted() }
345
+ @validator.valid['recipe']['yield'].should_not be_tainted()
346
+ @validator.valid['recipe']['ingredient']['rarity'].should_not be_tainted()
347
+ @validator.valid['recipe']['ingredient']['name'].should_not be_tainted()
348
+ @validator.valid['recipe']['ingredient']['cost'].should_not be_tainted()
349
+ end
350
+
351
+ it "accepts the value 'true' for fields with boolean constraints" do
352
+ params = {'required' => '1', 'bool_constraint' => 'true'}
353
+
354
+ @validator.validate( params )
355
+
356
+ @validator.should be_okay()
357
+ @validator.should_not have_errors()
358
+
359
+ @validator[:bool_constraint].should be_true()
360
+ end
361
+
362
+ it "accepts the value 't' for fields with boolean constraints" do
363
+ params = {'required' => '1', 'bool_constraint' => 't'}
364
+
365
+ @validator.validate( params )
366
+
367
+ @validator.should be_okay()
368
+ @validator.should_not have_errors()
369
+
370
+ @validator[:bool_constraint].should be_true()
371
+ end
372
+
373
+ it "accepts the value 'yes' for fields with boolean constraints" do
374
+ params = {'required' => '1', 'bool_constraint' => 'yes'}
375
+
376
+ @validator.validate( params )
377
+
378
+ @validator.should be_okay()
379
+ @validator.should_not have_errors()
380
+
381
+ @validator[:bool_constraint].should be_true()
382
+ end
383
+
384
+ it "accepts the value 'y' for fields with boolean constraints" do
385
+ params = {'required' => '1', 'bool_constraint' => 'y'}
386
+
387
+ @validator.validate( params )
388
+
389
+ @validator.should be_okay()
390
+ @validator.should_not have_errors()
391
+
392
+ @validator[:bool_constraint].should be_true()
393
+ end
394
+
395
+ it "accepts the value '1' for fields with boolean constraints" do
396
+ params = {'required' => '1', 'bool_constraint' => '1'}
397
+
398
+ @validator.validate( params )
399
+
400
+ @validator.should be_okay()
401
+ @validator.should_not have_errors()
402
+
403
+ @validator[:bool_constraint].should be_true()
404
+ end
405
+
406
+ it "accepts the value 'false' for fields with boolean constraints" do
407
+ params = {'required' => '1', 'bool_constraint' => 'false'}
408
+
409
+ @validator.validate( params )
410
+
411
+ @validator.should be_okay()
412
+ @validator.should_not have_errors()
413
+
414
+ @validator[:bool_constraint].should be_false()
415
+ end
416
+
417
+ it "accepts the value 'f' for fields with boolean constraints" do
418
+ params = {'required' => '1', 'bool_constraint' => 'f'}
419
+
420
+ @validator.validate( params )
421
+
422
+ @validator.should be_okay()
423
+ @validator.should_not have_errors()
424
+
425
+ @validator[:bool_constraint].should be_false()
426
+ end
427
+
428
+ it "accepts the value 'no' for fields with boolean constraints" do
429
+ params = {'required' => '1', 'bool_constraint' => 'no'}
430
+
431
+ @validator.validate( params )
432
+
433
+ @validator.should be_okay()
434
+ @validator.should_not have_errors()
435
+
436
+ @validator[:bool_constraint].should be_false()
437
+ end
438
+
439
+ it "accepts the value 'n' for fields with boolean constraints" do
440
+ params = {'required' => '1', 'bool_constraint' => 'n'}
441
+
442
+ @validator.validate( params )
443
+
444
+ @validator.should be_okay()
445
+ @validator.should_not have_errors()
446
+
447
+ @validator[:bool_constraint].should be_false()
448
+ end
449
+
450
+ it "accepts the value '0' for fields with boolean constraints" do
451
+ params = {'required' => '1', 'bool_constraint' => '0'}
452
+
453
+ @validator.validate( params )
454
+
455
+ @validator.should be_okay()
456
+ @validator.should_not have_errors()
457
+
458
+ @validator[:bool_constraint].should be_false()
459
+ end
460
+
461
+ it "rejects non-boolean parameters for fields with boolean constraints" do
462
+ params = {'required' => '1', 'bool_constraint' => 'peanut'}
463
+
464
+ @validator.validate( params )
465
+
466
+ @validator.should_not be_okay()
467
+ @validator.should have_errors()
468
+
469
+ @validator[:bool_constraint].should be_nil()
470
+ end
471
+
472
+ it "accepts simple integers for fields with integer constraints" do
473
+ params = {'required' => '1', 'int_constraint' => '11'}
474
+
475
+ @validator.validate( params )
476
+
477
+ @validator.should be_okay()
478
+ @validator.should_not have_errors()
479
+
480
+ @validator[:int_constraint].should == 11
481
+ end
482
+
483
+ it "accepts '0' for fields with integer constraints" do
484
+ params = {'required' => '1', 'int_constraint' => '0'}
485
+
486
+ @validator.validate( params )
487
+
488
+ @validator.should be_okay()
489
+ @validator.should_not have_errors()
490
+
491
+ @validator[:int_constraint].should == 0
492
+ end
493
+
494
+ it "accepts negative integers for fields with integer constraints" do
495
+ params = {'required' => '1', 'int_constraint' => '-407'}
496
+
497
+ @validator.validate( params )
498
+
499
+ @validator.should be_okay()
500
+ @validator.should_not have_errors()
501
+
502
+ @validator[:int_constraint].should == -407
503
+ end
504
+
505
+ it "rejects non-integers for fields with integer constraints" do
506
+ params = {'required' => '1', 'int_constraint' => '11.1'}
507
+
508
+ @validator.validate( params )
509
+
510
+ @validator.should_not be_okay()
511
+ @validator.should have_errors()
512
+
513
+ @validator[:int_constraint].should be_nil()
514
+ end
515
+
516
+ it "rejects integer values with other cruft in them for fields with integer constraints" do
517
+ params = {'required' => '1', 'int_constraint' => '88licks'}
518
+
519
+ @validator.validate( params )
520
+
521
+ @validator.should_not be_okay()
522
+ @validator.should have_errors()
523
+
524
+ @validator[:int_constraint].should be_nil()
525
+ end
526
+
527
+ it "accepts simple floats for fields with float constraints" do
528
+ params = {'required' => '1', 'float_constraint' => '3.14'}
529
+
530
+ @validator.validate( params )
531
+
532
+ @validator.should be_okay()
533
+ @validator.should_not have_errors()
534
+
535
+ @validator[:float_constraint].should == 3.14
536
+ end
537
+
538
+ it "accepts negative floats for fields with float constraints" do
539
+ params = {'required' => '1', 'float_constraint' => '-3.14'}
540
+
541
+ @validator.validate( params )
542
+
543
+ @validator.should be_okay()
544
+ @validator.should_not have_errors()
545
+
546
+ @validator[:float_constraint].should == -3.14
547
+ end
548
+
549
+ it "accepts positive floats for fields with float constraints" do
550
+ params = {'required' => '1', 'float_constraint' => '+3.14'}
551
+
552
+ @validator.validate( params )
553
+
554
+ @validator.should be_okay()
555
+ @validator.should_not have_errors()
556
+
557
+ @validator[:float_constraint].should == 3.14
558
+ end
559
+
560
+ it "accepts floats that begin with '.' for fields with float constraints" do
561
+ params = {'required' => '1', 'float_constraint' => '.1418'}
562
+
563
+ @validator.validate( params )
564
+
565
+ @validator.should be_okay()
566
+ @validator.should_not have_errors()
567
+
568
+ @validator[:float_constraint].should == 0.1418
569
+ end
570
+
571
+ it "accepts negative floats that begin with '.' for fields with float constraints" do
572
+ params = {'required' => '1', 'float_constraint' => '-.171'}
573
+
574
+ @validator.validate( params )
575
+
576
+ @validator.should be_okay()
577
+ @validator.should_not have_errors()
578
+
579
+ @validator[:float_constraint].should == -0.171
580
+ end
581
+
582
+ it "accepts positive floats that begin with '.' for fields with float constraints" do
583
+ params = {'required' => '1', 'float_constraint' => '+.86668001'}
584
+
585
+ @validator.validate( params )
586
+
587
+ @validator.should be_okay()
588
+ @validator.should_not have_errors()
589
+
590
+ @validator[:float_constraint].should == 0.86668001
591
+ end
592
+
593
+ it "accepts floats in exponential notation for fields with float constraints" do
594
+ params = {'required' => '1', 'float_constraint' => '1756e-5'}
595
+
596
+ @validator.validate( params )
597
+
598
+ @validator.should be_okay()
599
+ @validator.should_not have_errors()
600
+
601
+ @validator[:float_constraint].should == 0.01756
602
+ end
603
+
604
+ it "accepts negative floats in exponential notation for fields with float constraints" do
605
+ params = {'required' => '1', 'float_constraint' => '-28e8'}
606
+
607
+ @validator.validate( params )
608
+
609
+ @validator.should be_okay()
610
+ @validator.should_not have_errors()
611
+
612
+ @validator[:float_constraint].should == -28e8
613
+ end
614
+
615
+ it "accepts floats that start with '.' in exponential notation for fields with float " +
616
+ "constraints" do
617
+ params = {'required' => '1', 'float_constraint' => '.5552e-10'}
618
+
619
+ @validator.validate( params )
620
+
621
+ @validator.should be_okay()
622
+ @validator.should_not have_errors()
623
+
624
+ @validator[:float_constraint].should == 0.5552e-10
625
+ end
626
+
627
+ it "accepts negative floats that start with '.' in exponential notation for fields with " +
628
+ "float constraints" do
629
+ params = {'required' => '1', 'float_constraint' => '-.288088e18'}
630
+
631
+ @validator.validate( params )
632
+
633
+ @validator.should be_okay()
634
+ @validator.should_not have_errors()
635
+
636
+ @validator[:float_constraint].should == -0.288088e18
637
+ end
638
+
639
+ it "accepts integers for fields with float constraints" do
640
+ params = {'required' => '1', 'float_constraint' => '288'}
641
+
642
+ @validator.validate( params )
643
+
644
+ @validator.should be_okay()
645
+ @validator.should_not have_errors()
646
+
647
+ @validator[:float_constraint].should == 288.0
648
+ end
649
+
650
+ it "accepts negative integers for fields with float constraints" do
651
+ params = {'required' => '1', 'float_constraint' => '-1606'}
652
+
653
+ @validator.validate( params )
654
+
655
+ @validator.should be_okay()
656
+ @validator.should_not have_errors()
657
+
658
+ @validator[:float_constraint].should == -1606.0
659
+ end
660
+
661
+ it "accepts positive integers for fields with float constraints" do
662
+ params = {'required' => '1', 'float_constraint' => '+2600'}
663
+
664
+ @validator.validate( params )
665
+
666
+ @validator.should be_okay()
667
+ @validator.should_not have_errors()
668
+
669
+ @validator[:float_constraint].should == 2600.0
670
+ end
671
+
672
+ it "accepts dates for fields with date constraints" do
673
+ params = {'required' => '1', 'date_constraint' => '2008-11-18'}
674
+
675
+ @validator.validate( params )
676
+
677
+ @validator.should be_okay()
678
+ @validator.should_not have_errors()
679
+
680
+ @validator[:date_constraint].should == Date.parse( '2008-11-18' )
681
+ end
682
+
683
+
684
+ VALID_URIS = %w{
685
+ http://127.0.0.1
686
+ http://127.0.0.1/
687
+ http://[127.0.0.1]/
688
+ http://ruby-lang.org/
689
+ http://www.rocketboom.com/vlog/rb_08_feb_01
690
+ http://del.icio.us/search/?fr=del_icio_us&p=ruby+arrow&type=all
691
+ http://[FEDC:BA98:7654:3210:FEDC:BA98:7654:3210]:8080/index.html
692
+ http://[1080:0:0:0:8:800:200C:417A]/index.html
693
+ http://[3ffe:2a00:100:7031::1]
694
+ http://[1080::8:800:200C:417A]/foo
695
+ http://[::192.9.5.5]/ipng
696
+ http://[::FFFF:129.144.52.38]:3474/index.html
697
+ http://[2010:836B:4179::836B:4179]
698
+
699
+ https://mail.google.com/
700
+ https://127.0.0.1/
701
+ https://r4.com:8080/
702
+
703
+ ftp://ftp.ruby-lang.org/pub/ruby/1.0/ruby-0.49.tar.gz
704
+ ftp://crashoverride:god@gibson.ellingsonmineral.com/root/.workspace/.garbage.
705
+
706
+ ldap:/o=University%20of%20Michigan,c=US
707
+ ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US
708
+ ldap://ldap.itd.umich.edu/o=University%20of%20Michigan,c=US?postalAddress
709
+ ldap://host.com:6666/o=University%20of%20Michigan,c=US??sub?(cn=Babs%20Jensen)
710
+ ldap://ldap.itd.umich.edu/c=GB?objectClass?one
711
+ ldap://ldap.question.com/o=Question%3f,c=US?mail
712
+ ldap://ldap.netscape.com/o=Babsco,c=US??(int=%5c00%5c00%5c00%5c04)
713
+ ldap:/??sub??bindname=cn=Manager%2co=Foo
714
+ ldap:/??sub??!bindname=cn=Manager%2co=Foo
715
+ }
716
+
717
+ VALID_URIS.each do |uri_string|
718
+ it "accepts #{uri_string} for fields with URI constraints" do
719
+ params = {'required' => '1', 'uri_constraint' => uri_string}
720
+
721
+ @validator.validate( params )
722
+
723
+ @validator.should be_okay()
724
+ @validator.should_not have_errors()
725
+
726
+ @validator[:uri_constraint].should be_a_kind_of( URI::Generic )
727
+ @validator[:uri_constraint].to_s.should == uri_string
728
+ end
729
+ end
730
+
731
+ # :FIXME: I don't know LDAP uris very well, so I'm not sure how they're likely to
732
+ # be invalidly-occurring in the wild
733
+ INVALID_URIS = %W{
734
+ glark:
735
+
736
+ http:
737
+ http://
738
+ http://_com/vlog/rb_08_feb_01
739
+ http://del.icio.us/search/\x20\x14\x18
740
+ http://FEDC:BA98:7654:3210:FEDC:BA98:7654:3210/index.html
741
+ http://1080:0:0:0:8:800:200C:417A/index.html
742
+ http://3ffe:2a00:100:7031::1
743
+ http://1080::8:800:200C:417A/foo
744
+ http://::192.9.5.5/ipng
745
+ http://::FFFF:129.144.52.38:80/index.html
746
+ http://2010:836B:4179::836B:4179
747
+
748
+ https:
749
+ https://user:pass@/
750
+ https://r4.com:nonnumericport/
751
+
752
+ ftp:
753
+ ftp:ruby-0.49.tar.gz
754
+ ftp://crashoverride:god@/root/.workspace/.garbage.
755
+
756
+ ldap:
757
+ ldap:/o=University\x20of\x20Michigan,c=US
758
+ ldap://ldap.itd.umich.edu/o=University+\x00of+Michigan
759
+ }
760
+
761
+ INVALID_URIS.each do |uri_string|
762
+ it "rejects #{uri_string} for fields with URI constraints" do
763
+ params = {'required' => '1', 'uri_constraint' => uri_string}
764
+
765
+ # lambda {
766
+ @validator.validate( params )
767
+ # }.should_not raise_error()
768
+
769
+ @validator.should_not be_okay()
770
+ @validator.should have_errors()
771
+
772
+ @validator[:uri_constraint].should be_nil()
773
+ end
774
+ end
775
+
776
+ it "accepts simple RFC822 addresses for fields with email constraints" do
777
+ params = {'required' => '1', 'email_constraint' => 'jrandom@hacker.ie'}
778
+
779
+ @validator.validate( params )
780
+
781
+ @validator.should be_okay()
782
+ @validator.should_not have_errors()
783
+
784
+ @validator[:email_constraint].should == 'jrandom@hacker.ie'
785
+ end
786
+
787
+ it "accepts hyphenated domains in RFC822 addresses for fields with email constraints" do
788
+ params = {'required' => '1', 'email_constraint' => 'jrandom@just-another-hacquer.fr'}
789
+
790
+ @validator.validate( params )
791
+
792
+ @validator.should be_okay()
793
+ @validator.should_not have_errors()
794
+
795
+ @validator[:email_constraint].should == 'jrandom@just-another-hacquer.fr'
796
+ end
797
+
798
+ COMPLEX_ADDRESSES = [
799
+ 'ruby+hacker@random-example.org',
800
+ '"ruby hacker"@ph8675309.org',
801
+ 'jrandom@[ruby hacquer].com',
802
+ 'abcdefghijklmnopqrstuvwxyz@abcdefghijklmnopqrstuvwxyz',
803
+ ]
804
+ COMPLEX_ADDRESSES.each do |addy|
805
+ it "accepts #{addy} for fields with email constraints" do
806
+ params = {'required' => '1', 'email_constraint' => addy}
807
+
808
+ @validator.validate( params )
809
+
810
+ @validator.should be_okay()
811
+ @validator.should_not have_errors()
812
+
813
+ @validator[:email_constraint].should == addy
814
+ end
815
+ end
816
+
817
+
818
+ BOGUS_ADDRESSES = [
819
+ 'jrandom@hacquer com',
820
+ 'jrandom@ruby hacquer.com',
821
+ 'j random@rubyhacquer.com',
822
+ 'j random@ruby|hacquer.com',
823
+ 'j:random@rubyhacquer.com',
824
+ ]
825
+ BOGUS_ADDRESSES.each do |addy|
826
+ it "rejects #{addy} for fields with email constraints" do
827
+ params = {'required' => '1', 'email_constraint' => addy}
828
+
829
+ @validator.validate( params )
830
+
831
+ @validator.should_not be_okay()
832
+ @validator.should have_errors()
833
+
834
+ @validator[:email_constraint].should be_nil()
835
+ end
836
+ end
837
+
838
+ it "accepts simple hosts for fields with host constraints" do
839
+ params = {'required' => '1', 'host_constraint' => 'deveiate.org'}
840
+
841
+ @validator.validate( params )
842
+
843
+ @validator.should be_okay()
844
+ @validator.should_not have_errors()
845
+
846
+ @validator[:host_constraint].should == 'deveiate.org'
847
+ end
848
+
849
+ it "accepts hyphenated hosts for fields with host constraints" do
850
+ params = {'required' => '1', 'host_constraint' => 'your-characters-can-fly.kr'}
851
+
852
+ @validator.validate( params )
853
+
854
+ @validator.should be_okay()
855
+ @validator.should_not have_errors()
856
+
857
+ @validator[:host_constraint].should == 'your-characters-can-fly.kr'
858
+ end
859
+
860
+ BOGUS_HOSTS = [
861
+ '.',
862
+ 'glah ',
863
+ 'glah[lock]',
864
+ 'glah.be$',
865
+ 'indus«tree».com',
866
+ ]
867
+
868
+ BOGUS_HOSTS.each do |hostname|
869
+ it "rejects #{hostname} for fields with host constraints" do
870
+ params = {'required' => '1', 'host_constraint' => hostname}
871
+
872
+ @validator.validate( params )
873
+
874
+ @validator.should_not be_okay()
875
+ @validator.should have_errors()
876
+
877
+ @validator[:host_constraint].should be_nil()
878
+ end
879
+ end
880
+
881
+ it "accepts alpha characters for fields with alpha constraints" do
882
+ params = {'required' => '1', 'alpha_constraint' => 'abelincoln'}
883
+
884
+ @validator.validate( params )
885
+
886
+ @validator.should be_okay()
887
+ @validator.should_not have_errors()
888
+
889
+ @validator[:alpha_constraint].should == 'abelincoln'
890
+ end
891
+
892
+ it "rejects non-alpha characters for fields with alpha constraints" do
893
+ params = {'required' => '1', 'alpha_constraint' => 'duck45'}
894
+
895
+ @validator.validate( params )
896
+
897
+ @validator.should_not be_okay()
898
+ @validator.should have_errors()
899
+
900
+ @validator[:alpha_constraint].should be_nil()
901
+ end
902
+
903
+ ### 'alphanumeric'
904
+ it "accepts alphanumeric characters for fields with alphanumeric constraints" do
905
+ params = {'required' => '1', 'alphanumeric_constraint' => 'zombieabe11'}
906
+
907
+ @validator.validate( params )
908
+
909
+ @validator.should be_okay()
910
+ @validator.should_not have_errors()
911
+
912
+ @validator[:alphanumeric_constraint].should == 'zombieabe11'
913
+ end
914
+
915
+ it "rejects non-alphanumeric characters for fields with alphanumeric constraints" do
916
+ params = {'required' => '1', 'alphanumeric_constraint' => 'duck!ling'}
917
+
918
+ @validator.validate( params )
919
+
920
+ @validator.should_not be_okay()
921
+ @validator.should have_errors()
922
+
923
+ @validator[:alphanumeric_constraint].should be_nil()
924
+ end
925
+
926
+ ### 'printable'
927
+ it "accepts printable characters for fields with 'printable' constraints" do
928
+ test_content = <<-EOF
929
+ I saw you with some kind of medical apparatus strapped to your
930
+ spine. It was all glass and metal, a great crystaline hypodermic
931
+ spider, carrying you into the aether with a humming, crackling sound.
932
+ EOF
933
+
934
+ params = {
935
+ 'required' => '1',
936
+ 'printable_constraint' => test_content
937
+ }
938
+
939
+ @validator.validate( params )
940
+
941
+ @validator.should be_okay()
942
+ @validator[:printable_constraint].should == test_content
943
+ end
944
+
945
+ it "rejects non-printable characters for fields with 'printable' constraints" do
946
+ params = {'required' => '1', 'printable_constraint' => %{\0Something cold\0}}
947
+
948
+ @validator.validate( params )
949
+
950
+ @validator.should_not be_okay()
951
+ @validator.should have_errors()
952
+
953
+ @validator[:printable_constraint].should be_nil()
954
+ end
955
+
956
+
957
+ it "accepts any word characters for fields with 'word' constraints" do
958
+ params = {
959
+ 'required' => '1',
960
+ 'word_constraint' => "Собака"
961
+ }
962
+
963
+ @validator.validate( params )
964
+
965
+ @validator.should_not have_errors()
966
+ @validator.should be_okay()
967
+
968
+ @validator[:word_constraint].should == params['word_constraint']
969
+ end
970
+
971
+ it "accepts parameters for fields with Proc constraints if the Proc returns a true value" do
972
+ test_date = '2007-07-17'
973
+ params = {'required' => '1', 'proc_constraint' => test_date}
974
+
975
+ @validator.validate( params )
976
+
977
+ @validator.should be_okay()
978
+ @validator.should_not have_errors()
979
+
980
+ @validator[:proc_constraint].should == Date.parse( test_date )
981
+ end
982
+
983
+ it "rejects parameters for fields with Proc constraints if the Proc returns a false value" do
984
+ params = {'required' => '1', 'proc_constraint' => %{::::}}
985
+
986
+ @validator.validate( params )
987
+
988
+ @validator.should_not be_okay()
989
+ @validator.should have_errors()
990
+
991
+ @validator[:proc_constraint].should be_nil()
992
+ end
993
+
994
+ it "can be merged with another set of parameters" do
995
+ params = {}
996
+ @validator.validate( params )
997
+ newval = @validator.merge( 'required' => '1' )
998
+
999
+ newval.should_not equal( @validator )
1000
+
1001
+ @validator.should_not be_okay()
1002
+ @validator.should have_errors()
1003
+ newval.should be_okay()
1004
+ newval.should_not have_errors()
1005
+
1006
+ @validator[:required].should == nil
1007
+ newval[:required].should == '1'
1008
+ end
1009
+
1010
+ it "can have required parameters merged into it after the initial validation" do
1011
+ params = {}
1012
+ @validator.validate( params )
1013
+ @validator.merge!( 'required' => '1' )
1014
+
1015
+ @validator.should be_okay()
1016
+ @validator.should_not have_errors()
1017
+
1018
+ @validator[:required].should == '1'
1019
+ end
1020
+
1021
+ it "can have optional parameters merged into it after the initial validation" do
1022
+ params = { 'required' => '1' }
1023
+ @validator.validate( params )
1024
+ @validator.merge!( 'optional' => 'yep.' )
1025
+
1026
+ @validator.should be_okay()
1027
+ @validator.should_not have_errors()
1028
+
1029
+ @validator[:optional].should == 'yep.'
1030
+ end
1031
+
1032
+ it "rejects invalid parameters when they're merged after initial validation" do
1033
+ params = { 'required' => '1', 'number' => '88' }
1034
+ @validator.validate( params )
1035
+ @validator.merge!( 'number' => 'buckwheat noodles' )
1036
+
1037
+ @validator.should_not be_okay()
1038
+ @validator.should have_errors()
1039
+
1040
+ @validator[:number].should be_nil()
1041
+ end
1042
+
1043
+ it "allows valid parameters to be fetched en masse" do
1044
+ params = { 'required' => '1', 'number' => '88' }
1045
+ @validator.validate( params )
1046
+ @validator.values_at( :required, :number ).should == [ '1', '88' ]
1047
+ end
1048
+
1049
+ it "treats ArgumentErrors in builtin constraints as validation failures" do
1050
+ params = { 'required' => '1', 'number' => 'jalopy' }
1051
+ @validator.validate( params )
1052
+ @validator.should_not be_okay()
1053
+ @validator.should have_errors()
1054
+ @validator.error_messages.should == ["Invalid value for 'Number'"]
1055
+ @validator[:number].should == nil
1056
+ end
1057
+
1058
+ end
1059
+