• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

notEthan / jsi / 10443391104

29 Jul 2024 09:51PM UTC coverage: 97.876% (-0.2%) from 98.102%
10443391104

push

github

notEthan
Merge branches 'elements', 'dialect_invoke_each', 'cxt_keyword_type', 'elements.interdependencies', 'msn_initialize_bootstrap_finalize', 'schema_module_overrides_sub_applicators.reverse_include' and 'misc811' into HEAD

143 of 163 new or added lines in 21 files covered. (87.73%)

16 existing lines in 7 files now uncovered.

5899 of 6027 relevant lines covered (97.88%)

111727.63 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.45
/test/unsupported_test.rb
1
# frozen_string_literal: true
2

3
require_relative 'test_helper'
14✔
4

5
# the behaviors described in these tests are not officially supported.
6
# behaviors are tested so I know if any of it breaks, but unsupported behavior may change at any time.
7

8
describe 'unsupported behavior' do
14✔
9
  let(:schema_opt) { {} }
140✔
10
  let(:schema) { JSI.new_schema(schema_content, default_metaschema: JSI::JSONSchemaDraft07, **schema_opt) }
154✔
11
  let(:instance) { {} }
14✔
12
  let(:subject_opt) { {} }
112✔
13
  let(:subject) { schema.new_jsi(instance, **subject_opt) }
154✔
14

15
  describe 'JSI::Schema' do
14✔
16
    # reinstantiating objects at unrecognized paths as schemas is implemented but I don't want to officially
17
    # support it. the spec states that the behavior is undefined, and the code implementing it is brittle,
18
    # ugly, and prone to breakage, particularly with $id.
19
    describe 'reinstantiation' do
14✔
20
      describe 'below another schema' do
14✔
21
        let(:schema_content) do
14✔
22
          YAML.safe_load(<<~YAML
14✔
23
            definitions:
24
              a:
25
                $id: http://jsi/test/reinstantiation/below_another/a
26
                definitions:
27
                  sub:
28
                    {}
29
                unknown:
30
                  definitions:
31
                    b:
32
                      additionalProperties:
33
                        $ref: "#/definitions/sub"
34
            items:
35
              $ref: "#/definitions/a/unknown/definitions/b"
36
            YAML
37
          )
38
        end
39
        let(:instance) do
14✔
40
          [{'x' => {}}]
14✔
41
        end
42
        it "instantiates" do
14✔
43
          assert_equal(
16✔
44
            [JSI::Ptr["definitions", "a", "unknown", "definitions", "b"]],
45
            subject[0].jsi_schemas.map(&:jsi_ptr)
46
          )
47
          assert_equal(
16✔
48
            [JSI::Ptr["definitions", "a", "definitions", "sub"]],
49
            subject[0]['x'].jsi_schemas.map(&:jsi_ptr)
50
          )
51
        end
52
      end
53

54
      describe("in a location described by the metaschema as a nonschema") do
14✔
55
        let(:schema_content) do
14✔
56
          YAML.safe_load(<<~YAML
14✔
57
            const: true
58
            properties:
59
              additionalProperties:
60
                $ref: "#/const"
61
            items:
62
              $ref: "#/properties"
63
            YAML
64
          )
65
        end
66
        let(:instance) do
14✔
67
          [{'x' => {}}]
14✔
68
        end
69

70
        it("instantiates") do
14✔
71
          assert_equal(
16✔
72
            [JSI::Ptr["properties"]],
73
            subject[0].jsi_schemas.map(&:jsi_ptr)
74
          )
75
          # the schema that describes subject[0] is both a schema and a 'properties' object
76
          assert_schemas(
16✔
77
            [
78
              JSI::JSONSchemaOrgDraft07.schema,
79
              JSI::JSONSchemaOrgDraft07.schema.properties['properties'],
80
            ],
81
            subject[0].jsi_schemas.first
82
          )
83
          assert_equal(
16✔
84
            [JSI::Ptr["const"]],
85
            subject[0]['x'].jsi_schemas.map(&:jsi_ptr)
86
          )
