json_matchers 0.9.0 → 0.10.0

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