committee 3.3.0 → 5.0.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/lib/committee/drivers/open_api_2/driver.rb +1 -2
  3. data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +1 -1
  4. data/lib/committee/drivers.rb +22 -10
  5. data/lib/committee/errors.rb +12 -0
  6. data/lib/committee/middleware/base.rb +5 -4
  7. data/lib/committee/middleware/request_validation.rb +4 -18
  8. data/lib/committee/middleware/response_validation.rb +15 -16
  9. data/lib/committee/request_unpacker.rb +46 -60
  10. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +8 -2
  11. data/lib/committee/schema_validator/hyper_schema.rb +41 -27
  12. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +44 -37
  13. data/lib/committee/schema_validator/open_api_3/request_validator.rb +11 -2
  14. data/lib/committee/schema_validator/open_api_3/router.rb +3 -1
  15. data/lib/committee/schema_validator/open_api_3.rb +52 -26
  16. data/lib/committee/schema_validator/option.rb +14 -3
  17. data/lib/committee/schema_validator.rb +1 -1
  18. data/lib/committee/test/methods.rb +27 -16
  19. data/lib/committee/test/schema_coverage.rb +101 -0
  20. data/lib/committee/utils.rb +28 -0
  21. data/lib/committee/validation_error.rb +3 -2
  22. data/lib/committee/version.rb +5 -0
  23. data/lib/committee.rb +11 -4
  24. data/test/bin/committee_stub_test.rb +5 -1
  25. data/test/committee_test.rb +29 -3
  26. data/test/drivers/open_api_3/driver_test.rb +1 -1
  27. data/test/drivers_test.rb +20 -7
  28. data/test/middleware/base_test.rb +9 -10
  29. data/test/middleware/request_validation_open_api_3_test.rb +175 -18
  30. data/test/middleware/request_validation_test.rb +20 -28
  31. data/test/middleware/response_validation_open_api_3_test.rb +96 -7
  32. data/test/middleware/response_validation_test.rb +21 -26
  33. data/test/middleware/stub_test.rb +4 -0
  34. data/test/request_unpacker_test.rb +51 -110
  35. data/test/schema_validator/hyper_schema/response_validator_test.rb +10 -0
  36. data/test/schema_validator/hyper_schema/router_test.rb +4 -0
  37. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +1 -1
  38. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +72 -20
  39. data/test/schema_validator/open_api_3/request_validator_test.rb +27 -0
  40. data/test/schema_validator/open_api_3/response_validator_test.rb +26 -5
  41. data/test/test/methods_new_version_test.rb +17 -5
  42. data/test/test/methods_test.rb +155 -31
  43. data/test/test/schema_coverage_test.rb +216 -0
  44. data/test/test_helper.rb +34 -4
  45. metadata +47 -15
@@ -28,7 +28,10 @@ describe Committee::Test::Methods do
28
28
  # our purposes here in testing the module.
29
29
  @committee_router = nil
30
30
  @committee_schema = nil
31
- @committee_options = nil
31
+ @committee_options = {}
32
+
33
+ # TODO: delete when 5.0.0 released because default value changed
34
+ @committee_options[:parse_response_by_content_type] = true
32
35
  end
33
36
 
34
37
  describe "Hyper-Schema" do
@@ -36,33 +39,24 @@ describe Committee::Test::Methods do
36
39
  sc = JsonSchema.parse!(hyper_schema_data)
37
40
  sc.expand_references!
38
41
  s = Committee::Drivers::HyperSchema::Driver.new.parse(sc)
39
- @committee_options = {schema: s}
42
+ @committee_options.merge!({schema: s})
40
43
  end
41
44
 
42
45
  describe "#assert_schema_conform" do
43
46
  it "passes through a valid response" do
44
47
  @app = new_rack_app(JSON.generate([ValidApp]))
45
48
  get "/apps"
46
- assert_schema_conform
49
+ assert_schema_conform(200)
47
50
  end
48
51
 
49
52
  it "detects an invalid response Content-Type" do
50
53
  @app = new_rack_app(JSON.generate([ValidApp]), {})
51
54
  get "/apps"
52
55
  e = assert_raises(Committee::InvalidResponse) do
53
- assert_schema_conform
56
+ assert_schema_conform(200)
54
57
  end
55
58
  assert_match(/response header must be set to/i, e.message)
56
59
  end
