json_matchers 0.9.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,10 @@ module JsonMatchers
4
4
  @payload = extract_json_string(payload)
5
5
  end
6
6
 
7
+ def as_json
8
+ JSON.parse(payload)
9
+ end
10
+
7
11
  def to_s
8
12
  payload
9
13
  end
@@ -5,20 +5,8 @@ module JsonMatchers
5
5
  self.schema_root = File.join("spec", "support", "api", "schemas")
6
6
  end
7
7
 
8
- RSpec::Matchers.define :match_json_schema do |schema_name, **options|
9
- if options.present?
10
- warn <<-WARN
11
- DEPRECATION:
12
-
13
- After `json_matchers@0.9.x`, calls to `match_json_schema` and
14
- `match_response_schema` will no longer accept options.
15
-
16
- See https://github.com/thoughtbot/json_matchers/pull/31 for more information.
17
-
18
- WARN
19
- end
20
-
21
- assertion = JsonMatchers::Assertion.new(schema_name.to_s, options)
8
+ RSpec::Matchers.define :match_json_schema do |schema_name|
9
+ assertion = JsonMatchers::Assertion.new(schema_name.to_s)
22
10
 
23
11
  match do |json|
24
12
  assertion.valid?(json)
@@ -1,37 +1,34 @@
1
- require "json-schema"
1
+ require "json_matchers/parser"
2
2
 
3
3
  module JsonMatchers
4
4
  class Validator
5
- def initialize(options:, payload:, schema_path:)
6
- @options = options.dup
7
- @payload = payload
8
- @schema_path = schema_path.to_s
5
+ def initialize(document_store:, schema_path:)
6
+ @document_store = document_store
7
+ @schema_path = schema_path
9
8
  end
10
9
 
11
- def validate!
12
- if recording_errors?
13
- validate_recording_errors
14
- else
15
- validate
16
- end
10
+ def validate(payload)
11
+ json_schema.validate!(payload.as_json, fail_fast: true)
12
+
13
+ []
14
+ rescue JsonSchema::Error => error
15
+ [error.message]
17
16
  end
18
17
 
19
18
  private
20
19
 
21
- attr_reader :options, :payload, :schema_path
20
+ attr_reader :document_store, :schema_path
22
21
 
23
- def recording_errors?
24
- options.fetch(:record_errors, false)
22
+ def json_schema
23
+ @json_schema ||= build_json_schema_with_expanded_references
25
24
  end
26
25
 
27
- def validate_recording_errors
28
- JSON::Validator.fully_validate(schema_path, payload, options)
29
- end
26
+ def build_json_schema_with_expanded_references
27
+ json_schema = Parser.new(schema_path).parse
30
28
 
31
- def validate
32
- JSON::Validator.validate!(schema_path, payload, options)
29
+ json_schema.expand_references!(store: document_store)
33
30
 
34
- []
31
+ json_schema
35
32
  end
36
33
  end
37
34
  end
@@ -1,3 +1,3 @@
1
1
  module JsonMatchers
2
- VERSION = "0.9.0".freeze
2
+ VERSION = "0.10.0".freeze
3
3
  end
@@ -6,12 +6,12 @@ FactoryBot.define do
6
6
  factory :response, class: FakeResponse do
7
7
  skip_create
8
8
 
9
- trait :object do
10
- body { { "id": 1 } }
9
+ trait :location do
10
+ body { { "latitude": 1, "longitude": 1 } }
11
11
  end
12
12
 
13
- trait :invalid_object do
14
- body { { "id": "1" } }
13
+ trait :invalid_location do
14
+ body { { "latitude": "1", "longitude": "1" } }
15
15
  end
16
16
 
17
17
  initialize_with do
@@ -24,29 +24,32 @@ FactoryBot.define do
24
24
  end
25
25
 
26
26
  factory :schema, class: FakeSchema do
27
- skip_create
28
-
29
27
  sequence(:name) { |n| "json_schema-#{n}" }
30
28
 
31
29
  trait :invalid do
32
30
  json { "" }
33
31
  end
34
32
 
35
- trait :object do
33
+ trait :location do
36
34
  json do