87
          # the schema that describes subject[0]['x'] is both a schema and a 'const' value
88
          assert_schemas(
16✔
89
            [
90
              JSI::JSONSchemaOrgDraft07.schema,
91
              JSI::JSONSchemaOrgDraft07.schema.properties['const'],
92
            ],
93
            subject[0]['x'].jsi_schemas.first
94
          )
95
        end
96
      end
97

98
      describe 'below nonschema root' do
14✔
99
        it "instantiates" do
14✔
100
          schema_doc_schema = JSI::JSONSchemaDraft04.new_schema({
14✔
101
            'properties' => {'schema' => {'$ref' => 'http://json-schema.org/draft-04/schema'}}
102
          })
103
          schema_doc = schema_doc_schema.new_jsi({
14✔
104
            'schema' => {'$ref' => '#/unknown'},
105
            'unknown' => {},
106
          })
107
          subject = schema_doc.schema.new_jsi({})
14✔
108
          assert_equal(
16✔
109
            [JSI::Ptr["unknown"]],
110
            subject.jsi_schemas.map(&:jsi_ptr)
111
          )
112
        end
113
      end
114
      describe 'the origin schema has schemas that do not describe a schema (items)' do
14✔
115
        let(:schema_content) do
14✔
116
          {
4✔
117
            'items' => {'$ref' => '#/unknown'},
6✔
118
            'unknown' => {},
119
          }
2✔
120
        end
121
        let(:instance) do
14✔
122
          [{}]
14✔
123
        end
124
        it "instantiates" do
14✔
125
          assert_equal(
16✔
126
            [JSI::Ptr["unknown"]],
127
            subject[0].jsi_schemas.map(&:jsi_ptr)
128
          )
129
          unknown_schema = subject[0].jsi_schemas.to_a[0]
14✔
130
          # check it's not an items schema like schema.items is
131
          assert_equal(schema.jsi_schemas, unknown_schema.jsi_schemas)
14✔
132
          refute_equal(schema.items.jsi_schemas, unknown_schema.jsi_schemas)
14✔
133
        end
134
      end
135
    end
136
  end
137

138
  describe 'property names which are not strings' do
14✔
139
    ARBITRARY_OBJECT = Object.new
14✔
140
    describe 'arbitrary object property name' do
14✔
141
      let(:schema_content) do
14✔
142
        {
4✔
143
          'properties' => {
6✔
144
            ARBITRARY_OBJECT => {'type' => 'string'},
145
          },
146
        }
2✔
147
      end
148
      let(:schema_opt) { {to_immutable: nil} }
28✔
149
      let(:instance) do
14✔
150
        {
4✔
151
          ARBITRARY_OBJECT => {},
6✔
152
        }
2✔
153
      end
154
      let(:subject_opt) { {to_immutable: nil} }
28✔
155

156
      it 'applies properties' do
14✔
157
        assert_schemas([schema.properties[ARBITRARY_OBJECT]], subject[ARBITRARY_OBJECT])
14✔
158
        assert(!subject.jsi_valid?)
14✔
159
        assert(!subject[ARBITRARY_OBJECT].jsi_valid?)
14✔
160
      end
161
    end
162
    describe 'property name which is an array, described by propertyNames' do
14✔
163
      let(:schema_content) do
14✔
164
        {
8✔
165
          'properties' => {
12✔
166
            [1] => {},
167
          },
2✔
168
          'propertyNames' => {
169
            'type' => 'array',
170
            'items' => {'type' => 'integer'},
171
          },
172
        }
4✔
173
      end
174
      describe 'valid' do
14✔
175
        let(:instance) do
14✔
176
          {
4✔
177
            [] => {},
6✔
178
            [1] => {},
179
          }
2✔
180
        end
181

182
        it 'applies properties, propertyNames' do
14✔
183
          assert_schemas([schema.properties[[1]]], subject[[1]])
14✔
184
          assert_schemas([], subject[[]])
14✔
185

186
          assert(subject.jsi_valid?)
14✔
187

188
          subject.jsi_each_propertyName do |propertyName|