57
-
58
- it "outputs deprecation warning" do
59
- @app = new_rack_app(JSON.generate([ValidApp]))
60
- get "/apps"
61
- _, err = capture_io do
62
- assert_schema_conform
63
- end
64
- assert_match(/\[DEPRECATION\]/i, err)
65
- end
66
60
  end
67
61
 
68
62
  describe "assert_request_schema_confirm" do
@@ -95,14 +89,14 @@ describe Committee::Test::Methods do
95
89
  it "passes through a valid response" do
96
90
  @app = new_rack_app(JSON.generate([ValidApp]))
97
91
  get "/apps"
98
- assert_response_schema_confirm
92
+ assert_response_schema_confirm(200)
99
93
  end
100
94
 
101
95
  it "detects an invalid response Content-Type" do
102
96
  @app = new_rack_app(JSON.generate([ValidApp]), {})
103
97
  get "/apps"
104
98
  e = assert_raises(Committee::InvalidResponse) do
105
- assert_response_schema_confirm
99
+ assert_response_schema_confirm(200)
106
100
  end
107
101
  assert_match(/response header must be set to/i, e.message)
108
102
  end
@@ -120,7 +114,7 @@ describe Committee::Test::Methods do
120
114
 
121
115
  describe "OpenAPI3" do
122
116
  before do
123
- @committee_options = {schema: open_api_3_schema}
117
+ @committee_options.merge!({schema: open_api_3_schema})
124
118
 
125
119
  @correct_response = { string_1: :honoka }
126
120
  end
@@ -129,14 +123,14 @@ describe Committee::Test::Methods do
129
123
  it "passes through a valid response" do
130
124
  @app = new_rack_app(JSON.generate(@correct_response))
131
125
  get "/characters"
132
- assert_schema_conform
126
+ assert_schema_conform(200)
133
127
  end
134
128
 
135
129
  it "detects an invalid response Content-Type" do
136
130
  @app = new_rack_app(JSON.generate([@correct_response]), {})
137
131
  get "/characters"
138
132
  e = assert_raises(Committee::InvalidResponse) do
139
- assert_schema_conform
133
+ assert_schema_conform(200)
140
134
  end
141
135
  assert_match(/response definition does not exist/i, e.message)
142
136
  end
@@ -147,19 +141,10 @@ describe Committee::Test::Methods do
147
141
  get "/characters"
148
142
 
149
143
  e = assert_raises(Committee::InvalidResponse) do
150
- assert_schema_conform
144
+ assert_schema_conform(419)
151
145
  end
152
146
  assert_match(/status code definition does not exist/i, e.message)
153
147
  end
154
-
155
- it "outputs deprecation warning" do
156
- @app = new_rack_app(JSON.generate(@correct_response))
157
- get "/characters"
158
- _, err = capture_io do
159
- assert_schema_conform
160
- end
161
- assert_match(/\[DEPRECATION\]/i, err)
162
- end
163
148
  end
164
149
 
165
150
  describe "assert_request_schema_confirm" do
@@ -193,14 +178,14 @@ describe Committee::Test::Methods do
193
178
  it "passes through a valid response" do
194
179
  @app = new_rack_app(JSON.generate(@correct_response))
195
180
  get "/characters"
196
- assert_response_schema_confirm
181
+ assert_response_schema_confirm(200)
197
182
  end
198
183
 
199
184
  it "detects an invalid response Content-Type" do
200
185
  @app = new_rack_app(JSON.generate([@correct_response]), {})
201
186
  get "/characters"
202
187
  e = assert_raises(Committee::InvalidResponse) do
203
- assert_response_schema_confirm
188
+ assert_response_schema_confirm(200)
204
189
  end
205
190
  assert_match(/response definition does not exist/i, e.message)
206
191
  end
@@ -211,7 +196,7 @@ describe Committee::Test::Methods do
211
196
  get "/characters"
212
197
 
213
198
  e = assert_raises(Committee::InvalidResponse) do
214
- assert_response_schema_confirm
199
+ assert_response_schema_confirm(419)
215
200
  end
216
201
  assert_match(/status code definition does not exist/i, e.message)
217
202
  end
@@ -224,6 +209,145 @@ describe Committee::Test::Methods do
224
209
  end
225
210
  assert_match(/`GET \/undefined` undefined in schema/i, e.message)
226
211
  end
