dato_json_schema 0.20.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,46 @@
1
+ require "test_helper"
2
+
3
+ require "json_schema"
4
+
5
+ describe JsonSchema::Schema do
6
+ it "allows schema attribute access with #[]" do
7
+ schema = JsonSchema::Schema.new
8
+ schema.properties = { "foo" => nil }
9
+ assert_equal({ "foo" => nil }, schema[:properties])
10
+ end
11
+
12
+ it "allows schema attribute access with #[] and overridden name" do
13
+ schema = JsonSchema::Schema.new
14
+ schema.additional_properties = { "foo" => nil }
15
+ assert_equal({ "foo" => nil }, schema[:additionalProperties])
16
+ end
17
+
18
+ it "allows schema attribute access with #[] as string" do
19
+ schema = JsonSchema::Schema.new
20
+ schema.properties = { "foo" => nil }
21
+ assert_equal({ "foo" => nil }, schema["properties"])
22
+ end
23
+
24
+ it "raises if attempting to access #[] with bad method" do
25
+ schema = JsonSchema::Schema.new
26
+ assert_raises NoMethodError do
27
+ schema[:wat]
28
+ end
29
+ end
30
+
31
+ it "raises if attempting to access #[] with non-schema attribute" do
32
+ schema = JsonSchema::Schema.new
33
+ assert_raises NoMethodError do
34
+ schema[:expanded]
35
+ end
36
+ end
37
+
38
+ it "updates type_parsed when type is changed" do
39
+ schema = JsonSchema::Schema.new
40
+ schema.type = ["integer"]
41
+ assert_equal [Integer], schema.type_parsed
42
+
43
+ schema.type = ["string"]
44
+ assert_equal [String], schema.type_parsed
45
+ end
46
+ end
@@ -0,0 +1,1078 @@
1
+ require "test_helper"
2
+
3
+ require "json_schema"
4
+
5
+ describe JsonSchema::Validator do
6
+ after do
7
+ JsonSchema.configuration.reset!
8
+ end
9
+
10
+ it "can find data valid" do
11
+ assert_valid
12
+ end
13
+
14
+ it "validates enum successfully" do
15
+ pointer("#/definitions/app/definitions/visibility").merge!(
16
+ "enum" => ["private", "public"]
17
+ )
18
+ data_sample["visibility"] = "public"
19
+ assert_valid
20
+ end
21
+
22
+ it "validates enum unsuccessfully" do
23
+ pointer("#/definitions/app/definitions/visibility").merge!(
24
+ "enum" => ["private", "public"]
25
+ )
26
+ data_sample["visibility"] = "personal"
27
+ refute_valid
28
+ assert_includes error_messages,
29
+ %{personal is not a member of ["private", "public"].}
30
+ assert_includes error_types, :invalid_type
31
+ end
32
+
33
+ it "validates type successfully" do
34
+ pointer("#/definitions/app").merge!(
35
+ "type" => ["object"]
36
+ )
37
+ @data_sample = { "name" => "cloudnasium" }
38
+ assert_valid
39
+ end
40
+
41
+ it "validates sub-type successfully" do
42
+ pointer("#/definitions/app").merge!(
43
+ "type" => ["object"]
44
+ )
45
+ class SomeClass < Hash; end
46
+ @data_sample = SomeClass.new
47
+ @data_sample["name"] = "yayrails"
48
+ assert_valid
49
+ end
50
+
51
+ it "validates type unsuccessfully" do
52
+ pointer("#/definitions/app").merge!(
53
+ "type" => ["object"]
54
+ )
55
+ @data_sample = 4
56
+ refute_valid
57
+ assert_includes error_messages, %{For 'definitions/app', 4 is not an object.}
58
+ assert_includes error_types, :invalid_type
59
+ assert_includes error_data, 4
60
+ end
61
+
62
+ it "provides accurate error messages for multiple type errors" do
63
+ pointer("#/definitions/app").merge!(
64
+ "type" => ["string"]
65
+ )
66
+ @data_sample = 4
67
+ refute_valid
68
+ assert_includes error_messages, %{For 'definitions/app', 4 is not a string.}
69
+ assert_includes error_types, :invalid_type
70
+
71
+ pointer("#/definitions/app").merge!(
72
+ "type" => ["string", "null"]
73
+ )
74
+ @data_sample = 4
75
+ refute_valid
76
+ assert_includes error_messages, %{For 'definitions/app', 4 is not a string or null.}
77
+ assert_includes error_types, :invalid_type
78
+
79
+ pointer("#/definitions/app").merge!(
80
+ "type" => ["object", "null", "string"]
81
+ )
82
+ @data_sample = 4
83
+ refute_valid
84
+ assert_includes error_messages, %{For 'definitions/app', 4 is not an object, null, or string.}
85
+ assert_includes error_types, :invalid_type
86
+ end
87
+
88
+ it "validates items with list successfully" do
89
+ pointer("#/definitions/app/definitions/flags").merge!(
90
+ "items" => {
91
+ "pattern" => "^[a-z][a-z\\-]*[a-z]$"
92
+ }
93
+ )
94
+ data_sample["flags"] = ["websockets"]
95
+ assert_valid
96
+ end
97
+
98
+ it "validates items with list unsuccessfully" do
99
+ pointer("#/definitions/app/definitions/flags").merge!(
100
+ "items" => {
101
+ "pattern" => "^[a-z][a-z\\-]*[a-z]$"
102
+ }
103
+ )
104
+ data_sample["flags"] = ["1337"]
105
+ refute_valid
106
+ assert_includes error_messages,
107
+ %{1337 does not match /^[a-z][a-z\\-]*[a-z]$/.}
108
+ assert_includes error_types, :pattern_failed
109
+ assert_includes error_data, "1337"
110
+ end
111
+
112
+ it "validates items with tuple successfully" do
113
+ pointer("#/definitions/app/definitions/flags").merge!(
114
+ "items" => [
115
+ { "enum" => ["bamboo", "cedar"] },
116
+ { "enum" => ["http", "https"] }
117
+ ]
118
+ )
119
+ data_sample["flags"] = ["cedar", "https"]
120
+ assert_valid
121
+ end
122
+
123
+ it "validates items with tuple with additionalItems boolean successfully" do
124
+ pointer("#/definitions/app/definitions/flags").merge!(
125
+ "additionalItems" => true,
126
+ "items" => [
127
+ { "enum" => ["bamboo", "cedar"] },
128
+ { "enum" => ["http", "https"] }
129
+ ]
130
+ )
131
+ data_sample["flags"] = ["cedar", "https", "websockets"]
132
+ assert_valid
133
+ end
134
+
135
+ it "validates items with tuple with additionalItems boolean unsuccessfully" do
136
+ pointer("#/definitions/app/definitions/flags").merge!(
137
+ "additionalItems" => false,
138
+ "items" => [
139
+ { "enum" => ["bamboo", "cedar"] },
140
+ { "enum" => ["http", "https"] }
141
+ ]
142
+ )
143
+ data_sample["flags"] = ["cedar", "https", "websockets"]
144
+ refute_valid
145
+ assert_includes error_messages, %{No more than 2 items are allowed; 3 were supplied.}
146
+ assert_includes error_types, :max_items_failed
147
+ assert_includes error_data, ["cedar", "https", "websockets"]
148
+ end
149
+
150
+ it "validates items with tuple with additionalItems schema successfully" do
151
+ pointer("#/definitions/app/definitions/flags").merge!(
152
+ "additionalItems" => { "enum" => [ "foo", "websockets" ] },
153
+ "items" => [
154
+ { "enum" => ["bamboo", "cedar"] },
155
+ { "enum" => ["http", "https"] }
156
+ ]
157
+ )
158
+ data_sample["flags"] = ["cedar", "https", "websockets"]
159
+ assert_valid
160
+ end
161
+
162
+ it "validates items with tuple with additionalItems schema unsuccessfully for non-conforming additional item" do
163
+ pointer("#/definitions/app/definitions/flags").merge!(
164
+ "additionalItems" => { "enum" => [ "foo", "bar" ] },
165
+ "items" => [
166
+ { "enum" => ["bamboo", "cedar"] },
167
+ { "enum" => ["http", "https"] }
168
+ ]
169
+ )
170
+ data_sample["flags"] = ["cedar", "https", "websockets"]
171
+ refute_valid
172
+ assert_includes error_messages,
173
+ %{websockets is not a member of ["foo", "bar"].}
174
+ assert_includes error_types, :invalid_type
175
+ assert_includes error_data, "websockets"
176
+ end
177
+
178
+ it "validates items with tuple with additionalItems schema unsuccessfully with multiple failures" do
179
+ pointer("#/definitions/app/definitions/flags").merge!(
180
+ "additionalItems" => { "enum" => [ "foo", "bar" ] },
181
+ "items" => [
182
+ { "enum" => ["bamboo", "cedar"] },
183
+ { "enum" => ["http", "https"] }
184
+ ]
185
+ )
186
+ data_sample["flags"] = ["cedar", "https", "websockets", "1337"]
187
+ refute_valid
188
+ assert_includes error_messages,
189
+ %{websockets is not a member of ["foo", "bar"].}
190
+ assert_includes error_types, :invalid_type
191
+ assert_includes error_data, "websockets"
192
+ assert_includes error_messages,
193
+ %{1337 is not a member of ["foo", "bar"].}
194
+ assert_includes error_types, :invalid_type
195
+ assert_includes error_data, "1337"
196
+ end
197
+
198
+ it "validates items with tuple with additionalItems schema unsuccessfully with non-conforming items and additional items" do
199
+ pointer("#/definitions/app/definitions/flags").merge!(
200
+ "additionalItems" => { "enum" => [ "foo", "bar" ] },
201
+ "items" => [
202
+ { "enum" => ["bamboo", "cedar"] },
203
+ { "enum" => ["http", "https"] }
204
+ ]
205
+ )
206
+ data_sample["flags"] = ["cedar", "1337", "websockets"]
207
+ refute_valid
208
+ assert_includes error_messages,
209
+ %{websockets is not a member of ["foo", "bar"].}
210
+ assert_includes error_types, :invalid_type
211
+ assert_includes error_data, "websockets"
212
+ assert_includes error_messages,
213
+ %{1337 is not a member of ["http", "https"].}
214
+ assert_includes error_types, :invalid_type
215
+ assert_includes error_data, "1337"
216
+ end
217
+
218
+ it "validates items with tuple unsuccessfully for not enough items" do
219
+ pointer("#/definitions/app/definitions/flags").merge!(
220
+ "items" => [
221
+ { "enum" => ["bamboo", "cedar"] },
222
+ { "enum" => ["http", "https"] }
223
+ ]
224
+ )
225
+ data_sample["flags"] = ["cedar"]
226
+ refute_valid
227
+ assert_includes error_messages,
228
+ %{2 items required; only 1 was supplied.}
229
+ assert_includes error_types, :min_items_failed
230
+ assert_includes error_data, ["cedar"]
231
+ end
232
+
233
+ it "validates items with tuple unsuccessfully for too many items" do
234
+ pointer("#/definitions/app/definitions/flags").merge!(
235
+ "additionalItems" => false,
236
+ "items" => [
237
+ { "enum" => ["bamboo", "cedar"] },
238
+ { "enum" => ["http", "https"] }
239
+ ]
240
+ )
241
+ data_sample["flags"] = ["cedar", "https", "websockets"]
242
+ refute_valid
243
+ assert_includes error_messages,
244
+ %{No more than 2 items are allowed; 3 were supplied.}
245
+ assert_includes error_types, :max_items_failed
246
+ assert_includes error_data, ["cedar", "https", "websockets"]
247
+ end
248
+
249
+ it "validates items with tuple unsuccessfully for non-conforming items" do
250
+ pointer("#/definitions/app/definitions/flags").merge!(
251
+ "additionalItems" => false,
252
+ "items" => [
253
+ { "enum" => ["bamboo", "cedar"] },
254
+ { "enum" => ["http", "https"] }
255
+ ]
256
+ )
257
+ data_sample["flags"] = ["cedar", "1337"]
258
+ refute_valid
259
+ assert_includes error_messages,
260
+ %{1337 is not a member of ["http", "https"].}
261
+ assert_includes error_types, :invalid_type
262
+ assert_includes error_data, "1337"
263
+ end
264
+
265
+ it "validates maxItems successfully" do
266
+ pointer("#/definitions/app/definitions/flags").merge!(
267
+ "maxItems" => 10
268
+ )
269
+ data_sample["flags"] = (0...10).to_a
270
+ assert_valid
271
+ end
272
+
273
+ it "validates maxItems unsuccessfully" do
274
+ pointer("#/definitions/app/definitions/flags").merge!(
275
+ "maxItems" => 10
276
+ )
277
+ data_sample["flags"] = (0...11).to_a
278
+ refute_valid
279
+ assert_includes error_messages,
280
+ %{No more than 10 items are allowed; 11 were supplied.}
281
+ assert_includes error_types, :max_items_failed
282
+ assert_includes error_data, (0...11).to_a
283
+ end
284
+
285
+ it "validates minItems successfully" do
286
+ pointer("#/definitions/app/definitions/flags").merge!(
287
+ "minItems" => 1
288
+ )
289
+ data_sample["flags"] = ["websockets"]
290
+ assert_valid
291
+ end
292
+
293
+ it "validates minItems unsuccessfully" do
294
+ pointer("#/definitions/app/definitions/flags").merge!(
295
+ "minItems" => 1
296
+ )
297
+ data_sample["flags"] = []
298
+ refute_valid
299
+ assert_includes error_messages, %{1 item required; only 0 were supplied.}
300
+ assert_includes error_types, :min_items_failed
301
+ assert_includes error_data, []
302
+ end
303
+
304
+ it "validates uniqueItems successfully" do
305
+ pointer("#/definitions/app/definitions/flags").merge!(
306
+ "uniqueItems" => true
307
+ )
308
+ data_sample["flags"] = ["websockets"]
309
+ assert_valid
310
+ end
311
+
312
+ it "validates uniqueItems unsuccessfully" do
313
+ pointer("#/definitions/app/definitions/flags").merge!(
314
+ "uniqueItems" => true
315
+ )
316
+ data_sample["flags"] = ["websockets", "websockets"]
317
+ refute_valid
318
+ assert_includes error_messages, %{Duplicate items are not allowed.}
319
+ assert_includes error_types, :unique_items_failed
320
+ assert_includes error_data, ["websockets", "websockets"]
321
+ end
322
+
323
+ it "validates maximum for an integer with exclusiveMaximum false" do
324
+ pointer("#/definitions/app/definitions/id").merge!(
325
+ "exclusiveMaximum" => false,
326
+ "maximum" => 10
327
+ )
328
+ data_sample["id"] = 11
329
+ refute_valid
330
+ assert_includes error_messages, %{11 must be less than or equal to 10.}
331
+ assert_includes error_types, :max_failed
332
+ assert_includes error_data, 11
333
+ end
334
+
335
+ it "validates maximum for an integer with exclusiveMaximum true" do
336
+ pointer("#/definitions/app/definitions/id").merge!(
337
+ "exclusiveMaximum" => true,
338
+ "maximum" => 10
339
+ )
340
+ data_sample["id"] = 10
341
+ refute_valid
342
+ assert_includes error_messages, %{10 must be less than 10.}
343
+ assert_includes error_types, :max_failed
344
+ end
345
+
346
+ it "validates maximum for a number with exclusiveMaximum false" do
347
+ pointer("#/definitions/app/definitions/cost").merge!(
348
+ "exclusiveMaximum" => false,
349
+ "maximum" => 10.0
350
+ )
351
+ data_sample["cost"] = 10.1
352
+ refute_valid
353
+ assert_includes error_messages, %{10.1 must be less than or equal to 10.0.}
354
+ assert_includes error_types, :max_failed
355
+ end
356
+
357
+ it "validates maximum for a number with exclusiveMaximum true" do
358
+ pointer("#/definitions/app/definitions/cost").merge!(
359
+ "exclusiveMaximum" => true,
360
+ "maximum" => 10.0
361
+ )
362
+ data_sample["cost"] = 10.0
363
+ refute_valid
364
+ assert_includes error_messages, %{10.0 must be less than 10.0.}
365
+ assert_includes error_types, :max_failed
366
+ end
367
+
368
+ it "validates minimum for an integer with exclusiveMaximum false" do
369
+ pointer("#/definitions/app/definitions/id").merge!(
370
+ "exclusiveMinimum" => false,
371
+ "minimum" => 1
372
+ )
373
+ data_sample["id"] = 0
374
+ refute_valid
375
+ assert_includes error_messages, %{0 must be greater than or equal to 1.}
376
+ assert_includes error_types, :min_failed
377
+ assert_includes error_data, 0
378
+ end
379
+
380
+ it "validates minimum for an integer with exclusiveMaximum true" do
381
+ pointer("#/definitions/app/definitions/id").merge!(
382
+ "exclusiveMinimum" => true,
383
+ "minimum" => 1
384
+ )
385
+ data_sample["id"] = 1
386
+ refute_valid
387
+ assert_includes error_messages, %{1 must be greater than 1.}
388
+ end
389
+
390
+ it "validates minimum for a number with exclusiveMaximum false" do
391
+ pointer("#/definitions/app/definitions/cost").merge!(
392
+ "exclusiveMinimum" => false,
393
+ "minimum" => 0.0
394
+ )
395
+ data_sample["cost"] = -0.01
396
+ refute_valid
397
+ assert_includes error_messages,
398
+ %{-0.01 must be greater than or equal to 0.0.}
399
+ assert_includes error_types, :min_failed
400
+ end
401
+
402
+ it "validates minimum for a number with exclusiveMaximum true" do
403
+ pointer("#/definitions/app/definitions/cost").merge!(
404
+ "exclusiveMinimum" => true,
405
+ "minimum" => 0.0
406
+ )
407
+ data_sample["cost"] = 0.0
408
+ refute_valid
409
+ assert_includes error_messages, %{0.0 must be greater than 0.0.}
410
+ assert_includes error_types, :min_failed
411
+ end
412
+
413
+ it "validates multipleOf for an integer" do
414
+ pointer("#/definitions/app/definitions/id").merge!(
415
+ "multipleOf" => 2
416
+ )
417
+ data_sample["id"] = 1
418
+ refute_valid
419
+ assert_includes error_messages, %{1 is not a multiple of 2.}
420
+ assert_includes error_types, :multiple_of_failed
421
+ assert_includes error_data, 1
422
+ end
423
+
424
+ it "validates multipleOf for a number" do
425
+ pointer("#/definitions/app/definitions/cost").merge!(
426
+ "multipleOf" => 0.01
427
+ )
428
+ data_sample["cost"] = 0.005
429
+ refute_valid
430
+ assert_includes error_messages, %{0.005 is not a multiple of 0.01.}
431
+ assert_includes error_types, :multiple_of_failed
432
+ end
433
+
434
+ it "validates additionalProperties boolean successfully" do
435
+ pointer("#/definitions/app").merge!(
436
+ "additionalProperties" => true
437
+ )
438
+ data_sample["foo"] = "bar"
439
+ assert_valid
440
+ end
441
+
442
+ it "validates additionalProperties boolean unsuccessfully" do
443
+ pointer("#/definitions/app").merge!(
444
+ "additionalProperties" => false,
445
+ "patternProperties" => {
446
+ "^matches" => {}
447
+ }
448
+ )
449
+ data_sample["foo"] = "bar"
450
+ data_sample["matches_pattern"] = "yes!"
451
+ refute_valid
452
+ assert_includes error_messages, %{"foo" is not a permitted key.}
453
+ assert_includes error_types, :invalid_keys
454
+ end
455
+
456
+ it "validates additionalProperties boolean unsuccessfully with multiple failures" do
457
+ pointer("#/definitions/app").merge!(
458
+ "additionalProperties" => false,
459
+ "patternProperties" => {
460
+ "^matches" => {}
461
+ }
462
+ )
463
+ data_sample["foo"] = "bar"
464
+ data_sample["baz"] = "blah"
465
+ data_sample["matches_pattern"] = "yes!"
466
+ refute_valid
467
+ assert_includes error_messages, %{"baz", "foo" are not permitted keys.}
468
+ assert_includes error_types, :invalid_keys
469
+ end
470
+
471
+ it "validates additionalProperties schema successfully" do
472
+ pointer("#/definitions/app").merge!(
473
+ "additionalProperties" => {
474
+ "type" => ["boolean"]
475
+ }
476
+ )
477
+ data_sample["foo"] = true
478
+ assert_valid
479
+ end
480
+
481
+ it "validates additionalProperties schema unsuccessfully" do
482
+ pointer("#/definitions/app").merge!(
483
+ "additionalProperties" => {
484
+ "type" => ["boolean"]
485
+ },
486
+ "patternProperties" => {
487
+ "^matches" => {}
488
+ }
489
+ )
490
+ data_sample["foo"] = 4
491
+ data_sample["matches_pattern"] = "yes!"
492
+ refute_valid
493
+ assert_includes error_messages, %{For 'additionalProperties', 4 is not a boolean.}
494
+ assert_includes error_types, :invalid_type
495
+ end
496
+
497
+ it "validates simple dependencies" do
498
+ pointer("#/definitions/app/dependencies").merge!(
499
+ "production" => "ssl"
500
+ )
501
+ data_sample["production"] = true
502
+ refute_valid
503
+ assert_includes error_messages,
504
+ %{"ssl" wasn't supplied.}
505
+ end
506
+
507
+ it "validates schema dependencies" do
508
+ pointer("#/definitions/app/dependencies").merge!(
509
+ "ssl" => {
510
+ "properties" => {
511
+ "cost" => {
512
+ "minimum" => 20.0,
513
+ }
514
+ }
515
+ }
516
+ )
517
+ data_sample["cost"] = 10.0
518
+ data_sample["ssl"] = true
519
+ refute_valid
520
+ assert_includes error_messages, %{10.0 must be greater than or equal to 20.0.}
521
+ assert_includes error_types, :min_failed
522
+ end
523
+
524
+ it "validates maxProperties" do
525
+ pointer("#/definitions/app").merge!(
526
+ "maxProperties" => 0
527
+ )
528
+ data_sample["name"] = "cloudnasium"
529
+ refute_valid
530
+ assert_includes error_messages, %{No more than 0 properties are allowed; 1 was supplied.}
531
+ assert_includes error_types, :max_properties_failed
532
+ assert_includes error_data, { "name" => "cloudnasium" }
533
+ end
534
+
535
+ it "validates minProperties" do
536
+ pointer("#/definitions/app").merge!(
537
+ "minProperties" => 2
538
+ )
539
+ data_sample["name"] = "cloudnasium"
540
+ refute_valid
541
+ assert_includes error_messages, %{At least 2 properties are required; 1 was supplied.}
542
+ assert_includes error_types, :min_properties_failed
543
+ assert_includes error_data, { "name" => "cloudnasium" }
544
+ end
545
+
546
+ it "validates patternProperties" do
547
+ pointer("#/definitions/app/definitions/config_vars").merge!(
548
+ "patternProperties" => {
549
+ "^\\w+$" => {
550
+ "type" => ["null", "string"]
551
+ }
552
+ }
553
+ )
554
+ data_sample["config_vars"] = {
555
+ "" => 123,
556
+ "KEY" => 456
557
+ }
558
+ refute_valid
559
+ assert_includes error_messages, %{For 'definitions/config_vars', 456 is not a null or string.}
560
+ assert_includes error_types, :invalid_type
561
+ end
562
+
563
+ it "validates patternProperties with missing parent" do
564
+ data_sample["S_0"] = 123
565
+
566
+ refute validate_parentless_pattern
567
+ assert_includes error_messages, %{For 'patternProperties/^S_', 123 is not a string.}
568
+ assert_includes error_types, :invalid_type
569
+ end
570
+
571
+ it "validates required" do
572
+ pointer("#/definitions/app/dependencies").merge!(
573
+ "required" => ["name"]
574
+ )
575
+ data_sample.delete("name")
576
+ refute_valid
577
+ assert_includes error_messages, %{"name" wasn't supplied.}
578
+ assert_includes error_types, :required_failed
579
+ assert_includes error_data, ["name"]
580
+ end
581
+
582
+ it "validates strictProperties successfully" do
583
+ pointer("#/definitions/app").merge!(
584
+ "strictProperties" => false
585
+ )
586
+ assert_valid
587
+ end
588
+
589
+ it "validates strictProperties unsuccessfully" do
590
+ pointer("#/definitions/app").merge!(
591
+ "patternProperties" => {
592
+ "^matches" => {}
593
+ },
594
+ "strictProperties" => true
595
+ )
596
+ data_sample["extra_key"] = "value"
597
+ data_sample["matches_pattern"] = "yes!"
598
+ refute_valid
599
+ missing = @schema.properties.keys.sort - ["name"]
600
+ assert_includes error_messages, %{"#{missing.join('", "')}" weren't supplied.}
601
+ assert_includes error_messages, %{"extra_key" is not a permitted key.}
602
+ assert_includes error_types, :invalid_keys
603
+ end
604
+
605
+ it "validates allOf" do
606
+ pointer("#/definitions/app/definitions/contrived").merge!(
607
+ "allOf" => [
608
+ { "maxLength" => 30 },
609
+ { "minLength" => 3 }
610
+ ]
611
+ )
612
+ data_sample["contrived"] = "ab"
613
+ refute_valid
614
+ assert_includes error_messages, %{Not all subschemas of "allOf" matched.}
615
+ assert_includes error_types, :all_of_failed
616
+ end
617
+
618
+ it "includes the failing condition when validating allOf" do
619
+ pointer("#/definitions/app/definitions/contrived").merge!(
620
+ "allOf" => [
621
+ { "maxLength" => 30 },
622
+ { "minLength" => 3 }
623
+ ]
624
+ )
625
+ data_sample["contrived"] = "ab"
626
+ refute_valid
627
+ assert_includes error_messages, %{At least 3 characters are required; only 2 were supplied.}
628
+ assert_includes error_data, "ab"
629
+ end
630
+
631
+ it "includes all failing conditions for allOf as sub-errors when all_of_sub_errors is true" do
632
+ JsonSchema.configure do |c|
633
+ c.all_of_sub_errors = true
634
+ end
635
+ pointer("#/definitions/app/definitions/contrived").merge!(
636
+ "allOf" => [
637
+ { "minLength" => 5 },
638
+ { "minLength" => 3 }
639
+ ]
640
+ )
641
+ data_sample["contrived"] = "ab"
642
+ refute_valid
643
+ assert_includes error_messages, %{Not all subschemas of "allOf" matched.}
644
+ assert_includes error_types, :all_of_failed
645
+ all_of_error = @validator.errors.find { |error| error.type == :all_of_failed }
646
+ sub_error_messages = all_of_error.sub_errors.map { |errors| errors.map(&:message) }
647
+ sub_error_types = all_of_error.sub_errors.map { |errors| errors.map(&:type) }
648
+ assert_includes sub_error_messages, [%{At least 3 characters are required; only 2 were supplied.}]
649
+ assert_includes sub_error_messages, [%{At least 5 characters are required; only 2 were supplied.}]
650
+ assert_equal sub_error_types, [[:min_length_failed], [:min_length_failed]]
651
+ assert_includes error_data, "ab"
652
+ end
653
+
654
+ it "validates anyOf" do
655
+ pointer("#/definitions/app/definitions/contrived").merge!(
656
+ "anyOf" => [
657
+ { "minLength" => 5 },
658
+ { "minLength" => 3 }
659
+ ]
660
+ )
661
+ data_sample["contrived"] = "ab"
662
+ refute_valid
663
+ assert_includes error_messages, %{No subschema in "anyOf" matched.}
664
+ assert_includes error_types, :any_of_failed
665
+ any_of_error = @validator.errors.find { |error| error.type == :any_of_failed }
666
+ sub_error_messages = any_of_error.sub_errors.map { |errors| errors.map(&:message) }
667
+ sub_error_types = any_of_error.sub_errors.map { |errors| errors.map(&:type) }
668
+ assert_includes sub_error_messages, [%{At least 5 characters are required; only 2 were supplied.}]
669
+ assert_includes sub_error_messages, [%{At least 3 characters are required; only 2 were supplied.}]
670
+ assert_equal sub_error_types, [[:min_length_failed], [:min_length_failed]]
671
+ assert_includes error_data, "ab"
672
+ end
673
+
674
+ it "validates oneOf" do
675
+ pointer("#/definitions/app/definitions/contrived").merge!(
676
+ "oneOf" => [
677
+ { "pattern" => "^(foo|aaa)$" },
678
+ { "pattern" => "^(foo|zzz)$" },
679
+ { "pattern" => "^(hell|no)$" }
680
+ ]
681
+ )
682
+ data_sample["contrived"] = "foo"
683
+ refute_valid
684
+ assert_includes error_messages, %{More than one subschema in "oneOf" matched.}
685
+ assert_includes error_types, :one_of_failed
686
+ one_of_error = @validator.errors.find { |error| error.type == :one_of_failed }
687
+ sub_error_messages = one_of_error.sub_errors.map { |errors| errors.map(&:message) }
688
+ sub_error_types = one_of_error.sub_errors.map { |errors| errors.map(&:type) }
689
+ assert_equal sub_error_messages, [[], [], [%{foo does not match /^(hell|no)$/.}]]
690
+ assert_equal sub_error_types, [[], [], [:pattern_failed]]
691
+ assert_includes error_data, "foo"
692
+ end
693
+
694
+ it "validates not" do
695
+ pointer("#/definitions/app/definitions/contrived").merge!(
696
+ "not" => { "pattern" => "^$" }
697
+ )
698
+ data_sample["contrived"] = ""
699
+ refute_valid
700
+ assert_includes error_messages, %{Matched "not" subschema.}
701
+ assert_includes error_types, :not_failed
702
+ assert_includes error_data, ""
703
+ end
704
+
705
+ it "validates date format successfully" do
706
+ pointer("#/definitions/app/definitions/owner").merge!(
707
+ "format" => "date"
708
+ )
709
+ data_sample["owner"] = "2014-05-13"
710
+ assert_valid
711
+ end
712
+
713
+ it "validates date format unsuccessfully" do
714
+ pointer("#/definitions/app/definitions/owner").merge!(
715
+ "format" => "date"
716
+ )
717
+ data_sample["owner"] = "13/05/2014"
718
+ refute_valid
719
+ end
720
+
721
+ it "validates date-time format successfully" do
722
+ pointer("#/definitions/app/definitions/owner").merge!(
723
+ "format" => "date-time"
724
+ )
725
+ data_sample["owner"] = "2014-05-13T08:42:40Z"
726
+ assert_valid
727
+ end
728
+
729
+ it "validates date-time format with time zone successfully" do
730
+ pointer("#/definitions/app/definitions/owner").merge!(
731
+ "format" => "date-time"
732
+ )
733
+ data_sample["owner"] = "2014-05-13T08:42:40-00:00"
734
+ assert_valid
735
+ end
736
+
737
+ it "validates date-time format with time fraction successfully" do
738
+ pointer("#/definitions/app/definitions/owner").merge!(
739
+ "format" => "date-time"
740
+ )
741
+ data_sample["owner"] = "2014-05-13T08:42:40.444Z"
742
+ assert_valid
743
+ end
744
+
745
+ it "validates date-time format unsuccessfully" do
746
+ pointer("#/definitions/app/definitions/owner").merge!(
747
+ "format" => "date-time"
748
+ )
749
+ data_sample["owner"] = "2014-05-13T08:42:40"
750
+ refute_valid
751
+ assert_includes error_messages, %{2014-05-13T08:42:40 is not a valid date-time.}
752
+ assert_includes error_types, :invalid_format
753
+ assert_includes error_data, "2014-05-13T08:42:40"
754
+ end
755
+
756
+ it "validates email format successfully" do
757
+ pointer("#/definitions/app/definitions/owner").merge!(
758
+ "format" => "email"
759
+ )
760
+ data_sample["owner"] = "dwarf@example.com"
761
+ assert_valid
762
+ end
763
+
764
+ it "validates email format with long TLDs successfully" do
765
+ pointer("#/definitions/app/definitions/owner").merge!(
766
+ "format" => "email"
767
+ )
768
+ data_sample["owner"] = "dwarf@example.technology"
769
+ assert_valid
770
+ end
771
+
772
+ it "validates email format unsuccessfully" do
773
+ pointer("#/definitions/app/definitions/owner").merge!(
774
+ "format" => "email"
775
+ )
776
+ data_sample["owner"] = "@example.com"
777
+ refute_valid
778
+ assert_includes error_messages, %{@example.com is not a valid email.}
779
+ assert_includes error_types, :invalid_format
780
+ end
781
+
782
+ it "validates hostname format successfully" do
783
+ pointer("#/definitions/app/definitions/owner").merge!(
784
+ "format" => "hostname"
785
+ )
786
+ data_sample["owner"] = "example.com"
787
+ assert_valid
788
+ end
789
+
790
+ it "validates hostname format unsuccessfully" do
791
+ pointer("#/definitions/app/definitions/owner").merge!(
792
+ "format" => "hostname"
793
+ )
794
+ data_sample["owner"] = "@example.com"
795
+ refute_valid
796
+ assert_includes error_messages, %{@example.com is not a valid hostname.}
797
+ assert_includes error_types, :invalid_format
798
+ end
799
+
800
+ it "validates ipv4 format successfully" do
801
+ pointer("#/definitions/app/definitions/owner").merge!(
802
+ "format" => "ipv4"
803
+ )
804
+ data_sample["owner"] = "1.2.3.4"
805
+ assert_valid
806
+ end
807
+
808
+ it "validates ipv4 format unsuccessfully" do
809
+ pointer("#/definitions/app/definitions/owner").merge!(
810
+ "format" => "ipv4"
811
+ )
812
+ data_sample["owner"] = "1.2.3.4.5"
813
+ refute_valid
814
+ assert_includes error_messages, %{1.2.3.4.5 is not a valid ipv4.}
815
+ assert_includes error_types, :invalid_format
816
+ end
817
+
818
+ it "validates ipv6 format successfully" do
819
+ pointer("#/definitions/app/definitions/owner").merge!(
820
+ "format" => "ipv6"
821
+ )
822
+ data_sample["owner"] = "1::3:4:5:6:7:8"
823
+ assert_valid
824
+ end
825
+
826
+ it "validates ipv6 format unsuccessfully" do
827
+ pointer("#/definitions/app/definitions/owner").merge!(
828
+ "format" => "ipv6"
829
+ )
830
+ data_sample["owner"] = "1::3:4:5:6:7:8:9"
831
+ refute_valid
832
+ assert_includes error_messages, %{1::3:4:5:6:7:8:9 is not a valid ipv6.}
833
+ assert_includes error_types, :invalid_format
834
+ end
835
+
836
+ it "validates regex format successfully" do
837
+ pointer("#/definitions/app/definitions/owner").merge!(
838
+ "format" => "regex"
839
+ )
840
+ data_sample["owner"] = "^owner@heroku\.com$"
841
+ assert_valid
842
+ end
843
+
844
+ it "validates regex format successfully" do
845
+ pointer("#/definitions/app/definitions/owner").merge!(
846
+ "format" => "regex"
847
+ )
848
+ data_sample["owner"] = "^owner($"
849
+ refute_valid
850
+ assert_includes error_messages, %{^owner($ is not a valid regex.}
851
+ assert_includes error_types, :invalid_format
852
+ end
853
+
854
+ it "validates absolute uri format successfully" do
855
+ pointer("#/definitions/app/definitions/owner").merge!(
856
+ "format" => "uri"
857
+ )
858
+ data_sample["owner"] = "https://example.com"
859
+ assert_valid
860
+ end
861
+
862
+ it "validates relative uri format successfully" do
863
+ pointer("#/definitions/app/definitions/owner").merge!(
864
+ "format" => "uri"
865
+ )
866
+ data_sample["owner"] = "schemata/app"
867
+ assert_valid
868
+ end
869
+
870
+ it "validates uri format unsuccessfully" do
871
+ pointer("#/definitions/app/definitions/owner").merge!(
872
+ "format" => "uri"
873
+ )
874
+ data_sample["owner"] = "http://example.com[]"
875
+ refute_valid
876
+ assert_includes error_messages, %{http://example.com[] is not a valid uri.}
877
+ assert_includes error_types, :invalid_format
878
+ end
879
+
880
+ it "validates absolute uri-reference format successfully" do
881
+ pointer("#/definitions/app/definitions/owner").merge!(
882
+ "format" => "uri-reference"
883
+ )
884
+ data_sample["owner"] = "https://example.com"
885
+ assert_valid
886
+ end
887
+
888
+ it "validates relative uri format successfully" do
889
+ pointer("#/definitions/app/definitions/owner").merge!(
890
+ "format" => "uri"
891
+ )
892
+ data_sample["owner"] = "#hello"
893
+ assert_valid
894
+ end
895
+
896
+ it "validates uri format unsuccessfully" do
897
+ pointer("#/definitions/app/definitions/owner").merge!(
898
+ "format" => "uri-reference"
899
+ )
900
+ data_sample["owner"] = "http://example.com[]"
901
+ refute_valid
902
+ assert_includes error_messages, %{http://example.com[] is not a valid uri-reference.}
903
+ assert_includes error_types, :invalid_format
904
+ end
905
+
906
+ it "validates uuid format successfully" do
907
+ pointer("#/definitions/app/definitions/owner").merge!(
908
+ "format" => "uuid"
909
+ )
910
+ data_sample["owner"] = "01234567-89ab-cdef-0123-456789abcdef"
911
+ assert_valid
912
+ end
913
+
914
+ it "validates uuid format unsuccessfully" do
915
+ pointer("#/definitions/app/definitions/owner").merge!(
916
+ "format" => "uuid"
917
+ )
918
+ data_sample["owner"] = "123"
919
+ refute_valid
920
+ assert_includes error_messages, %{123 is not a valid uuid.}
921
+ assert_includes error_types, :invalid_format
922
+ end
923
+
924
+ it "validates maxLength" do
925
+ pointer("#/definitions/app/definitions/name").merge!(
926
+ "maxLength" => 3
927
+ )
928
+ data_sample["name"] = "abcd"
929
+ refute_valid
930
+ assert_includes error_messages, %{Only 3 characters are allowed; 4 were supplied.}
931
+ assert_includes error_types, :max_length_failed
932
+ end
933
+
934
+ it "validates minLength" do
935
+ pointer("#/definitions/app/definitions/name").merge!(
936
+ "minLength" => 3
937
+ )
938
+ data_sample["name"] = "ab"
939
+ refute_valid
940
+ assert_includes error_messages, %{At least 3 characters are required; only 2 were supplied.}
941
+ assert_includes error_types, :min_length_failed
942
+ end
943
+
944
+ it "validates pattern" do
945
+ pointer("#/definitions/app/definitions/name").merge!(
946
+ "pattern" => "^[a-z][a-z0-9-]{3,30}$",
947
+ )
948
+ data_sample["name"] = "ab"
949
+ refute_valid
950
+ assert_includes error_messages, %{ab does not match /^[a-z][a-z0-9-]{3,30}$/.}
951
+ assert_includes error_types, :pattern_failed
952
+ assert_includes error_data, "ab"
953
+ end
954
+
955
+ it "builds appropriate JSON Pointers to bad data" do
956
+ pointer("#/definitions/app/definitions/visibility").merge!(
957
+ "enum" => ["private", "public"]
958
+ )
959
+ data_sample["visibility"] = "personal"
960
+ refute_valid
961
+ assert_equal "#/visibility", @validator.errors[0].pointer
962
+ end
963
+
964
+ =begin
965
+ it "handles a validation loop" do
966
+ pointer("#/definitions/app").merge!(
967
+ "not" => { "$ref" => "#/definitions/app" }
968
+ )
969
+ data_sample["visibility"] = "personal"
970
+ refute_valid
971
+ assert_includes error_messages, %{Validation loop detected.}
972
+ end
973
+ =end
974
+
975
+ it "validates custom formats successfully" do
976
+ JsonSchema.configure do |c|
977
+ c.register_format "the-answer", ->(data) { data.to_i == 42 }
978
+ end
979
+ pointer("#/definitions/app/definitions/owner").merge!(
980
+ "format" => "the-answer"
981
+ )
982
+ data_sample["owner"] = "42"
983
+ assert_valid
984
+ end
985
+
986
+ it "validates custom formats unsuccessfully" do
987
+ JsonSchema.configure do |c|
988
+ c.register_format "the-answer", ->(data) { data.to_i == 42 }
989
+ end
990
+ pointer("#/definitions/app/definitions/owner").merge!(
991
+ "format" => "the-answer"
992
+ )
993
+ data_sample["owner"] = "43"
994
+ refute_valid
995
+ assert_includes error_messages, %{43 is not a valid the-answer.}
996
+ assert_includes error_types, :invalid_format
997
+ end
998
+
999
+ it "raises an aggregate error with validate!" do
1000
+ pointer("#/definitions/app").merge!(
1001
+ "type" => ["object"]
1002
+ )
1003
+
1004
+ schema = JsonSchema.parse!(schema_sample)
1005
+ schema.expand_references!
1006
+ schema = schema.definitions["app"]
1007
+ validator = JsonSchema::Validator.new(schema)
1008
+
1009
+ # don't bother checking the particulars of the error here because we have
1010
+ # other tests for that above
1011
+ assert_raises JsonSchema::AggregateError do
1012
+ validator.validate!(4)
1013
+ end
1014
+ end
1015
+
1016
+ def data_sample
1017
+ @data_sample ||= DataScaffold.data_sample
1018
+ end
1019
+
1020
+ def error_messages
1021
+ @validator.errors.map(&:message)
1022
+ end
1023
+
1024
+ def error_data
1025
+ @validator.errors.map(&:data)
1026
+ end
1027
+
1028
+ def error_types
1029
+ @validator.errors.map(&:type)
1030
+ end
1031
+
1032
+ def pointer(path)
1033
+ JsonPointer.evaluate(schema_sample, path)
1034
+ end
1035
+
1036
+ def validate_parentless_pattern
1037
+ schema = {
1038
+ "$schema" => "http://json-schema.org/draft-04/hyper-schema",
1039
+ "patternProperties" => {
1040
+ "^S_" => {
1041
+ "type" => [
1042
+ "string"
1043
+ ]
1044
+ }
1045
+ }
1046
+ }
1047
+ schema = JsonSchema.parse!(schema)
1048
+ @validator = JsonSchema::Validator.new(schema)
1049
+ @validator.validate(data_sample)
1050
+ end
1051
+
1052
+ def schema_sample
1053
+ @schema_sample ||= DataScaffold.schema_sample
1054
+ end
1055
+
1056
+ def validator
1057
+ @schema = JsonSchema.parse!(schema_sample)
1058
+ @schema.expand_references!
1059
+ @schema = @schema.definitions["app"]
1060
+ JsonSchema::Validator.new(@schema)
1061
+ end
1062
+
1063
+ # assert_valid asserts that both the "fail fast" and the "full error messages"
1064
+ # code paths consider the data sample valid for the set schema.
1065
+ def assert_valid
1066
+ @validator = validator
1067
+ assert @validator.validate(data_sample, fail_fast: true)
1068
+ assert @validator.validate(data_sample, fail_fast: false)
1069
+ end
1070
+
1071
+ # refute_valid asserts that both the "fail fast" and the "full error messages"
1072
+ # code paths consider the data sample erroneous for the set schema.
1073
+ def refute_valid
1074
+ @validator = validator
1075
+ refute @validator.validate(data_sample, fail_fast: true)
1076
+ refute @validator.validate(data_sample, fail_fast: false)
1077
+ end
1078
+ end