14✔
189
            assert_schemas([schema.propertyNames], propertyName)
28✔
190
          end
191
          # child application of propertyNames' `items` subschema
192
          pn_item = subject.jsi_each_propertyName.detect { |j| j.size > 0 }[0, as_jsi: true]
42✔
193
          assert_schemas([schema.propertyNames.items], pn_item)
14✔
194

195
          assert(subject.jsi_each_propertyName.to_a.all?(&:jsi_valid?))
14✔
196

197
          exp_jsis = [schema.propertyNames.new_jsi([]), schema.propertyNames.new_jsi([1])]
14✔
198
          assert_equal(exp_jsis, subject.jsi_each_propertyName.to_a) # this test seems unnecessary, w/e
14✔
199
        end
200
      end
201
      describe 'invalid' do
14✔
202
        let(:instance) do
14✔
203
          {
4✔
204
            [] => {},
6✔
205
            [1] => {},
206
            {} => {},
207
          }
2✔
208
        end
209

210
        it 'applies properties, propertyNames' do
14✔
211
          assert_schemas([schema.properties[[1]]], subject[[1]])
14✔
212
          assert_schemas([], subject[[]])
14✔
213

214
          assert_equal([
16✔
215
            "instance type does not match `type` value",
216
            "instance object property names are not all valid against `propertyNames` schema",
217
          ], subject.jsi_validate.each_validation_error.map(&:message))
218

219
          subject.jsi_each_propertyName do |propertyName|
14✔
220
            assert_schemas([schema.propertyNames], propertyName)
42✔
221
          end
222

223
          valid, invalid = subject.jsi_each_propertyName.partition(&:jsi_valid?)
14✔
224
          assert_equal([[], [1]], valid.map(&:jsi_instance))
14✔
225
          assert_equal([{}], invalid.map(&:jsi_instance))
14✔
226
        end
227
      end
228
    end
229

230
    describe '#jsi_each_descendent_node(propertyNames: true)' do
14✔
231
      DescPropNamesTest = JSI::JSONSchemaDraft07.new_schema_module({
14✔
232
        'patternProperties' => {
233
          '^n' => {'title' => 'no'}, # 'no' properties don't recurse
234
        },
235
        'additionalProperties' => {'$ref' => '#'}, # other properties do
236
        'propertyNames' => {
237
          'items' => [{}], # first item doesn't recurse
238
          'additionalItems' => {'$ref' => '#'}, # rest of items do
239
        }
240
      })
241

242
      let(:schema) do
14✔
243
        DescPropNamesTest.schema
14✔
244
      end
245

246
      let(:yesno_object) { {'y' => {'a' => 0}, 'n' => {'a' => 0}} }
28✔
247
      let(:ary01) { [yesno_object, yesno_object] }
28✔
248
      let(:instance) do
14✔
249
        {
4✔
250
          ary01 => yesno_object,
6✔
251
          'no' => ary01,
252
        }
2✔
253
      end
254

255
      it "yields descendent JSIs and propertyNames" do
14✔
256
        expected_nodes = Set[]
14✔
257
        expected_nodes << subject
14✔
258
        subject_key_ary01 = schema.propertyNames.new_jsi(ary01)
14✔
259
        expected_nodes += [ # recursing the the first propertyName
10✔
260
          subject_key_ary01,
261
          subject_key_ary01 / [0],
262
          JSI::SchemaSet[].new_jsi('y'), # (subject key ary01) / [0] key 'y'
263
          JSI::SchemaSet[].new_jsi('n'), # (subject key ary01) / [0] key 'n'
264
          subject_key_ary01 / [0, 'y'],
265
          JSI::SchemaSet[].new_jsi('a'), # (subject key ary01) / [0, 'y'] key 'a'
266
          subject_key_ary01 / [0, 'y', 'a'],
267
          subject_key_ary01 / [0, 'n'],
268
          JSI::SchemaSet[].new_jsi('a'), # (subject key ary01) / [0, 'n'] key 'a'
269
          subject_key_ary01 / [0, 'n', 'a'],
270
          subject_key_ary01 / [1],
271
          schema.propertyNames.new_jsi('y'), # (subject key ary01) / [1] key 'y'
272
          schema.propertyNames.new_jsi('n'), # (subject key ary01) / [1] key 'n'
273
          subject_key_ary01 / [1, 'y'],
274
          schema.propertyNames.new_jsi('a'), # (subject key ary01) / [1, 'y'] key 'a'
275
          subject_key_ary01 / [1, 'y', 'a'],
276
          subject_key_ary01 / [1, 'n'],
277
          JSI::SchemaSet[].new_jsi('a'), # (subject key ary01) / [1, 'n'] key 'a'
278
          subject_key_ary01 / [1, 'n', 'a'],
279
        ]
