committee 1.15.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. checksums.yaml +5 -5
  2. data/bin/committee-stub +11 -38
  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 +17 -0
  22. data/lib/committee/middleware/base.rb +46 -29
  23. data/lib/committee/middleware/request_validation.rb +31 -49
  24. data/lib/committee/middleware/response_validation.rb +48 -25
  25. data/lib/committee/middleware/stub.rb +62 -37
  26. data/lib/committee/middleware.rb +11 -0
  27. data/lib/committee/request_unpacker.rb +58 -50
  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 +68 -38
  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 +5 -2
  46. data/lib/committee/version.rb +5 -0
  47. data/lib/committee.rb +31 -18
  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 +96 -7
  60. data/test/middleware/request_validation_open_api_3_test.rb +626 -0
  61. data/test/middleware/request_validation_test.rb +423 -32
  62. data/test/middleware/response_validation_open_api_3_test.rb +291 -0
  63. data/test/middleware/response_validation_test.rb +125 -23
  64. data/test/middleware/stub_test.rb +81 -20
  65. data/test/request_unpacker_test.rb +126 -52
  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/{response_validator_test.rb → schema_validator/hyper_schema/response_validator_test.rb} +43 -6
  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 +334 -27
  77. data/test/test/schema_coverage_test.rb +216 -0
  78. data/test/test_helper.rb +108 -1
  79. data/test/validation_error_test.rb +3 -1
  80. metadata +190 -27
  81. data/lib/committee/query_params_coercer.rb +0 -45
  82. data/lib/committee/request_validator.rb +0 -44
  83. data/lib/committee/response_generator.rb +0 -35
  84. data/lib/committee/response_validator.rb +0 -59
  85. data/lib/committee/router.rb +0 -62
  86. data/test/query_params_coercer_test.rb +0 -70
  87. data/test/request_validator_test.rb +0 -103
  88. data/test/response_generator_test.rb +0 -61
  89. data/test/router_test.rb +0 -38