212
+
213
+ it "raises error when path does not match prefix" do
214
+ @committee_options.merge!({prefix: '/api'})
215
+ @app = new_rack_app(JSON.generate(@correct_response))
216
+ get "/characters"
217
+ e = assert_raises(Committee::InvalidResponse) do
218
+ assert_response_schema_confirm
219
+ end
220
+ assert_match(/`GET \/characters` undefined in schema \(prefix: "\/api"\)/i, e.message)
221
+ end
222
+
223
+ describe 'coverage' do
224
+ before do
225
+ @schema_coverage = Committee::Test::SchemaCoverage.new(open_api_3_coverage_schema)
226
+ @committee_options.merge!(schema: open_api_3_coverage_schema, schema_coverage: @schema_coverage)
227
+
228
+ @app = new_rack_app(JSON.generate({ success: true }))
229
+ end
230
+ it 'records openapi coverage' do
231
+ get "/posts"
232
+ assert_response_schema_confirm(200)
233
+ assert_equal({
234
+ '/threads/{id}' => {
235
+ 'get' => {
236
+ 'responses' => {
237
+ '200' => false,
238
+ },
239
+ },
240
+ },
241
+ '/posts' => {
242
+ 'get' => {
243
+ 'responses' => {
244
+ '200' => true,
245
+ '404' => false,
246
+ 'default' => false,
247
+ },
248
+ },
249
+ 'post' => {
250
+ 'responses' => {
251
+ '200' => false,
252
+ },
253
+ },
254
+ },
255
+ '/likes' => {
256
+ 'post' => {
257
+ 'responses' => {
258
+ '200' => false,
259
+ },
260
+ },
261
+ 'delete' => {
262
+ 'responses' => {
263
+ '200' => false,
264
+ },
265
+ },
266
+ },
267
+ }, @schema_coverage.report)
268
+ end
269
+
270
+ it 'can record openapi coverage correctly when prefix is set' do
271
+ @committee_options.merge!(prefix: '/api')
272
+ post "/api/likes"
273
+ assert_response_schema_confirm(200)
274
+ assert_equal({
275
+ '/threads/{id}' => {
276
+ 'get' => {
277
+ 'responses' => {
278
+ '200' => false,
279
+ },
280
+ },
281
+ },
282
+ '/posts' => {
283
+ 'get' => {
284
+ 'responses' => {
285
+ '200' => false,
286
+ '404' => false,
287
+ 'default' => false,
288
+ },
289
+ },
290
+ 'post' => {
291
+ 'responses' => {
292
+ '200' => false,
293
+ },
294
+ },
295
+ },
296
+ '/likes' => {
297
+ 'post' => {
298
+ 'responses' => {
299
+ '200' => true,
300
+ },
301
+ },
302
+ 'delete' => {
303
+ 'responses' => {
304
+ '200' => false,
305
+ },
306
+ },
307
+ },
308
+ }, @schema_coverage.report)
309
+ end
310
+
311
+ it 'records openapi coverage correctly with path param' do
312
+ get "/threads/asd"
313
+ assert_response_schema_confirm(200)
314
+ assert_equal({
315
+ '/threads/{id}' => {
316
+ 'get' => {
317
+ 'responses' => {
318
+ '200' => true,
319
+ },
320
+ },
321
+ },
322
+ '/posts' => {
323
+ 'get' => {
324
+ 'responses' => {
325
+ '200' => false,
326
+ '404' => false,
327
+ 'default' => false,
328
+ },
329
+ },
330
+ 'post' => {
331
+ 'responses' => {
332
+ '200' => false,
333
+ },
334
+ },
335
+ },
336
+ '/likes' => {
337
+ 'post' => {
338
+ 'responses' => {
339
+ '200' => false,
340
+ },
341
+ },
342
+ 'delete' => {
343
+ 'responses' => {
344
+ '200' => false,
345
+ },
346
+ },
347
+ },
348
+ }, @schema_coverage.report)
349
+ end
350
+ end
227
351
  end
228
352
  end