280
        expected_nodes << schema.propertyNames.new_jsi('no') # second propertyName (nothing to recurse)
14✔
281
        expected_nodes += [ # recursing the the first property value
10✔
282
          subject / [ary01],
283
          schema.propertyNames.new_jsi('y'), # subject / [ary01] key 'y'
284
          schema.propertyNames.new_jsi('n'), # subject / [ary01] key 'n'
285
          subject / [ary01, 'y'],
286
          schema.propertyNames.new_jsi('a'), # subject / [ary01, 'y'] key 'a'
287
          subject / [ary01, 'y', 'a'],
288
          subject / [ary01, 'n'],
289
          JSI::SchemaSet[].new_jsi('a'), # subject / [ary01, 'n'] key 'a'
290
          subject / [ary01, 'n', 'a'],
291
        ]
292
        expected_nodes += [ # recursing the the second property value
10✔
293
          subject / ['no'],
294
          subject / ['no', 0],
295
          JSI::SchemaSet[].new_jsi('y'), # subject / ['no', 0] key 'y'
296
          JSI::SchemaSet[].new_jsi('n'), # subject / ['no', 0] key 'n'
297
          subject / ['no', 0, 'y'],
298
          JSI::SchemaSet[].new_jsi('a'), # subject / ['no', 0, 'y'] key 'a'
299
          subject / ['no', 0, 'y', 'a'],
300
          subject / ['no', 0, 'n'],
301
          JSI::SchemaSet[].new_jsi('a'), # subject / ['no', 0, 'n'] key 'a'
302
          subject / ['no', 0, 'n', 'a'],
303
          subject / ['no', 1],
304
          JSI::SchemaSet[].new_jsi('y'), # subject / ['no', 1] key 'y'
305
          JSI::SchemaSet[].new_jsi('n'), # subject / ['no', 1] key 'n'
306
          subject / ['no', 1, 'y'],
307
          JSI::SchemaSet[].new_jsi('a'), # subject / ['no', 1, 'y'] key 'a'
308
          subject / ['no', 1, 'y', 'a'],
309
          subject / ['no', 1, 'n'],
310
          JSI::SchemaSet[].new_jsi('a'), # subject / ['no', 1, 'n'] key 'a'
311
          subject / ['no', 1, 'n', 'a'],
312
        ]
313
        assert_equal(expected_nodes, subject.jsi_each_descendent_node(propertyNames: true).to_set)
14✔
314
      end
315
    end
316
  end
317

318
  describe 'an instance that responds to to_hash and to_ary' do
14✔
319
    class HashlikeAry
14✔
320
      def initialize(ary)
14✔
321
        @ary = ary
28✔
322
      end
323

324
      def to_hash
14✔
325
        @ary.each_with_index.map { |e, i| {i => e} }.inject({}, &:update)
1,176✔
326
      end
327

328
      def to_ary
14✔
329
        @ary
280✔
330
      end
331

332
      def each_index(&b)
14✔
333
        @ary.each_index(&b)
14✔
334
      end
335

336
      def keys
14✔
UNCOV
337
        @ary.each_index.to_a
×
338
      end
339

340
      def each_key(&b)
14✔
341
        @ary.each_index(&b)
98✔
342
      end
343
    end
344

345
    let(:subject_opt) { {to_immutable: nil} }
42✔
346

347
    describe 'properties and items' do