37
35
  {
36
+ "id": "file:/#{name}.json#",
37
+ "description": "An object containing some #{name} data",
38
38
  "type": "object",
39
- "required": [
40
- "id",
41
- ],
39
+ "required": ["latitude", "longitude"],
42
40
  "properties": {
43
- "id": { "type": "number" },
41
+ "latitude": {
42
+ "type": "number",
43
+ },
44
+ "longitude": {
45
+ "type": "number",
46
+ },
44
47
  },
45
48
  "additionalProperties": false,
46
49
  }
47
50
  end
48
51
  end
49
- trait(:objects) { object }
52
+ trait(:locations) { location }
50
53
 
51
54
  trait :array_of do
52
55
  initialize_with do
@@ -60,17 +63,49 @@ FactoryBot.define do
60
63
  end
61
64
  end
62
65
 
63
- trait :referencing_objects do
64
- association :items, factory: [:schema, :object]
66
+ trait :referencing_locations do
67
+ association :items, factory: [:schema, :location]
65
68
 
66
69
  initialize_with do
67
70
  FakeSchema.new(name, {
71
+ "$schema": "https://json-schema.org/draft-04/schema#",
68
72
  "type": "array",
69
- "items": { "$ref": "#{items.name}.json" },
73
+ "items": { "$ref": "file:/#{items.name}.json#" },
70
74
  })
71
75
  end
72
76
  end
73
77
 
78
+ trait :referencing_definitions do
79
+ association :items, factory: [:schema, :location], name: "location"
80
+ association :example, factory: [:response, :location]
81
+
82
+ transient do
83
+ plural { items.name.pluralize }
84
+ end
85
+
86
+ json do
87
+ {
88
+ "$schema": "https://json-schema.org/draft-04/schema#",
89
+ "id": "file:/#{name}.json#",
90
+ "type": "object",
91
+ "definitions": {
92
+ plural => {
93
+ "type": "array",
94
+ "items": { "$ref": "file:/#{items.name}.json#" },
95
+ "description": "A collection of #{plural}",
96
+ "example": example,
97
+ },
98
+ },
99
+ "required": [plural],
100
+ "properties": {
101
+ plural => {
102
+ "$ref": "#/definitions/#{plural}",
103
+ },
104
+ },
105
+ }
106
+ end
107
+ end
108
+
74
109
  initialize_with do
75
110
  schema_body_as_json = attributes.fetch(:json, nil)
76
111
  schema_body = attributes.except(:json, :name)
@@ -78,10 +113,11 @@ FactoryBot.define do
78
113
  FakeSchema.new(name, schema_body_as_json || schema_body)
79
114
  end
80
115
 
81
- after :create do |schema|
82
- path = File.join(JsonMatchers.schema_root, "#{schema.name}.json")
116
+ to_create do |schema|
117
+ path = JsonMatchers.path_to_schema(schema.name)
83
118
  payload = JsonMatchers::Payload.new(schema.json)
84
119
 
120
+ path.dirname.mkpath
85
121
  IO.write(path, payload)
86
122
  end
87
123
  end
@@ -20,51 +20,59 @@ describe JsonMatchers, "#match_json_schema" do
20
20
  end
21
21
 
22
22
  it "supports asserting with the match_response_schema alias" do
23
- schema = create(:schema, :object)
23
+ schema = create(:schema, :location)
24
24
 
25
- json = build(:response, :invalid_object)
25
+ json = build(:response, :invalid_location)
26
26
 
27
27
  expect(json).not_to match_response_schema(schema)
28
28
  end
29
29
 
30
30
  it "supports refuting with the match_response_schema alias" do
31
- schema = create(:schema, :object)
31
+ schema = create(:schema, :location)
32
32
 
33
- json = build(:response, :invalid_object)
33
+ json = build(:response, :invalid_location)
34
34
 
35
35
  expect(json).not_to match_response_schema(schema)
36
36
  end
37
37
 
38
38
  it "fails when the body contains a property with the wrong type" do
39
- schema = create(:schema, :object)
39
+ schema = create(:schema, :location)
40
40
 
41
- json = build(:response, :invalid_object)
41
+ json = build(:response, :invalid_location)
42
42
 
43
43
  expect(json).not_to match_json_schema(schema)
44
44
  end
45
45
 
46
46
  it "fails when the body is missing a required property" do
47
- schema = create(:schema, :object)
47
+ schema = create(:schema, :location)
48
48
 
49
49
  json = build(:response, {})
50
50
 
51
51
  expect(json).not_to match_json_schema(schema)