229
353
 
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::Test::SchemaCoverage do
6
+ before do
7
+ @schema_coverage = Committee::Test::SchemaCoverage.new(open_api_3_coverage_schema)
8
+ end
9
+
10
+ describe 'recording coverage' do
11
+ def response_as_str(response)
12
+ [:path, :method, :status].map { |key| response[key] }.join(' ')
13
+ end
14
+
15
+ def uncovered_responses
16
+ @schema_coverage.report_flatten[:responses].select { |r| !r[:is_covered] }.map { |r| response_as_str(r) }
17
+ end
18
+
19
+ def covered_responses
20
+ @schema_coverage.report_flatten[:responses].select { |r| r[:is_covered] }.map { |r| response_as_str(r) }
21
+ end
22
+ it 'can record and report coverage properly' do
23
+ @schema_coverage.update_response_coverage!('/posts', 'get', '200')
24
+ assert_equal([
25
+ '/posts get 200',
26
+ ], covered_responses)
27
+ assert_equal([
28
+ '/threads/{id} get 200',
29
+ '/posts get 404',
30
+ '/posts get default',
31
+ '/posts post 200',
32
+ '/likes post 200',
33
+ '/likes delete 200',
34
+ ], uncovered_responses)
35
+
36
+ @schema_coverage.update_response_coverage!('/likes', 'post', '200')
37
+ assert_equal([
38
+ '/posts get 200',
39
+ '/likes post 200',
40
+ ], covered_responses)
41
+ assert_equal([
42
+ '/threads/{id} get 200',
43
+ '/posts get 404',
44
+ '/posts get default',
45
+ '/posts post 200',
46
+ '/likes delete 200',
47
+ ], uncovered_responses)
48
+
49
+ @schema_coverage.update_response_coverage!('/likes', 'delete', '200')
50
+ assert_equal([
51
+ '/posts get 200',
52
+ '/likes post 200',
53
+ '/likes delete 200',
54
+ ], covered_responses)
55
+ assert_equal([
56
+ '/threads/{id} get 200',
57
+ '/posts get 404',
58
+ '/posts get default',
59
+ '/posts post 200',
60
+ ], uncovered_responses)
61
+
62
+ @schema_coverage.update_response_coverage!('/posts', 'get', '422')
63
+ assert_equal([
64
+ '/posts get 200',
65
+ '/posts get default',
66
+ '/likes post 200',
67
+ '/likes delete 200',
68
+ ], covered_responses)
69
+ assert_equal([
70
+ '/threads/{id} get 200',
71
+ '/posts get 404',
72
+ '/posts post 200',
73
+ ], uncovered_responses)
74
+
75
+ assert_equal({
76
+ '/threads/{id}' => {
77
+ 'get' => {
78
+ 'responses' => {
79
+ '200' => false,
80
+ },
81
+ },
82
+ },
83
+ '/posts' => {
84
+ 'get' => {
85
+ 'responses' => {
86
+ '200' => true,
87
+ '404' => false,
88
+ 'default' => true,
89
+ },
90
+ },
91
+ 'post' => {
92
+ 'responses' => {
93
+ '200' => false,
94
+ },
95
+ },
96
+ },
97
+ '/likes' => {
98
+ 'post' => {
99
+ 'responses' => {
100
+ '200' => true,
101
+ },
102
+ },
103
+ 'delete' => {
104
+ 'responses' => {
105
+ '200' => true,
106
+ },
107
+ },
108
+ },
109
+ }, @schema_coverage.report)
110
+
111
+ @schema_coverage.update_response_coverage!('/posts', 'post', '200')
112
+ @schema_coverage.update_response_coverage!('/posts', 'get', '404')
113
+ @schema_coverage.update_response_coverage!('/threads/{id}', 'get', '200')
114
+ assert_equal([
115
+ '/threads/{id} get 200',
116
+ '/posts get 200',
117
+ '/posts get 404',
118
+ '/posts get default',
119
+ '/posts post 200',
120
+ '/likes post 200',
121
+ '/likes delete 200',
122
+ ], covered_responses)
123
+ assert_equal([], uncovered_responses)
124
+ end
125
+ end
126
+
127
+ describe '.merge_report' do
128
+ it 'can merge 2 coverage reports together' do
129
+ report = Committee::Test::SchemaCoverage.merge_report(
130
+ {
131
+ '/posts' => {
132
+ 'get' => {
133
+ 'responses' => {
134
+ '200' => true,
135
+ '404' => false,
136
+ },
137
+ },
138
+ 'post' => {
139
+ 'responses' => {
140
+ '200' => false,
141
+ },
142
+ },
143
+ },
144
+ '/likes' => {
145
+ 'post' => {
146
+ 'responses' => {
147
+ '200' => true,
148
+ },
149
+ },
150
+ },
151
+ },
152
+ {
153
+ '/posts' => {
154
+ 'get' => {
155
+ 'responses' => {
156
+ '200' => true,
157
+ '404' => true,
158
+ },
159
+ },
160
+ 'post' => {
161
+ 'responses' => {
162
+ '200' => false,
163
+ },
164
+ },
165
+ },
166
+ '/likes' => {
167
+ 'post' => {
168
+ 'responses' => {
169
+ '200' => false,
170
+ '400' => false,
171
+ },
172
+ },
173
+ },
174
+ '/users' => {
175
+ 'get' => {
176
+ 'responses' => {
177
+ '200' => true,
178
+ },
179
+ },
180
+ },
181
+ },
182
+ )
183
+
184
+ assert_equal({
185
+ '/posts' => {
186
+ 'get' => {
187
+ 'responses' => {
188
+ '200' => true,
189
+ '404' => true,
190
+ },
191
+ },
192
+ 'post' => {
193
+ 'responses' => {
194
+ '200' => false,
195
+ },
196
+ },
197
+ },
198
+ '/likes' => {
199
+ 'post' => {
200
+ 'responses' => {
201
+ '200' => true,
202
+ '400' => false,
203
+ },
204
+ },
205
+ },
206
+ '/users' => {
207
+ 'get' => {
208
+ 'responses' => {
209
+ '200' => true,
210
+ },
211
+ },
212
+ },
213
+ }, report)
214
+ end
215
+ end
216
+ end
data/test/test_helper.rb CHANGED
@@ -11,8 +11,10 @@ SimpleCov.start do
11
11
  add_filter "/test/"