14✔
348
      let(:schema_content) do
14✔
349
        {
4✔
350
          'properties' => {
6✔
351
            0 => {},
352
          },
353
          'items' => {
354
          },
355
        }
2✔
356
      end
357
      let(:instance) do
14✔
358
        HashlikeAry.new([{}])
14✔
359
      end
360

361
      it 'applies properties and itmes' do
14✔
362
        assert_schemas([schema.properties[0], schema.items], subject[0])
14✔
363
        assert_is_a(Hash, subject.to_hash)
14✔
364
        assert_is_a(Array, subject.to_ary)
14✔
365
      end
366
    end
367

368
    describe 'additionalProperties, patternProperties, additionalItems' do
14✔
369
      let(:schema_content) do
14✔
370
        {
4✔
371
          'properties' => {
6✔
372
            0 => {},
373
          },
374
          'patternProperties' => {
375
            '1' => {}
376
          },
377
          'additionalProperties' => {},
378
          'items' => [
379
            {},
380
          ],
381
          'additionalItems' => {},
382
        }
2✔
383
      end
384

385
      let(:instance) do
14✔
386
        HashlikeAry.new([{}, {}, {}])
14✔
387
      end
388

389
      it 'applies' do
14✔
390
        assert_schemas([schema.properties[0],         schema.items[0]],        subject[0])
14✔
391
        assert_schemas([schema.patternProperties['1'], schema.additionalItems], subject[1])
14✔
392
        assert_schemas([schema.additionalProperties,  schema.additionalItems], subject[2])
14✔
393
        assert(subject.jsi_valid?)
14✔
394
        assert_is_a(Hash, subject.to_hash)
14✔
395
        assert_is_a(Array, subject.to_ary)
14✔
396
      end
397
    end
398
  end
399

400
  describe 'recursive structures' do
14✔
401
    describe 'a instance whose child references itself' do
14✔
402
      let(:schema_content) do
14✔
403
        YAML.load(<<~YAML
14✔
404
          properties:
405
            "a": {}
406
            "on":
407
              $ref: "#"
408
          YAML
409
        )
410
      end
411
      it 'goes all the way down' do
14✔
412
        child = {'a' => ['turtle']}
14✔
413
        child['on'] = child
10✔
414
        root = {'a' => ['world'], 'on' => child}
14✔
415
        jsi = schema.new_jsi(root, to_immutable: nil)
14✔
416
        assert_schemas([schema.properties['a']], jsi.a)
14✔
417
        assert_schemas([schema], jsi.on)
14✔
418
        # little deeper
419
        deep_parent_ptr = JSI::Ptr['on', 'on', 'on', 'on', 'on', 'on', 'on', 'on', 'on', 'on', 'on', 'on', 'on', 'on']
14✔
420
        assert_schemas([schema.properties['a']], deep_parent_ptr.evaluate(jsi).a)
14✔
421
        assert_schemas([schema], deep_parent_ptr.evaluate(jsi))
14✔
422

423
        # lul
424
        #assert_raises(SystemStackError) do
425
        #  jsi.jsi_each_descendent_node { }
426
        #end
427
      end
428
    end
429
  end
430

431
  describe 'conflicting JSI Schema Module instance methods' do
14✔
432
    let(:schema_content) do
14✔
433
      YAML.safe_load(<<~YAML
14✔
434
        definitions:
435
          a:
436
            {}
437
          b:
438
            {}
439
        allOf:
440
          - $ref: "#/definitions/a"
441
          - $ref: "#/definitions/b"
442
        YAML
443
      )
444
    end
445
    let(:instance) do
14✔
446
      {}
14✔
447
    end
448
    it "defines both; an undefined one wins" do
14✔
449
      schema.definitions['a'].jsi_schema_module_exec { define_method(:foo) { :a } }
40✔
450
      schema.definitions['b'].jsi_schema_module_exec { define_method(:foo) { :b } }
28✔
451
      assert_includes([:a, :b], subject.foo)
14✔
452
    end
453
  end
454
end
455

456
$test_report_file_loaded[__FILE__]
14✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc