strelka 0.0.1pre4 → 0.0.1.pre129

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