12
12
 
13
13
  # This library has a pretty modest number of lines, so let's try to stick
14
- # to a 100% coverage target for a while and see what happens.
15
- minimum_coverage 100
14
+ # to a 99% coverage target for a while and see what happens.
15
+ # We can't use 100% because old rack version doesn't support media_type and it's not testable :(
16
+ # https://github.com/interagent/committee/pull/360/files#diff-ce1125b6594690a88a70dbe2869f7fcfa2962c2bca80751f3720888920e2dfabR54
17
+ minimum_coverage 99
16
18
  end
17
19
 
18
20
  require "minitest"
@@ -55,8 +57,16 @@ def open_api_2_schema
55
57
  @open_api_2_schema ||= Committee::Drivers.load_from_file(open_api_2_schema_path)
56
58
  end
57
59
 
60
+ def open_api_2_form_schema
61
+ @open_api_2_form_schema ||= Committee::Drivers.load_from_file(open_api_2_form_schema_path)
62
+ end
63
+
58
64
  def open_api_3_schema
59
- @open_api_3_schema ||= Committee::Drivers.load_from_data(open_api_3_data)
65
+ @open_api_3_schema ||= Committee::Drivers.load_from_file(open_api_3_schema_path, parser_options:{strict_reference_validation: true})
66
+ end
67
+
68
+ def open_api_3_coverage_schema
69
+ @open_api_3_coverage_schema ||= Committee::Drivers.load_from_file(open_api_3_coverage_schema_path, parser_options:{strict_reference_validation: true})
60
70
  end
61
71
 
62
72
  # Don't cache this because we'll often manipulate the created hash in tests.
@@ -69,8 +79,16 @@ def open_api_2_data
69
79
  JSON.parse(File.read(open_api_2_schema_path))
70
80
  end
71
81
 
82
+ def open_api_2_form_data
83
+ JSON.parse(File.read(open_api_2_form_schema_path))
84
+ end
85
+
72
86
  def open_api_3_data
73
- YAML.load_file(open_api_3_schema_path)
87
+ if YAML.respond_to?(:unsafe_load_file)
88
+ YAML.unsafe_load_file(open_api_3_schema_path)
89
+ else
90
+ YAML.load_file(open_api_3_schema_path)
91
+ end
74
92
  end
75
93
 
76
94
  def hyper_schema_schema_path
@@ -81,10 +99,22 @@ def open_api_2_schema_path
81
99
  "./test/data/openapi2/petstore-expanded.json"
82
100
  end
83
101
 
102
+ def open_api_2_form_schema_path
103
+ "./test/data/openapi2/petstore-expanded-form.json"
104
+ end
105
+
84
106
  def open_api_3_schema_path
85
107
  "./test/data/openapi3/normal.yaml"
86
108
  end
87
109
 
110
+ def open_api_3_coverage_schema_path
111
+ "./test/data/openapi3/coverage.yaml"
112
+ end
113
+
88
114
  def open_api_3_0_1_schema_path
89
115
  "./test/data/openapi3/3_0_1.yaml"
90
116
  end
117
+
118
+ def open_api_3_invalid_reference_path
119
+ "./test/data/openapi3/invalid_reference.yaml"
120
+ end