@@ -0,0 +1,626 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::Middleware::RequestValidation do
6
+ include Rack::Test::Methods
7
+
8
+ def app
9
+ @app
10
+ end
11
+
12
+ it "OpenAPI3 pass through a valid request" do
13
+ @app = new_rack_app(schema: open_api_3_schema)
14
+ params = {
15
+ "string_post_1" => "cloudnasium"
16
+ }
17
+ header "Content-Type", "application/json"
18
+ post "/characters", JSON.generate(params)
19
+
20
+ assert_equal 200, last_response.status
21
+ end
22
+
23
+ it "not parameter request" do
24
+ check_parameter_string = lambda { |_|
25
+ [200, {integer: 1}, []]
26
+ }
27
+
28
+ @app = new_rack_app_with_lambda(check_parameter_string, schema: open_api_3_schema)
29
+
30
+ put "/validate_no_parameter", {no_schema: 'no'}
31
+ end
32
+
33
+ it "passes given a datetime and with coerce_date_times enabled on GET endpoint" do
34
+ params = { "datetime_string" => "2016-04-01T16:00:00.000+09:00" }
35
+
36
+ check_parameter = lambda { |env|
37
+ assert_equal DateTime, env['test.query_hash']["datetime_string"].class
38
+ assert_equal String, env['rack.request.query_hash']["datetime_string"].class
39
+ [200, {}, []]
40
+ }
41
+
42
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_3_schema, coerce_date_times: true, query_hash_key: "test.query_hash")
43
+
44
+ get "/string_params_coercer", params
45
+ assert_equal 200, last_response.status
46
+ end
47
+
48
+ it "passes given a datetime and with coerce_date_times enabled on GET endpoint overwrite query_hash" do
49
+ params = { "datetime_string" => "2016-04-01T16:00:00.000+09:00" }
50
+
51
+ check_parameter = lambda { |env|
52
+ assert_nil env['committee.query_hash']
53
+ assert_equal DateTime, env['rack.request.query_hash']["datetime_string"].class
54
+ [200, {}, []]
55
+ }
56
+
57
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_3_schema, coerce_date_times: true, query_hash_key: "rack.request.query_hash")
58
+
59
+ get "/string_params_coercer", params
60
+ assert_equal 200, last_response.status
61
+ end
62
+
63
+ it "passes given a valid parameter on GET endpoint with request body and allow_get_body=true" do
64
+ params = { "data" => "abc" }
65
+
66
+ @app = new_rack_app(schema: open_api_3_schema, allow_get_body: true)
67
+
68
+ get "/get_endpoint_with_required_parameter", { no_problem: true }, { input: params.to_json }
69
+ assert_equal 200, last_response.status
70
+ end
71
+
72
+ it "errors given valid parameter on GET endpoint with request body and allow_get_body=false" do
73
+ params = { "data" => "abc" }
74
+
75
+ @app = new_rack_app(schema: open_api_3_schema, allow_get_body: false)
76
+
77
+ get "/get_endpoint_with_required_parameter", { no_problem: true }, { input: params.to_json }
78
+ assert_equal 400, last_response.status
79
+ end
80
+
81
+ it "passes given a datetime and with coerce_date_times enabled on GET endpoint with request body" do
82
+ params = { "datetime_string" => "2016-04-01T16:00:00.000+09:00" }
83
+
84
+ check_parameter = lambda { |env|
85
+ assert_equal DateTime, env['committee.params']["datetime_string"].class
86
+ [200, {}, []]
87
+ }
88
+
89
+ @app = new_rack_app_with_lambda(check_parameter,
90
+ schema: open_api_3_schema,
91
+ coerce_date_times: true,
92
+ allow_get_body: true)
93
+
94
+ get "/string_params_coercer", { no_problem: true }, { input: params.to_json }
95
+ assert_equal 200, last_response.status
96
+ end
97
+
98
+ it "passes given a datetime and with coerce_date_times enabled on POST endpoint" do
99
+ params = {
100
+ "nested_array" => [
101
+ {
102
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
103
+ "nested_coercer_object" => {
104
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
105
+ },
106
+ "nested_no_coercer_object" => {
107
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
108
+ },
109
+ "nested_coercer_array" => [
110
+ {
111
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
112
+ }
113
+ ],
114
+ "nested_no_coercer_array" => [
115
+ {
116
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
117
+ }
118
+ ]
119
+ },
120
+ {
121
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
122
+ }
123
+ ]
124
+ }
125
+
126
+ check_parameter = lambda { |env|
127
+ nested_array = env['committee.params']["nested_array"]
128
+ first_data = nested_array[0]
129
+ assert_kind_of DateTime, first_data["update_time"]
130
+
131
+ second_data = nested_array[1]
132
+ assert_kind_of DateTime, second_data["update_time"]
133
+
134
+ assert_kind_of DateTime, first_data["nested_coercer_object"]["update_time"]
135
+
136
+ assert_kind_of String, first_data["nested_no_coercer_object"]["update_time"]
137
+
138
+ assert_kind_of DateTime, first_data["nested_coercer_array"].first["update_time"]
139
+ assert_kind_of String, first_data["nested_no_coercer_array"].first["update_time"]
140
+ [200, {}, []]
141
+ }
142
+
143
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_3_schema, coerce_date_times: true, coerce_recursive: true)
144
+
145
+ header "Content-Type", "application/json"
146
+ post "/string_params_coercer", JSON.generate(params)
147
+
148
+ assert_equal 200, last_response.status
149
+ end
150
+
151
+ it "passes given an invalid datetime string with coerce_date_times enabled" do
152
+ @app = new_rack_app(schema: open_api_3_schema, coerce_date_times: true)
153
+ params = {
154
+ "datetime_string" => "invalid_datetime_format"
155
+ }
156
+ get "/string_params_coercer", params
157
+
158
+ assert_equal 400, last_response.status
159
+ assert_match(/invalid_datetime/i, last_response.body)
160
+ end
161
+
162
+ it "passes a nested object with recursive option" do
163
+ params = {
164
+ "nested_array" => [
165
+ {
166
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
167
+ "per_page" => "1",
168
+ }
169
+ ],
170
+ }
171
+
172
+ check_parameter = lambda { |env|
173
+ hash = env["committee.query_hash"]
174
+ assert_equal DateTime, hash['nested_array'].first['update_time'].class
175
+ assert_equal 1, hash['nested_array'].first['per_page']
176
+
177
+ [200, {}, []]
178
+ }
179
+
180
+ @app = new_rack_app_with_lambda(check_parameter,
181
+ coerce_query_params: true,
182
+ coerce_recursive: true,
183
+ coerce_date_times: true,
184
+ schema: open_api_3_schema)
185
+
186
+ get "/string_params_coercer", params
187
+
188
+ assert_equal 200, last_response.status
189
+ end
190
+
191
+ it "passes given a nested datetime and with coerce_recursive=true and coerce_date_times=true on POST endpoint" do
192
+ params = {
193
+ "nested_array" => [
194
+ {
195
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
196
+ "per_page" => 1,
197
+ "nested_coercer_object" => {
198
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
199
+ "threshold" => 1.5
200
+ },
201
+ "nested_no_coercer_object" => {
202
+ "per_page" => 1,
203
+ "threshold" => 1.5
204
+ },
205
+ "nested_coercer_array" => [
206
+ {
207
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
208
+ "threshold" => 1.5
209
+ }
210
+ ],
211
+ "nested_no_coercer_array" => [
212
+ {
213
+ "per_page" => 1,
214
+ "threshold" => 1.5
215
+ }
216
+ ],
217
+ "integer_array" => [
218
+ 1, 2, 3
219
+ ],
220
+ "datetime_array" => [
221
+ "2016-04-01T16:00:00.000+09:00",
222
+ "2016-04-01T17:00:00.000+09:00",
223
+ "2016-04-01T18:00:00.000+09:00"
224
+ ]
225
+ },
226
+ {
227
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
228
+ "per_page" => 1,
229
+ "threshold" => 1.5
230
+ },
231
+ {
232
+ "threshold" => 1.5,
233
+ "per_page" => 1
234
+ }
235
+ ]
236
+ }
237
+
238
+ check_parameter = lambda { |env|
239
+ hash = env['committee.params']
240
+ array = hash['nested_array']
241
+
242
+ assert_equal DateTime, array.first['update_time'].class
243
+ assert_equal 1, array.first['per_page']
244
+ assert_equal DateTime, array.first['nested_coercer_object']['update_time'].class
245
+ assert_equal 1, array.first['nested_no_coercer_object']['per_page']
246
+ assert_equal 2, array.first['integer_array'][1]
247
+ assert_equal DateTime, array.first['datetime_array'][0].class
248
+ [200, {}, []]
249
+ }
250
+
251
+ @app = new_rack_app_with_lambda(check_parameter,
252
+ coerce_date_times: true,
253
+ schema: open_api_3_schema)
254
+
255
+ header "Content-Type", "application/json"
256
+ post "/string_params_coercer", JSON.generate(params)
257
+ assert_equal 200, last_response.status
258
+ end
259
+
260
+ it "OpenAPI3 detects an invalid request" do
261
+ @app = new_rack_app(schema: open_api_3_schema, strict: true)
262
+ header "Content-Type", "application/json"
263
+ params = {
264
+ "string_post_1" => 1
265
+ }
266
+ post "/characters", JSON.generate(params)
267
+ assert_equal 400, last_response.status
268
+ assert_match(/expected string, but received Integer:/i, last_response.body)
269
+ end
270
+
271
+ it "rescues JSON errors" do
272
+ @app = new_rack_app(schema: open_api_3_schema)
273
+ header "Content-Type", "application/json"
274
+ post "/characters", "{x:y}"
275
+ assert_equal 400, last_response.status
276
+ assert_match(/valid json/i, last_response.body)
277
+ end
278
+
279
+ it "take a prefix" do
280
+ @app = new_rack_app(prefix: "/v1", schema: open_api_3_schema)
281
+ params = {
282
+ "string_post_1" => "cloudnasium"
283
+ }
284
+ header "Content-Type", "application/json"
285
+ post "/v1/characters", JSON.generate(params)
286
+ assert_equal 200, last_response.status
287
+ end
288
+
289
+ it "take a prefix with invalid data" do
290
+ @app = new_rack_app(prefix: "/v1", schema: open_api_3_schema)
291
+ params = {
292
+ "string_post_1" => 1
293
+ }
294
+ header "Content-Type", "application/json"
295
+ post "/v1/characters", JSON.generate(params)
296
+ assert_equal 400, last_response.status
297
+ assert_match(/expected string, but received Integer: /i, last_response.body)
298
+ end
299
+
300
+ it "ignores paths outside the prefix" do
301
+ @app = new_rack_app(prefix: "/v1", schema: open_api_3_schema)
302
+ params = {
303
+ "string_post_1" => 1
304
+ }
305
+ header "Content-Type", "application/json"
306
+ post "/characters", JSON.generate(params)
307
+ assert_equal 200, last_response.status
308
+ end
309
+
310
+ it "don't check prefix with no option" do
311
+ @app = new_rack_app(schema: open_api_3_schema)
312
+ params = {
313
+ "string_post_1" => 1
314
+ }
315
+ header "Content-Type", "application/json"
316
+ post "/v1/characters", JSON.generate(params)
317
+ assert_equal 200, last_response.status
318
+ end
319
+
320
+ it "OpenAPI3 pass not exist href" do
321
+ @app = new_rack_app(schema: open_api_3_schema)
322
+ get "/unknown"
323
+ assert_equal 200, last_response.status
324
+ end
325
+
326
+ it "OpenAPI3 pass not exist href in strict mode" do
327
+ @app = new_rack_app(schema: open_api_3_schema, strict: true)
328
+ get "/unknown"
329
+ assert_equal 404, last_response.status
330
+ end
331
+
332
+ it "OpenAPI3 parser not exist required key" do
333
+ @app = new_rack_app(raise: true, schema: open_api_3_schema)
334
+
335
+ e = assert_raises(Committee::InvalidRequest) do
336
+ get "/validate", nil
337
+ end
338
+
339
+ assert_match(/missing required parameters: query_string/i, e.message)
340
+ end
341
+
342
+ it "raises error when required path parameter is invalid" do
343
+ @app = new_rack_app(raise: true, schema: open_api_3_schema)
344
+
345
+ e = assert_raises(Committee::InvalidRequest) do
346
+ not_an_integer = 'abc'
347
+ get "/coerce_path_params/#{not_an_integer}", nil
348
+ end
349
+
350
+ assert_match(/expected integer, but received String: \"abc\"/i, e.message)
351
+ end
352
+
353
+ it "optionally raises an error" do
354
+ @app = new_rack_app(raise: true, schema: open_api_3_schema)
355
+ header "Content-Type", "application/json"
356
+ assert_raises(Committee::InvalidRequest) do
357
+ post "/characters", "{x:y}"
358
+ end
359
+ end
360
+
361
+ it "optionally coerces query params" do
362
+ @app = new_rack_app(coerce_query_params: true, schema: open_api_3_schema)
363
+ header "Content-Type", "application/json"
364
+ get "/string_params_coercer", {"integer_1" => "1"}
365
+ assert_equal 200, last_response.status
366
+ end
367
+
368
+ it "still raises an error if query param coercion is not possible" do
369
+ @app = new_rack_app(coerce_query_params: false, schema: open_api_3_schema)
370
+ header "Content-Type", "application/json"
371
+ get "/string_params_coercer", {"integer_1" => "1"}
372
+
373
+ assert_equal 400, last_response.status
374
+ assert_match(/expected integer, but received String:/i, last_response.body)
375
+ end
376
+
377
+ it "passes through a valid request for OpenAPI3" do
378
+ check_parameter = lambda { |env|
379
+ assert_equal 3, env['committee.query_hash']['limit'] #5.0.x-
380
+ [200, {}, []]
381
+ }
382
+
383
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_3_schema)
384
+ get "/characters?limit=3"
385
+ assert_equal 200, last_response.status
386
+ end
387
+
388
+ it "detects an invalid request for OpenAPI" do
389
+ @app = new_rack_app(schema: open_api_3_schema)
390
+ get "/characters?limit=foo"
391
+
392
+ assert_equal 400, last_response.status
393
+ assert_match(/expected integer, but received String: \\"foo\\"/i, last_response.body)
394
+ end
395
+
396
+ it "ignores errors when ignore_error: true" do
397
+ @app = new_rack_app(schema: open_api_3_schema, ignore_error: true)
398
+ get "/characters?limit=foo"
399
+
400
+ assert_equal 200, last_response.status
401
+ end
402
+
403
+ it "coerce string to integer" do
404
+ check_parameter_string = lambda { |env|
405
+ assert env['committee.params']['integer'].is_a?(Integer)
406
+ [200, {}, []]
407
+ }
408
+
409
+ @app = new_rack_app_with_lambda(check_parameter_string, schema: open_api_3_schema, coerce_path_params: true)
410
+ get "/coerce_path_params/1"
411
+ end
412
+
413
+ describe "overwrite same parameter (old rule)" do
414
+ # (high priority) path_hash_key -> request_body_hash -> query_param
415
+ it "set query parameter to committee.params and query hash" do
416
+ @app = new_rack_app_with_lambda(lambda do |env|
417
+ assert_equal env['committee.params']['integer'], 42
418
+ assert_equal env['committee.params'][:integer], 42
419
+ assert_equal env['committee.query_hash']['integer'], 42
420
+ #assert_equal env['rack.request.query_hash'][:integer], 42 # this isn't hash indifferent hash because we use rack.request.query_hash
421
+ [204, {}, []]
422
+ end, schema: open_api_3_schema, parameter_overwite_by_rails_rule: false)
423
+
424
+ header "Content-Type", "application/json"
425
+ post '/overwrite_same_parameter?integer=42'
426
+ assert_equal 204, last_response.status
427
+ end
428
+
429
+ it "request body precedence over query parameter" do
430
+ @app = new_rack_app_with_lambda(lambda do |env|
431
+ assert_equal env['committee.params']['integer'], 21
432
+ assert_equal env['committee.params'][:integer], 21
433
+ assert_equal env['committee.request_body_hash']['integer'], 21
434
+ assert_equal env['committee.request_body_hash'][:integer], 21
435
+ assert_equal env['committee.query_hash']['integer'], 42
436
+ [204, {}, []]
437
+ end, schema: open_api_3_schema, parameter_overwite_by_rails_rule: false)
438
+
439
+ params = {integer: 21}
440
+
441
+ header "Content-Type", "application/json"
442
+ post '/overwrite_same_parameter?integer=42', JSON.generate(params)
443
+ assert_equal 204, last_response.status
444
+ end
445
+
446
+ it "path parameter precedence over request body" do
447
+ @app = new_rack_app_with_lambda(lambda do |env|
448
+ assert_equal env['committee.params']['integer'], 84
449
+ assert_equal env['committee.params'][:integer], 84
450
+ assert_equal env['committee.path_hash']['integer'], 84
451
+ assert_equal env['committee.path_hash'][:integer], 84
452
+ assert_equal env['committee.request_body_hash']['integer'], 21
453
+ assert_equal env['committee.request_body_hash'][:integer], 21
454
+ assert_equal env['committee.query_hash']['integer'], 84 # we can't use query_parameter :(
455
+ #assert_equal env['rack.request.query_hash'][:integer], 21 # this isn't hash indifferent hash because we use rack.request.query_hash
456
+ [204, {}, []]
457
+ end, schema: open_api_3_schema, parameter_overwite_by_rails_rule: false)
458
+
459
+ params = {integer: 21}
460
+
461
+ header "Content-Type", "application/json"
462
+ post '/overwrite_same_parameter/84?integer=42', JSON.generate(params)
463
+ assert_equal 204, last_response.status
464
+ end
465
+ end
466
+
467
+ describe "overwrite same parameter (new rule and seme to Rails)" do
468
+ # (high priority) path_hash_key -> query_param -> request_body_hash
469
+ it "set request body to committee.params and query hash" do
470
+ @app = new_rack_app_with_lambda(lambda do |env|
471
+ assert_equal env['committee.params']['integer'], 21
472
+ assert_equal env['committee.params'][:integer], 21
473
+ assert_equal env['committee.request_body_hash']['integer'], 21
474
+ assert_equal env['committee.request_body_hash'][:integer], 21
475
+ [204, {}, []]
476
+ end, schema: open_api_3_schema)
477
+
478
+ params = {integer: 21}
479
+
480
+ header "Content-Type", "application/json"
481
+ post '/overwrite_same_parameter', JSON.generate(params)
482
+ assert_equal 204, last_response.status
483
+ end
484
+
485
+ it "query parameter precedence over request body" do
486
+ @app = new_rack_app_with_lambda(lambda do |env|
487
+ assert_equal env['committee.params']['integer'], 42
488
+ assert_equal env['committee.params'][:integer], 42
489
+ assert_equal env['committee.request_body_hash']['integer'], 21
490
+ assert_equal env['committee.request_body_hash'][:integer], 21
491
+ assert_equal env['committee.query_hash']['integer'], 42
492
+ [204, {}, []]
493
+ end, schema: open_api_3_schema)
494
+
495
+ params = {integer: 21}
496
+
497
+ header "Content-Type", "application/json"
498
+ post '/overwrite_same_parameter?integer=42', JSON.generate(params)
499
+ assert_equal 204, last_response.status
500
+ end
501
+
502
+ it "path path parameter precedence over query parameter" do
503
+ @app = new_rack_app_with_lambda(lambda do |env|
504
+ assert_equal env['committee.params']['integer'], 84
505
+ assert_equal env['committee.params'][:integer], 84
506
+ assert_equal env['committee.request_body_hash']['integer'], 21
507
+ assert_equal env['committee.request_body_hash'][:integer], 21
508
+ assert_equal env['committee.query_hash']['integer'], 84 # we can't use query_parameter :(
509
+ assert_equal env['committee.path_hash']['integer'], 84
510
+ assert_equal env['committee.path_hash'][:integer], 84
511
+ [204, {}, []]
512
+ end, schema: open_api_3_schema)
513
+
514
+ params = {integer: 21}
515
+
516
+ header "Content-Type", "application/json"
517
+ post '/overwrite_same_parameter/84?integer=42', JSON.generate(params)
518
+ assert_equal 204, last_response.status
519
+ end
520
+ end
521
+
522
+ it "unpacker test" do
523
+ @app = new_rack_app_with_lambda(lambda do |env|
524
+ assert_equal '21', env['committee.params']['integer'] # query parameter has precedence
525
+ assert_equal '21', env['committee.params'][:integer]
526
+ assert_equal '21', env['rack.request.query_hash']['integer']
527
+ assert_equal 42, env['committee.request_body_hash']['integer']
528
+ [204, {}, []]
529
+ end, schema: open_api_3_schema, raise: true)
530
+
531
+ header "Content-Type", "application/x-www-form-urlencoded"
532
+ post '/validate?integer=21', "integer=42"
533
+ assert_equal 204, last_response.status
534
+ end
535
+
536
+ it "OpenAPI3 raise not support method" do
537
+ @app = new_rack_app(schema: open_api_3_schema)
538
+
539
+ e = assert_raises(RuntimeError) {
540
+ custom_request('TRACE', "/characters")
541
+ }
542
+
543
+ assert_equal 'Committee OpenAPI3 not support trace method', e.message
544
+ end
545
+
546
+ describe 'check header' do
547
+ [
548
+ { check_header: true, description: 'valid value', value: 1, expected: { status: 200 } },
549
+ { check_header: true, description: 'missing value', value: nil, expected: { status: 400, error: 'missing required parameters: integer' } },
550
+ { check_header: true, description: 'invalid value', value: 'x', expected: { status: 400, error: 'expected integer, but received String: \\"x\\"' } },
551
+
552
+ { check_header: false, description: 'valid value', value: 1, expected: { status: 200 } },
553
+ { check_header: false, description: 'missing value', value: nil, expected: { status: 200 } },
554
+ { check_header: false, description: 'invalid value', value: 'x', expected: { status: 200 } },
555
+ ].each do |h|
556
+ check_header = h[:check_header]
557
+ description = h[:description]
558
+ value = h[:value]
559
+ expected = h[:expected]
560
+ describe "when #{check_header}" do
561
+ %w(get post put patch delete options).each do |method|
562
+ describe method do
563
+ describe description do
564
+ it (expected[:error].nil? ? 'should pass' : 'should fail') do
565
+ @app = new_rack_app(schema: open_api_3_schema, check_header: check_header)
566
+
567
+ header 'integer', value
568
+ send(method, "/header")
569
+
570
+ assert_equal expected[:status], last_response.status
571
+ assert_match(expected[:error], last_response.body) if expected[:error]
572
+ end
573
+ end
574
+ end
575
+ end
576
+ end
577
+ end
578
+ end
579
+
580
+ describe ':accept_request_filter' do
581
+ [
582
+ { description: 'when not specified, includes everything', accept_request_filter: nil, expected: { status: 400 } },
583
+ { description: 'when predicate matches, performs validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/c') }, expected: { status: 400 } },
584
+ { description: 'when predicate does not match, skips validation', accept_request_filter: -> (request) { request.path.start_with?('/v1/x') }, expected: { status: 200 } },
585
+ ].each do |h|
586
+ description = h[:description]
587
+ accept_request_filter = h[:accept_request_filter]
588
+ expected = h[:expected]
589
+ it description do
590
+ @app = new_rack_app(prefix: '/v1', schema: open_api_3_schema, accept_request_filter: accept_request_filter)
591
+
592
+ post 'v1/characters', JSON.generate(string_post_1: 1)
593
+
594
+ assert_equal expected[:status], last_response.status
595
+ end
596
+ end
597
+ end
598
+
599
+ it 'does not suppress application error' do
600
+ @app = new_rack_app_with_lambda(lambda { |_|
601
+ JSON.load('-') # invalid json
602
+ }, schema: open_api_3_schema, raise: true)
603
+
604
+ assert_raises(JSON::ParserError) do
605
+ get "/error", nil
606
+ end
607
+ end
608
+
609
+ private
610
+
611
+ def new_rack_app(options = {})
612
+ new_rack_app_with_lambda(lambda { |_|
613
+ [200, {}, []]
614
+ }, options)
615
+ end
616
+
617
+ def new_rack_app_with_lambda(check_lambda, options = {})
618
+ # TODO: delete when 5.0.0 released because default value changed
619
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
620
+
621
+ Rack::Builder.new {
622
+ use Committee::Middleware::RequestValidation, options
623
+ run check_lambda
624
+ }
625
+ end
626
+ end