committee_firetail 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 (80) hide show
  1. checksums.yaml +7 -0
  2. data/bin/committee-stub +23 -0
  3. data/lib/committee/bin/committee_stub.rb +67 -0
  4. data/lib/committee/drivers/driver.rb +47 -0
  5. data/lib/committee/drivers/hyper_schema/driver.rb +105 -0
  6. data/lib/committee/drivers/hyper_schema/link.rb +68 -0
  7. data/lib/committee/drivers/hyper_schema/schema.rb +22 -0
  8. data/lib/committee/drivers/hyper_schema.rb +12 -0
  9. data/lib/committee/drivers/open_api_2/driver.rb +252 -0
  10. data/lib/committee/drivers/open_api_2/header_schema_builder.rb +33 -0
  11. data/lib/committee/drivers/open_api_2/link.rb +36 -0
  12. data/lib/committee/drivers/open_api_2/parameter_schema_builder.rb +83 -0
  13. data/lib/committee/drivers/open_api_2/schema.rb +26 -0
  14. data/lib/committee/drivers/open_api_2/schema_builder.rb +33 -0
  15. data/lib/committee/drivers/open_api_2.rb +13 -0
  16. data/lib/committee/drivers/open_api_3/driver.rb +51 -0
  17. data/lib/committee/drivers/open_api_3/schema.rb +41 -0
  18. data/lib/committee/drivers/open_api_3.rb +11 -0
  19. data/lib/committee/drivers/schema.rb +23 -0
  20. data/lib/committee/drivers.rb +84 -0
  21. data/lib/committee/errors.rb +36 -0
  22. data/lib/committee/middleware/base.rb +57 -0
  23. data/lib/committee/middleware/request_validation.rb +41 -0
  24. data/lib/committee/middleware/response_validation.rb +58 -0
  25. data/lib/committee/middleware/stub.rb +75 -0
  26. data/lib/committee/middleware.rb +11 -0
  27. data/lib/committee/request_unpacker.rb +91 -0
  28. data/lib/committee/schema_validator/hyper_schema/parameter_coercer.rb +79 -0
  29. data/lib/committee/schema_validator/hyper_schema/request_validator.rb +55 -0
  30. data/lib/committee/schema_validator/hyper_schema/response_generator.rb +102 -0
  31. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +89 -0
  32. data/lib/committee/schema_validator/hyper_schema/router.rb +46 -0
  33. data/lib/committee/schema_validator/hyper_schema/string_params_coercer.rb +105 -0
  34. data/lib/committee/schema_validator/hyper_schema.rb +119 -0
  35. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +139 -0
  36. data/lib/committee/schema_validator/open_api_3/request_validator.rb +52 -0
  37. data/lib/committee/schema_validator/open_api_3/response_validator.rb +29 -0
  38. data/lib/committee/schema_validator/open_api_3/router.rb +45 -0
  39. data/lib/committee/schema_validator/open_api_3.rb +120 -0
  40. data/lib/committee/schema_validator/option.rb +60 -0
  41. data/lib/committee/schema_validator.rb +23 -0
  42. data/lib/committee/test/methods.rb +84 -0
  43. data/lib/committee/test/schema_coverage.rb +101 -0
  44. data/lib/committee/utils.rb +28 -0
  45. data/lib/committee/validation_error.rb +26 -0
  46. data/lib/committee/version.rb +5 -0
  47. data/lib/committee.rb +40 -0
  48. data/test/bin/committee_stub_test.rb +57 -0
  49. data/test/bin_test.rb +25 -0
  50. data/test/committee_test.rb +77 -0
  51. data/test/drivers/hyper_schema/driver_test.rb +49 -0
  52. data/test/drivers/hyper_schema/link_test.rb +56 -0
  53. data/test/drivers/open_api_2/driver_test.rb +156 -0
  54. data/test/drivers/open_api_2/header_schema_builder_test.rb +26 -0
  55. data/test/drivers/open_api_2/link_test.rb +52 -0
  56. data/test/drivers/open_api_2/parameter_schema_builder_test.rb +195 -0
  57. data/test/drivers/open_api_3/driver_test.rb +84 -0
  58. data/test/drivers_test.rb +154 -0
  59. data/test/middleware/base_test.rb +130 -0
  60. data/test/middleware/request_validation_open_api_3_test.rb +626 -0
  61. data/test/middleware/request_validation_test.rb +516 -0
  62. data/test/middleware/response_validation_open_api_3_test.rb +291 -0
  63. data/test/middleware/response_validation_test.rb +189 -0
  64. data/test/middleware/stub_test.rb +145 -0
  65. data/test/request_unpacker_test.rb +200 -0
  66. data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +111 -0
  67. data/test/schema_validator/hyper_schema/request_validator_test.rb +151 -0
  68. data/test/schema_validator/hyper_schema/response_generator_test.rb +142 -0
  69. data/test/schema_validator/hyper_schema/response_validator_test.rb +118 -0
  70. data/test/schema_validator/hyper_schema/router_test.rb +88 -0
  71. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +137 -0
  72. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +218 -0
  73. data/test/schema_validator/open_api_3/request_validator_test.rb +110 -0
  74. data/test/schema_validator/open_api_3/response_validator_test.rb +92 -0
  75. data/test/test/methods_new_version_test.rb +97 -0
  76. data/test/test/methods_test.rb +363 -0
  77. data/test/test/schema_coverage_test.rb +216 -0
  78. data/test/test_helper.rb +120 -0
  79. data/test/validation_error_test.rb +25 -0
  80. metadata +328 -0
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
6
+ before do
7
+ @status = 200
8
+ @headers = {
9
+ "Content-Type" => "application/json"
10
+ }
11
+ @data = {"string" => "Honoka.Kousaka"}
12
+
13
+ @path = '/validate'
14
+ @method = 'post'
15
+
16
+ # TODO: delete when 5.0.0 released because default value changed
17
+ options = {}
18
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
19
+
20
+ @validator_option = Committee::SchemaValidator::Option.new(options, open_api_3_schema, :open_api_3)
21
+ end
22
+
23
+ it "passes through a valid response" do
24
+ call_response_validator
25
+ end
26
+
27
+ it "passes through a valid response with Content-Type options" do
28
+ @headers = { "Content-Type" => "application/json; charset=utf-8" }
29
+ call_response_validator
30
+ end
31
+
32
+ it "passes through a valid response with no registered Content-Type with strict = false" do
33
+ @headers = { "Content-Type" => "application/xml" }
34
+ call_response_validator
35
+ end
36
+
37
+ it "raises InvalidResponse when a valid response with no registered body with strict option" do
38
+ @headers = { "Content-Type" => "application/xml" }
39
+ e = assert_raises(Committee::InvalidResponse) {
40
+ call_response_validator(true)
41
+ }
42
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
43
+ end
44
+
45
+ it "raises InvalidResponse when a invalid status code with strict option" do
46
+ @status = 201
47
+ e = assert_raises(Committee::InvalidResponse) {
48
+ call_response_validator(true)
49
+ }
50
+
51
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
52
+ end
53
+
54
+ it "passes through a valid response with no Content-Type" do
55
+ @headers = {}
56
+ call_response_validator
57
+ end
58
+
59
+ it "raises InvalidResponse when a valid response with no Content-Type headers with strict option" do
60
+ @headers = {}
61
+ e = assert_raises(Committee::InvalidResponse) {
62
+ call_response_validator(true)
63
+ }
64
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
65
+ end
66
+
67
+ it "passes through a valid list response" do
68
+ @path = '/validate_response_array'
69
+ @method = 'get'
70
+ @data = ["Mari.Ohara"]
71
+ call_response_validator
72
+ end
73
+
74
+ it "passes through a 204 No Content response" do
75
+ @status, @headers, @data = 204, {}, nil
76
+ call_response_validator
77
+ end
78
+
79
+ it "passes through a 304 Not Modified response" do
80
+ @status, @headers, @data = 304, {}, nil
81
+ call_response_validator
82
+ end
83
+
84
+ private
85
+
86
+ def call_response_validator(strict = false)
87
+ @operation_object = open_api_3_schema.operation_object(@path, @method)
88
+ Committee::SchemaValidator::OpenAPI3::ResponseValidator.
89
+ new(@operation_object, @validator_option).
90
+ call(@status, @headers, @data, strict)
91
+ end
92
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::Test::Methods do
6
+ include Committee::Test::Methods
7
+ include Rack::Test::Methods
8
+
9
+ def app
10
+ @app
11
+ end
12
+
13
+ def committee_options
14
+ @committee_options
15
+ end
16
+
17
+ def request_object
18
+ last_request
19
+ end
20
+
21
+ def response_data
22
+ [last_response.status, last_response.headers, last_response.body]
23
+ end
24
+
25
+ before do
26
+ # This is a little icky, but the test methods will cache router and schema
27
+ # values between tests. This makes sense in real life, but is harmful for
28
+ # our purposes here in testing the module.
29
+ @committee_router = nil
30
+ @committee_schema = nil
31
+
32
+ @committee_options = {schema: hyper_schema}
33
+
34
+ # TODO: delete when 5.0.0 released because default value changed
35
+ @committee_options[:parse_response_by_content_type] = false
36
+ end
37
+
38
+ describe "#assert_schema_conform" do
39
+ it "passes through a valid response" do
40
+ @app = new_rack_app(JSON.generate([ValidApp]))
41
+ get "/apps"
42
+ assert_schema_conform(200)
43
+ end
44
+
45
+ it "passes with prefix" do
46
+ @committee_options.merge!(prefix: "/v1")
47
+
48
+ @app = new_rack_app(JSON.generate([ValidApp]))
49
+ get "/v1/apps"
50
+ assert_schema_conform(200)
51
+ end
52
+
53
+ it "detects an invalid response Content-Type" do
54
+ @app = new_rack_app(JSON.generate([ValidApp]), 200, {})
55
+ get "/apps"
56
+ e = assert_raises(Committee::InvalidResponse) do
57
+ assert_schema_conform(200)
58
+ end
59
+ assert_match(/response header must be set to/i, e.message)
60
+ end
61
+
62
+ it "it detects unexpected response code" do
63
+ @app = new_rack_app(JSON.generate([ValidApp]), 400)
64
+ get "/apps"
65
+ e = assert_raises(Committee::InvalidResponse) do
66
+ assert_schema_conform(200)
67
+ end
68
+ assert_match(/Expected `200` status code, but it was `400`/i, e.message)
69
+ end
70
+
71
+ it "detects an invalid response Content-Type but ignore because it's not success status code" do
72
+ @committee_options.merge!(validate_success_only: true)
73
+ @app = new_rack_app(JSON.generate([ValidApp]), 400, {})
74
+ get "/apps"
75
+ assert_schema_conform(400)
76
+ end
77
+
78
+ it "detects an invalid response Content-Type and check all status code" do
79
+ @app = new_rack_app(JSON.generate([ValidApp]), 400, {})
80
+ get "/apps"
81
+ e = assert_raises(Committee::InvalidResponse) do
82
+ assert_schema_conform(400)
83
+ end
84
+ assert_match(/response header must be set to/i, e.message)
85
+ end
86
+ end
87
+
88
+ private
89
+
90
+ def new_rack_app(response, status=200, headers={ "Content-Type" => "application/json" })
91
+ Rack::Builder.new {
92
+ run lambda { |_|
93
+ [status, headers, [response]]
94
+ }
95
+ }
96
+ end
97
+ end
@@ -0,0 +1,363 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::Test::Methods do
6
+ include Committee::Test::Methods
7
+ include Rack::Test::Methods
8
+
9
+ def app
10
+ @app
11
+ end
12
+
13
+ def committee_options
14
+ @committee_options
15
+ end
16
+
17
+ def request_object
18
+ last_request
19
+ end
20
+
21
+ def response_data
22
+ [last_response.status, last_response.headers, last_response.body]
23
+ end
24
+
25
+ before do
26
+ # This is a little icky, but the test methods will cache router and schema
27
+ # values between tests. This makes sense in real life, but is harmful for
28
+ # our purposes here in testing the module.
29
+ @committee_router = nil
30
+ @committee_schema = 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
35
+ end
36
+
37
+ describe "Hyper-Schema" do
38
+ before do
39
+ sc = JsonSchema.parse!(hyper_schema_data)
40
+ sc.expand_references!
41
+ s = Committee::Drivers::HyperSchema::Driver.new.parse(sc)
42
+ @committee_options.merge!({schema: s})
43
+ end
44
+
45
+ describe "#assert_schema_conform" do
46
+ it "passes through a valid response" do
47
+ @app = new_rack_app(JSON.generate([ValidApp]))
48
+ get "/apps"
49
+ assert_schema_conform(200)
50
+ end
51
+
52
+ it "detects an invalid response Content-Type" do
53
+ @app = new_rack_app(JSON.generate([ValidApp]), {})
54
+ get "/apps"
55
+ e = assert_raises(Committee::InvalidResponse) do
56
+ assert_schema_conform(200)
57
+ end
58
+ assert_match(/response header must be set to/i, e.message)
59
+ end
60
+ end
61
+
62
+ describe "assert_request_schema_confirm" do
63
+ it "passes through a valid request" do
64
+ @app = new_rack_app([])
65
+ get "/apps"
66
+ assert_request_schema_confirm
67
+ end
68
+
69
+ it "not exist required" do
70
+ @app = new_rack_app([])
71
+ get "/search/apps", {}
72
+ e = assert_raises(Committee::InvalidRequest) do
73
+ assert_request_schema_confirm
74
+ end
75
+ assert_match(/"query" wasn't supplied\./i, e.message)
76
+ end
77
+
78
+ it "path undefined in schema" do
79
+ @app = new_rack_app([])
80
+ get "/undefined"
81
+ e = assert_raises(Committee::InvalidRequest) do
82
+ assert_request_schema_confirm
83
+ end
84
+ assert_match(/`GET \/undefined` undefined in schema/i, e.message)
85
+ end
86
+ end
87
+
88
+ describe "#assert_response_schema_confirm" do
89
+ it "passes through a valid response" do
90
+ @app = new_rack_app(JSON.generate([ValidApp]))
91
+ get "/apps"
92
+ assert_response_schema_confirm(200)
93
+ end
94
+
95
+ it "detects an invalid response Content-Type" do
96
+ @app = new_rack_app(JSON.generate([ValidApp]), {})
97
+ get "/apps"
98
+ e = assert_raises(Committee::InvalidResponse) do
99
+ assert_response_schema_confirm(200)
100
+ end
101
+ assert_match(/response header must be set to/i, e.message)
102
+ end
103
+
104
+ it "path undefined in schema" do
105
+ @app = new_rack_app(JSON.generate([ValidApp]))
106
+ get "/undefined"
107
+ e = assert_raises(Committee::InvalidResponse) do
108
+ assert_response_schema_confirm
109
+ end
110
+ assert_match(/`GET \/undefined` undefined in schema/i, e.message)
111
+ end
112
+ end
113
+ end
114
+
115
+ describe "OpenAPI3" do
116
+ before do
117
+ @committee_options.merge!({schema: open_api_3_schema})
118
+
119
+ @correct_response = { string_1: :honoka }
120
+ end
121
+
122
+ describe "#assert_schema_conform" do
123
+ it "passes through a valid response" do
124
+ @app = new_rack_app(JSON.generate(@correct_response))
125
+ get "/characters"
126
+ assert_schema_conform(200)
127
+ end
128
+
129
+ it "detects an invalid response Content-Type" do
130
+ @app = new_rack_app(JSON.generate([@correct_response]), {})
131
+ get "/characters"
132
+ e = assert_raises(Committee::InvalidResponse) do
133
+ assert_schema_conform(200)
134
+ end
135
+ assert_match(/response definition does not exist/i, e.message)
136
+ end
137
+
138
+ it "detects an invalid response status code" do
139
+ @app = new_rack_app(JSON.generate([@correct_response]), {}, 419)
140
+
141
+ get "/characters"
142
+
143
+ e = assert_raises(Committee::InvalidResponse) do
144
+ assert_schema_conform(419)
145
+ end
146
+ assert_match(/status code definition does not exist/i, e.message)
147
+ end
148
+ end
149
+
150
+ describe "assert_request_schema_confirm" do
151
+ it "passes through a valid request" do
152
+ @app = new_rack_app([])
153
+ get "/characters"
154
+ assert_request_schema_confirm
155
+ end
156
+
157
+ it "not exist required" do
158
+ @app = new_rack_app([])
159
+ get "/validate", {"query_string" => "query", "query_integer_list" => [1, 2]}
160
+ e = assert_raises(Committee::InvalidRequest) do
161
+ assert_request_schema_confirm
162
+ end
163
+
164
+ assert_match(/missing required parameters: query_string/i, e.message)
165
+ end
166
+
167
+ it "path undefined in schema" do
168
+ @app = new_rack_app([])
169
+ get "/undefined"
170
+ e = assert_raises(Committee::InvalidRequest) do
171
+ assert_request_schema_confirm
172
+ end
173
+ assert_match(/`GET \/undefined` undefined in schema/i, e.message)
174
+ end
175
+ end
176
+
177
+ describe "#assert_response_schema_confirm" do
178
+ it "passes through a valid response" do
179
+ @app = new_rack_app(JSON.generate(@correct_response))
180
+ get "/characters"
181
+ assert_response_schema_confirm(200)
182
+ end
183
+
184
+ it "detects an invalid response Content-Type" do
185
+ @app = new_rack_app(JSON.generate([@correct_response]), {})
186
+ get "/characters"
187
+ e = assert_raises(Committee::InvalidResponse) do
188
+ assert_response_schema_confirm(200)
189
+ end
190
+ assert_match(/response definition does not exist/i, e.message)
191
+ end
192
+
193
+ it "detects an invalid response status code" do
194
+ @app = new_rack_app(JSON.generate([@correct_response]), {}, 419)
195
+
196
+ get "/characters"
197
+
198
+ e = assert_raises(Committee::InvalidResponse) do
199
+ assert_response_schema_confirm(419)
200
+ end
201
+ assert_match(/status code definition does not exist/i, e.message)
202
+ end
203
+
204
+ it "path undefined in schema" do
205
+ @app = new_rack_app(JSON.generate(@correct_response))
206
+ get "/undefined"
207
+ e = assert_raises(Committee::InvalidResponse) do
208
+ assert_response_schema_confirm
209
+ end
210
+ assert_match(/`GET \/undefined` undefined in schema/i, e.message)
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
351
+ end
352
+ end
353
+
354
+ private
355
+
356
+ def new_rack_app(response, headers={ "Content-Type" => "application/json" }, status_code = 200)
357
+ Rack::Builder.new {
358
+ run lambda { |_|
359
+ [status_code, headers, [response]]
360
+ }
361
+ }
362
+ end
363
+ end