52
52
  end
53
53
 
54
+ it "can reference a schema in a directory" do
55
+ create(:schema, :location, name: "api/v1/schema")
56
+
57
+ json = build(:response, :location)
58
+
59
+ expect(json).to match_json_schema("api/v1/schema")
60
+ end
61
+
54
62
  context "when passed a Hash" do
55
63
  it "validates that the schema matches" do
56
- schema = create(:schema, :object)
64
+ schema = create(:schema, :location)
57
65
 
58
- json = build(:response, :object)
66
+ json = build(:response, :location)
59
67
  json_as_hash = json.to_h
60
68
 
61
69
  expect(json_as_hash).to match_json_schema(schema)
62
70
  end
63
71
 
64
72
  it "fails with message when negated" do
65
- schema = create(:schema, :object)
73
+ schema = create(:schema, :location)
66
74
 
67
- json = build(:response, :invalid_object)
75
+ json = build(:response, :invalid_location)
68
76
  json_as_hash = json.to_h
69
77
 
70
78
  expect {
@@ -75,27 +83,27 @@ describe JsonMatchers, "#match_json_schema" do
75
83
 
76
84
  context "when passed a Array" do
77
85
  it "validates a root-level Array in the JSON" do
78
- schema = create(:schema, :array_of, :objects)
86
+ schema = create(:schema, :array_of, :locations)
79
87
 
80
- json = build(:response, :object)
88
+ json = build(:response, :location)
81
89
  json_as_array = [json.to_h]
82
90
 
83
91
  expect(json_as_array).to match_json_schema(schema)
84
92
  end
85
93
 
86
94
  it "refutes a root-level Array in the JSON" do
87
- schema = create(:schema, :array_of, :objects)
95
+ schema = create(:schema, :array_of, :locations)
88
96
 
89
- json = build(:response, :invalid_object)
97
+ json = build(:response, :invalid_location)
90
98
  json_as_array = [json.to_h]
91
99
 
92
100
  expect(json_as_array).not_to match_json_schema(schema)
93
101
  end
94
102
 
95
103
  it "fails with message when negated" do
96
- schema = create(:schema, :array_of, :object)
104
+ schema = create(:schema, :array_of, :location)
97
105
 
98
- json = build(:response, :invalid_object)
106
+ json = build(:response, :invalid_location)
99
107
  json_as_array = [json.to_h]
100
108
 
101
109
  expect {
@@ -106,18 +114,18 @@ describe JsonMatchers, "#match_json_schema" do
106
114
 
107
115
  context "when JSON is a string" do
108
116
  it "validates that the schema matches" do
109
- schema = create(:schema, :object)
117
+ schema = create(:schema, :location)
110
118
 
111
- json = build(:response, :object)
119
+ json = build(:response, :location)
112
120
  json_as_string = json.to_json
113
121
 
114
122
  expect(json_as_string).to match_json_schema(schema)
115
123
  end
116
124
 
117
125
  it "fails with message when negated" do
118
- schema = create(:schema, :object)
126
+ schema = create(:schema, :location)
119
127
 
120
- json = build(:response, :invalid_object)
128
+ json = build(:response, :invalid_location)
121
129
  json_as_string = json.to_json
122
130
 
123
131
  expect {
@@ -127,18 +135,18 @@ describe JsonMatchers, "#match_json_schema" do
127
135
  end
128
136
 
129
137
  it "fails when the body contains a property with the wrong type" do
130
- schema = create(:schema, :object)
138
+ schema = create(:schema, :location)
131
139
 
132
- json = build(:response, :invalid_object)
140
+ json = build(:response, :invalid_location)
133
141
 
134
142
  expect(json).not_to match_json_schema(schema)
135
143
  end
136
144
 
137
145
  describe "the failure message" do
138
146
  it "contains the body" do
139
- schema = create(:schema, :object)
147
+ schema = create(:schema, :location)
140
148
 
141
- json = build(:response, :invalid_object)
149
+ json = build(:response, :invalid_location)
142
150
 
143
151
  expect {
144
152
  expect(json).to match_json_schema(schema)
@@ -146,9 +154,9 @@ describe JsonMatchers, "#match_json_schema" do
146
154
  end
147
155
 
148
156
  it "contains the schema" do
149
- schema = create(:schema, :object)
157
+ schema = create(:schema, :location)
150
158
 
151
- json = build(:response, :invalid_object)
159
+ json = build(:response, :invalid_location)
152
160
 
153
161
  expect {
154
162
  expect(json).to match_json_schema(schema)
@@ -156,9 +164,9 @@ describe JsonMatchers, "#match_json_schema" do
156
164
  end
157
165
 
158
166
  it "when negated, contains the body" do
159
- schema = create(:schema, :object)
167
+ schema = create(:schema, :location)
160
168
 
161
- json = build(:response, :object)
169
+ json = build(:response, :location)
162
170
 
163
171
  expect {
164
172
  expect(json).not_to match_json_schema(schema)
@@ -166,9 +174,9 @@ describe JsonMatchers, "#match_json_schema" do
166
174
  end
167
175
 
168
176
  it "when negated, contains the schema" do
169
- schema = create(:schema, :object)
177
+ schema = create(:schema, :location)
170
178
 
171
- json = build(:response, :object)
179
+ json = build(:response, :location)
172
180
 
173
181
  expect {
174
182
  expect(json).not_to match_json_schema(schema)
@@ -177,71 +185,39 @@ describe JsonMatchers, "#match_json_schema" do
177
185
  end
178
186
 
179
187
  it "validates against a schema that uses $ref" do
180
- schema = create(:schema, :referencing_objects)
188
+ schema = create(:schema, :referencing_locations)
181
189
 
182
- json = build(:response, :object)
190
+ json = build(:response, :location)
183
191
  json_as_array = [json.to_h]
184
192
 
185
193
  expect(json_as_array).to match_json_schema(schema)
186
194
  end
187
195
 
188
196
  it "fails against a schema that uses $ref" do
189
- schema = create(:schema, :referencing_objects)
197
+ schema = create(:schema, :referencing_locations)
190
198
 
191
- json = build(:response, :invalid_object)
199
+ json = build(:response, :invalid_location)
192
200
  json_as_array = [json.to_h]
193
201
 
194
202
  expect(json_as_array).not_to match_json_schema(schema)
195
203
  end
196
204
 
197
- context "when options are passed directly to the matcher" do
198
- it "forwards options to the validator" do
199
- schema = create(:schema, :object)
205
+ it "validates against a schema referencing with 'definitions'" do
206
+ schema = create(:schema, :referencing_definitions)
200
207
 
201
- matching_json = build(:response, :object)
202
- invalid_json = build(:response, { "id": 1, "title": "bar" })
208
+ json = build(:response, :location)
209
+ json_as_hash = { "locations" => [json] }
203
210
 
204
- expect(matching_json).to match_json_schema(schema, strict: true)
205
- expect(invalid_json).not_to match_json_schema(schema, strict: true)
206
- end
211
+ expect(json_as_hash).to match_json_schema(schema)
207
212
  end
208
213
 
209
- context "when options are configured globally" do
210
- it "forwards them to the validator" do
211
- with_options(strict: true) do
212
- schema = create(:schema, :object)
213
-
214
- matching_json = build(:response, :object)
215
- invalid_json = build(:response, { "id": 1, "title": "bar" })
214
+ it "fails against a schema referencing with 'definitions'" do
215
+ schema = create(:schema, :referencing_definitions)
216
216
 
217
- expect(matching_json).to match_json_schema(schema)
218
- expect(invalid_json).not_to match_json_schema(schema)
219
- end
220
- end
217
+ json = build(:response, :invalid_location)
218
+ json_as_hash = { "locations" => [json] }
221
219
 
222
- context "when configured to record errors" do
223
- it "includes the reasons for failure in the exception's message" do
224
- with_options(record_errors: true) do
225
- schema = create(:schema, {
226
- "type": "object",
227
- "properties": {
228
- "username": {
229
- "allOf": [
230
- { "type": "string" },
231
- { "minLength": 5 },
232
- ],
233
- },
234
- },
235
- })
236
-
237
- invalid_json = build(:response, { "username": "foo" })
238
-
239
- expect {
240
- expect(invalid_json).to match_json_schema(schema)
241
- }.to raise_error(/minimum/)
242
- end
243
- end
244
- end
220
+ expect(json_as_hash).not_to match_json_schema(schema)
245
221
  end
246
222
 
247
223
  def raise_error_containing(schema_or_body)