committee 5.6.1 → 5.6.3

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 (49) hide show
  1. checksums.yaml +4 -4
  2. data/lib/committee/drivers/open_api_3/driver.rb +7 -1
  3. data/lib/committee/drivers.rb +2 -3
  4. data/lib/committee/errors.rb +11 -0
  5. data/lib/committee/middleware/base.rb +11 -5
  6. data/lib/committee/middleware/options/base.rb +107 -0
  7. data/lib/committee/middleware/options/request_validation.rb +80 -0
  8. data/lib/committee/middleware/options/response_validation.rb +46 -0
  9. data/lib/committee/middleware/options.rb +12 -0
  10. data/lib/committee/middleware/request_validation.rb +7 -1
  11. data/lib/committee/middleware/response_validation.rb +6 -2
  12. data/lib/committee/middleware.rb +1 -0
  13. data/lib/committee/schema_validator/hyper_schema/response_generator.rb +1 -3
  14. data/lib/committee/schema_validator/hyper_schema/response_validator.rb +14 -1
  15. data/lib/committee/schema_validator/hyper_schema/router.rb +1 -1
  16. data/lib/committee/schema_validator/hyper_schema.rb +3 -14
  17. data/lib/committee/schema_validator/open_api_3/operation_wrapper.rb +43 -13
  18. data/lib/committee/schema_validator/open_api_3/parameter_deserializer.rb +556 -0
  19. data/lib/committee/schema_validator/open_api_3/response_validator.rb +16 -2
  20. data/lib/committee/schema_validator/open_api_3.rb +19 -13
  21. data/lib/committee/schema_validator/option.rb +6 -17
  22. data/lib/committee/schema_validator.rb +12 -1
  23. data/lib/committee/test/except_parameter.rb +416 -0
  24. data/lib/committee/test/methods.rb +38 -2
  25. data/lib/committee/version.rb +1 -1
  26. data/lib/committee.rb +1 -1
  27. data/test/drivers/open_api_2/driver_test.rb +4 -16
  28. data/test/drivers/open_api_2/parameter_schema_builder_test.rb +4 -50
  29. data/test/drivers_test.rb +35 -21
  30. data/test/middleware/options/base_test.rb +120 -0
  31. data/test/middleware/options/request_validation_test.rb +177 -0
  32. data/test/middleware/options/response_validation_test.rb +121 -0
  33. data/test/middleware/request_validation_open_api_3_test.rb +200 -80
  34. data/test/middleware/request_validation_test.rb +13 -70
  35. data/test/middleware/response_validation_open_api_3_test.rb +40 -17
  36. data/test/middleware/response_validation_test.rb +3 -14
  37. data/test/request_unpacker_test.rb +2 -10
  38. data/test/schema_validator/hyper_schema/parameter_coercer_test.rb +1 -37
  39. data/test/schema_validator/hyper_schema/request_validator_test.rb +6 -30
  40. data/test/schema_validator/hyper_schema/router_test.rb +5 -0
  41. data/test/schema_validator/hyper_schema/string_params_coercer_test.rb +1 -37
  42. data/test/schema_validator/open_api_3/operation_wrapper_test.rb +58 -43
  43. data/test/schema_validator/open_api_3/parameter_deserializer_test.rb +457 -0
  44. data/test/schema_validator/open_api_3/request_validator_test.rb +1 -2
  45. data/test/schema_validator/open_api_3/response_validator_test.rb +3 -11
  46. data/test/schema_validator_test.rb +41 -0
  47. data/test/test/methods_test.rb +238 -105
  48. data/test/test/schema_coverage_test.rb +8 -155
  49. metadata +11 -1
@@ -84,42 +84,14 @@ describe Committee::Middleware::RequestValidation do
84
84
  [200, {}, []]
85
85
  }
86
86
 
87
- @app = new_rack_app_with_lambda(check_parameter,
88
- schema: open_api_3_schema,
89
- coerce_date_times: true,
90
- allow_get_body: true)
87
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_3_schema, coerce_date_times: true, allow_get_body: true)
91
88
 
92
89
  get "/string_params_coercer", { no_problem: true }, { input: params.to_json }
93
90
  assert_equal 200, last_response.status
94
91
  end
95
92
 
96
93
  it "passes given a datetime and with coerce_date_times enabled on POST endpoint" do
97
- params = {
98
- "nested_array" => [
99
- {
100
- "update_time" => "2016-04-01T16:00:00.000+09:00",
101
- "nested_coercer_object" => {
102
- "update_time" => "2016-04-01T16:00:00.000+09:00",
103
- },
104
- "nested_no_coercer_object" => {
105
- "update_time" => "2016-04-01T16:00:00.000+09:00",
106
- },
107
- "nested_coercer_array" => [
108
- {
109
- "update_time" => "2016-04-01T16:00:00.000+09:00",
110
- }
111
- ],
112
- "nested_no_coercer_array" => [
113
- {
114
- "update_time" => "2016-04-01T16:00:00.000+09:00",
115
- }
116
- ]
117
- },
118
- {
119
- "update_time" => "2016-04-01T16:00:00.000+09:00",
120
- }
121
- ]
122
- }
94
+ params = { "nested_array" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "nested_coercer_object" => { "update_time" => "2016-04-01T16:00:00.000+09:00", }, "nested_no_coercer_object" => { "update_time" => "2016-04-01T16:00:00.000+09:00", }, "nested_coercer_array" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", }], "nested_no_coercer_array" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", }] }, { "update_time" => "2016-04-01T16:00:00.000+09:00", }] }
123
95
 
124
96
  check_parameter = lambda { |env|
125
97
  nested_array = env['committee.params']["nested_array"]
@@ -166,11 +138,7 @@ describe Committee::Middleware::RequestValidation do
166
138
  [200, {}, []]
167
139
  }
168
140
 
169
- @app = new_rack_app_with_lambda(check_parameter,
170
- coerce_query_params: true,
171
- coerce_recursive: true,
172
- coerce_date_times: true,
173
- schema: open_api_3_schema)
141
+ @app = new_rack_app_with_lambda(check_parameter, coerce_query_params: true, coerce_recursive: true, coerce_date_times: true, schema: open_api_3_schema)
174
142
 
175
143
  get "/string_params_coercer", params
176
144
 
@@ -178,51 +146,7 @@ describe Committee::Middleware::RequestValidation do
178
146
  end
179
147
 
180
148
  it "passes given a nested datetime and with coerce_recursive=true and coerce_date_times=true on POST endpoint" do
181
- params = {
182
- "nested_array" => [
183
- {
184
- "update_time" => "2016-04-01T16:00:00.000+09:00",
185
- "per_page" => 1,
186
- "nested_coercer_object" => {
187
- "update_time" => "2016-04-01T16:00:00.000+09:00",
188
- "threshold" => 1.5
189
- },
190
- "nested_no_coercer_object" => {
191
- "per_page" => 1,
192
- "threshold" => 1.5
193
- },
194
- "nested_coercer_array" => [
195
- {
196
- "update_time" => "2016-04-01T16:00:00.000+09:00",
197
- "threshold" => 1.5
198
- }
199
- ],
200
- "nested_no_coercer_array" => [
201
- {
202
- "per_page" => 1,
203
- "threshold" => 1.5
204
- }
205
- ],
206
- "integer_array" => [
207
- 1, 2, 3
208
- ],
209
- "datetime_array" => [
210
- "2016-04-01T16:00:00.000+09:00",
211
- "2016-04-01T17:00:00.000+09:00",
212
- "2016-04-01T18:00:00.000+09:00"
213
- ]
214
- },
215
- {
216
- "update_time" => "2016-04-01T16:00:00.000+09:00",
217
- "per_page" => 1,
218
- "threshold" => 1.5
219
- },
220
- {
221
- "threshold" => 1.5,
222
- "per_page" => 1
223
- }
224
- ]
225
- }
149
+ params = { "nested_array" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "per_page" => 1, "nested_coercer_object" => { "update_time" => "2016-04-01T16:00:00.000+09:00", "threshold" => 1.5 }, "nested_no_coercer_object" => { "per_page" => 1, "threshold" => 1.5 }, "nested_coercer_array" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "threshold" => 1.5 }], "nested_no_coercer_array" => [{ "per_page" => 1, "threshold" => 1.5 }], "integer_array" => [1, 2, 3], "datetime_array" => ["2016-04-01T16:00:00.000+09:00", "2016-04-01T17:00:00.000+09:00", "2016-04-01T18:00:00.000+09:00"] }, { "update_time" => "2016-04-01T16:00:00.000+09:00", "per_page" => 1, "threshold" => 1.5 }, { "threshold" => 1.5, "per_page" => 1 }] }
226
150
 
227
151
  check_parameter = lambda { |env|
228
152
  hash = env['committee.params']
@@ -330,6 +254,14 @@ describe Committee::Middleware::RequestValidation do
330
254
  assert_equal 200, last_response.status
331
255
  end
332
256
 
257
+ it "ignores similar prefix paths outside the prefix in strict mode" do
258
+ @app = new_rack_app(prefix: "/v1", schema: open_api_3_schema, strict: true)
259
+ params = { "string_post_1" => 1 }
260
+ header "Content-Type", "application/json"
261
+ post "/v11/characters", JSON.generate(params)
262
+ assert_equal 200, last_response.status
263
+ end
264
+
333
265
  it "don't check prefix with no option" do
334
266
  @app = new_rack_app(schema: open_api_3_schema)
335
267
  params = { "string_post_1" => 1 }
@@ -431,6 +363,26 @@ describe Committee::Middleware::RequestValidation do
431
363
  get "/coerce_path_params/1"
432
364
  end
433
365
 
366
+ it "coerces path params even when query param coercion is disabled" do
367
+ check_parameter_string = lambda { |env|
368
+ assert env['committee.path_hash']['integer'].is_a?(Integer)
369
+ assert env['committee.params']['integer'].is_a?(Integer)
370
+ [200, {}, []]
371
+ }
372
+
373
+ @app = new_rack_app_with_lambda(check_parameter_string, schema: open_api_3_schema, coerce_path_params: true, coerce_query_params: false)
374
+ get "/coerce_path_params/1"
375
+ assert_equal 200, last_response.status
376
+ end
377
+
378
+ it "does not coerce path params when path coercion is disabled even if query coercion is enabled" do
379
+ @app = new_rack_app(schema: open_api_3_schema, coerce_path_params: false, coerce_query_params: true)
380
+ get "/coerce_path_params/1"
381
+
382
+ assert_equal 400, last_response.status
383
+ assert_match(/integer/i, last_response.body)
384
+ end
385
+
434
386
  describe "overwrite same parameter (old rule)" do
435
387
  # (high priority) path_hash_key -> request_body_hash -> query_param
436
388
  it "set query parameter to committee.params and query hash" do
@@ -627,6 +579,125 @@ describe Committee::Middleware::RequestValidation do
627
579
  end
628
580
  end
629
581
 
582
+ describe ':strict_query_params option' do
583
+ it 'allows unknown query params when strict_query_params is false' do
584
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: false)
585
+ get "/characters", { limit: 10, unknown_param: "value" }
586
+ assert_equal 200, last_response.status
587
+ end
588
+
589
+ it 'rejects unknown query params when strict_query_params is true' do
590
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
591
+ get "/characters", { limit: 10, unknown_param: "value" }
592
+ assert_equal 400, last_response.status
593
+ assert_match(/unknown_param/, last_response.body)
594
+ end
595
+
596
+ it 'allows defined query params when strict_query_params is true' do
597
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
598
+ get "/characters", { limit: 10, school_name: "test" }
599
+ assert_equal 200, last_response.status
600
+ end
601
+
602
+ it 'handles empty query params when strict_query_params is true' do
603
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
604
+ get "/characters"
605
+ assert_equal 200, last_response.status
606
+ end
607
+
608
+ it 'rejects multiple unknown query params' do
609
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
610
+ get "/characters", { limit: 10, unknown1: "a", unknown2: "b" }
611
+ assert_equal 400, last_response.status
612
+ assert_match(/unknown1/, last_response.body)
613
+ assert_match(/unknown2/, last_response.body)
614
+ end
615
+
616
+ it 'allows partial query params (only some defined params)' do
617
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
618
+ get "/characters", { limit: 10 }
619
+ assert_equal 200, last_response.status
620
+ end
621
+
622
+ it 'works with POST requests that have query params' do
623
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
624
+ header "Content-Type", "application/json"
625
+ post "/additional_properties?first_name=test&unknown=value", JSON.generate(last_name: "test")
626
+ assert_equal 400, last_response.status
627
+ assert_match(/unknown/, last_response.body)
628
+ end
629
+
630
+ it 'allows defined query params in POST requests' do
631
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
632
+ header "Content-Type", "application/json"
633
+ post "/additional_properties?first_name=test", JSON.generate(last_name: "test")
634
+ assert_equal 200, last_response.status
635
+ end
636
+
637
+ it 'works with DELETE requests' do
638
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
639
+ delete "/characters?limit=10"
640
+ assert_equal 200, last_response.status
641
+ end
642
+
643
+ it 'rejects unknown query params in DELETE requests' do
644
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
645
+ delete "/characters?limit=10&unknown=value"
646
+ assert_equal 400, last_response.status
647
+ assert_match(/unknown/, last_response.body)
648
+ end
649
+
650
+ it 'works with HEAD requests' do
651
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
652
+ head "/characters?limit=10"
653
+ assert_equal 200, last_response.status
654
+ end
655
+
656
+ it 'rejects unknown query params in HEAD requests' do
657
+ @app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
658
+ head "/characters?limit=10&unknown=value"
659
+ assert_equal 400, last_response.status
660
+ end
661
+ end
662
+
663
+ describe 'bracket-style query params' do
664
+ it 'validates query params declared with bracket notation names' do
665
+ check_parameter = lambda { |env|
666
+ assert_equal '/test', env['committee.query_hash']['filter[slug]']
667
+ refute env['committee.query_hash'].key?('filter')
668
+ [200, {}, []]
669
+ }
670
+
671
+ @app = new_rack_app_with_lambda(check_parameter, schema: query_param_schema(bracket_notation_query_parameter))
672
+
673
+ get '/events?filter[slug]=%2Ftest'
674
+
675
+ assert_equal 200, last_response.status
676
+ end
677
+
678
+ it 'rejects unknown nested query params with strict_query_params' do
679
+ @app = new_rack_app(schema: query_param_schema(bracket_notation_query_parameter), strict_query_params: true)
680
+
681
+ get '/events?filter[slug]=%2Ftest&filter[status]=active'
682
+
683
+ assert_equal 400, last_response.status
684
+ assert_match(/filter\[status\]/, last_response.body)
685
+ end
686
+
687
+ it 'continues to support deepObject query params from Rack nested hashes' do
688
+ check_parameter = lambda { |env|
689
+ assert_equal '/test', env['committee.query_hash']['filter']['slug']
690
+ [200, {}, []]
691
+ }
692
+
693
+ @app = new_rack_app_with_lambda(check_parameter, schema: query_param_schema(deep_object_query_parameter))
694
+
695
+ get '/events?filter[slug]=%2Ftest'
696
+
697
+ assert_equal 200, last_response.status
698
+ end
699
+ end
700
+
630
701
  private
631
702
 
632
703
  def new_rack_app(options = {})
@@ -641,4 +712,53 @@ describe Committee::Middleware::RequestValidation do
641
712
  run check_lambda
642
713
  }
643
714
  end
715
+
716
+ def query_param_schema(parameter)
717
+ Committee::Drivers.load_from_data(query_param_document(parameter), nil, parser_options: { strict_reference_validation: true })
718
+ end
719
+
720
+ def bracket_notation_query_parameter
721
+ {
722
+ 'name' => 'filter[slug]',
723
+ 'in' => 'query',
724
+ 'required' => true,
725
+ 'schema' => { 'type' => 'string' },
726
+ }
727
+ end
728
+
729
+ def deep_object_query_parameter
730
+ {
731
+ 'name' => 'filter',
732
+ 'in' => 'query',
733
+ 'required' => true,
734
+ 'style' => 'deepObject',
735
+ 'explode' => true,
736
+ 'schema' => {
737
+ 'type' => 'object',
738
+ 'required' => ['slug'],
739
+ 'properties' => {
740
+ 'slug' => { 'type' => 'string' },
741
+ },
742
+ },
743
+ }
744
+ end
745
+
746
+ def query_param_document(parameter)
747
+ {
748
+ 'openapi' => '3.0.3',
749
+ 'info' => { 'title' => 'test', 'version' => '1.0.0' },
750
+ 'paths' => {
751
+ '/events' => {
752
+ 'get' => {
753
+ 'parameters' => [parameter],
754
+ 'responses' => {
755
+ '200' => {
756
+ 'description' => 'ok',
757
+ },
758
+ },
759
+ },
760
+ },
761
+ },
762
+ }
763
+ end
644
764
  end
@@ -9,49 +9,7 @@ describe Committee::Middleware::RequestValidation do
9
9
  @app
10
10
  end
11
11
 
12
- ARRAY_PROPERTY = [
13
- {
14
- "update_time" => "2016-04-01T16:00:00.000+09:00",
15
- "per_page" => 1,
16
- "nested_coercer_object" => {
17
- "update_time" => "2016-04-01T16:00:00.000+09:00",
18
- "threshold" => 1.5
19
- },
20
- "nested_no_coercer_object" => {
21
- "per_page" => 1,
22
- "threshold" => 1.5
23
- },
24
- "nested_coercer_array" => [
25
- {
26
- "update_time" => "2016-04-01T16:00:00.000+09:00",
27
- "threshold" => 1.5
28
- }
29
- ],
30
- "nested_no_coercer_array" => [
31
- {
32
- "per_page" => 1,
33
- "threshold" => 1.5
34
- }
35
- ],
36
- "integer_array" => [
37
- 1, 2, 3
38
- ],
39
- "datetime_array" => [
40
- "2016-04-01T16:00:00.000+09:00",
41
- "2016-04-01T17:00:00.000+09:00",
42
- "2016-04-01T18:00:00.000+09:00"
43
- ]
44
- },
45
- {
46
- "update_time" => "2016-04-01T16:00:00.000+09:00",
47
- "per_page" => 1,
48
- "threshold" => 1.5
49
- },
50
- {
51
- "threshold" => 1.5,
52
- "per_page" => 1
53
- }
54
- ]
12
+ ARRAY_PROPERTY = [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "per_page" => 1, "nested_coercer_object" => { "update_time" => "2016-04-01T16:00:00.000+09:00", "threshold" => 1.5 }, "nested_no_coercer_object" => { "per_page" => 1, "threshold" => 1.5 }, "nested_coercer_array" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "threshold" => 1.5 }], "nested_no_coercer_array" => [{ "per_page" => 1, "threshold" => 1.5 }], "integer_array" => [1, 2, 3], "datetime_array" => ["2016-04-01T16:00:00.000+09:00", "2016-04-01T17:00:00.000+09:00", "2016-04-01T18:00:00.000+09:00"] }, { "update_time" => "2016-04-01T16:00:00.000+09:00", "per_page" => 1, "threshold" => 1.5 }, { "threshold" => 1.5, "per_page" => 1 }]
55
13
 
56
14
  it "passes through a valid request" do
57
15
  @app = new_rack_app(schema: hyper_schema)
@@ -98,11 +56,7 @@ describe Committee::Middleware::RequestValidation do
98
56
 
99
57
  it "passes a nested object with recursive option" do
100
58
  key_name = "update_time"
101
- params = {
102
- key_name => "2016-04-01T16:00:00.000+09:00",
103
- "query" => "cloudnasium",
104
- "array_property" => ARRAY_PROPERTY
105
- }
59
+ params = { key_name => "2016-04-01T16:00:00.000+09:00", "query" => "cloudnasium", "array_property" => ARRAY_PROPERTY }
106
60
 
107
61
  check_parameter = lambda { |env|
108
62
  hash = env['rack.request.query_hash']
@@ -116,11 +70,7 @@ describe Committee::Middleware::RequestValidation do
116
70
  [200, {}, []]
117
71
  }
118
72
 
119
- @app = new_rack_app_with_lambda(check_parameter,
120
- coerce_query_params: true,
121
- coerce_recursive: true,
122
- coerce_date_times: true,
123
- schema: hyper_schema)
73
+ @app = new_rack_app_with_lambda(check_parameter, coerce_query_params: true, coerce_recursive: true, coerce_date_times: true, schema: hyper_schema)
124
74
 
125
75
  get "/search/apps", params
126
76
  assert_equal 200, last_response.status
@@ -128,11 +78,7 @@ describe Committee::Middleware::RequestValidation do
128
78
 
129
79
  it "passes a nested object with coerce_recursive false" do
130
80
  key_name = "update_time"
131
- params = {
132
- key_name => "2016-04-01T16:00:00.000+09:00",
133
- "query" => "cloudnasium",
134
- "array_property" => ARRAY_PROPERTY
135
- }
81
+ params = { key_name => "2016-04-01T16:00:00.000+09:00", "query" => "cloudnasium", "array_property" => ARRAY_PROPERTY }
136
82
 
137
83
  @app = new_rack_app(coerce_query_params: true, coerce_date_times: true, coerce_recursive: false, schema: hyper_schema)
138
84
 
@@ -142,11 +88,7 @@ describe Committee::Middleware::RequestValidation do
142
88
 
143
89
  it "passes a nested object with coerce_recursive default(true)" do
144
90
  key_name = "update_time"
145
- params = {
146
- key_name => "2016-04-01T16:00:00.000+09:00",
147
- "query" => "cloudnasium",
148
- "array_property" => ARRAY_PROPERTY
149
- }
91
+ params = { key_name => "2016-04-01T16:00:00.000+09:00", "query" => "cloudnasium", "array_property" => ARRAY_PROPERTY }
150
92
 
151
93
  @app = new_rack_app(coerce_query_params: true, coerce_date_times: true, schema: hyper_schema)
152
94
 
@@ -321,6 +263,13 @@ describe Committee::Middleware::RequestValidation do
321
263
  assert_equal 200, last_response.status
322
264
  end
323
265
 
266
+ it "ignores similar prefix paths outside the prefix in strict mode" do
267
+ @app = new_rack_app(prefix: "/v1", schema: hyper_schema, strict: true)
268
+ header "Content-Type", "application/json"
269
+ post "/v11/apps", JSON.generate({ "name" => 1 })
270
+ assert_equal 200, last_response.status
271
+ end
272
+
324
273
  it "routes to paths not in schema" do
325
274
  @app = new_rack_app(schema: hyper_schema)
326
275
  get "/not-a-resource"
@@ -429,13 +378,7 @@ describe Committee::Middleware::RequestValidation do
429
378
  end
430
379
 
431
380
  it "not exist path and options" do
432
- options = {
433
- coerce_form_params: true,
434
- coerce_date_times: true,
435
- coerce_query_params: true,
436
- coerce_path_params: true,
437
- coerce_recursive: true
438
- }
381
+ options = { coerce_form_params: true, coerce_date_times: true, coerce_query_params: true, coerce_path_params: true, coerce_recursive: true }
439
382
 
440
383
  @app = new_rack_app({ schema: hyper_schema }.merge(options))
441
384
  header "Content-Type", "application/x-www-form-urlencoded"
@@ -33,6 +33,13 @@ describe Committee::Middleware::ResponseValidation do
33
33
  assert_equal 200, last_response.status
34
34
  end
35
35
 
36
+ it "passes through a valid response with a +json content-type" do
37
+ @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), { "Content-Type" => "application/problem+json; charset=utf-8" }, schema: open_api_3_schema, parse_response_by_content_type: true,)
38
+
39
+ get "/characters"
40
+ assert_equal 200, last_response.status
41
+ end
42
+
36
43
  it "passes through a invalid json" do
37
44
  @app = new_response_rack("not_json", {}, schema: open_api_3_schema)
38
45
 
@@ -187,12 +194,7 @@ describe Committee::Middleware::ResponseValidation do
187
194
 
188
195
  describe 'validate error option' do
189
196
  it "detects an invalid response status code" do
190
- @app = new_response_rack({ integer: '1' }.to_json,
191
- {},
192
- app_status: 400,
193
- schema: open_api_3_schema,
194
- raise: true,
195
- validate_success_only: false)
197
+ @app = new_response_rack({ integer: '1' }.to_json, {}, app_status: 400, schema: open_api_3_schema, raise: true, validate_success_only: false)
196
198
 
197
199
  e = assert_raises(Committee::InvalidResponse) do
198
200
  get "/characters"
@@ -202,12 +204,7 @@ describe Committee::Middleware::ResponseValidation do
202
204
  end
203
205
 
204
206
  it "detects an invalid response status code with validate_success_only=true" do
205
- @app = new_response_rack({ string_1: :honoka }.to_json,
206
- {},
207
- app_status: 400,
208
- schema: open_api_3_schema,
209
- raise: true,
210
- validate_success_only: true)
207
+ @app = new_response_rack({ string_1: :honoka }.to_json, {}, app_status: 400, schema: open_api_3_schema, raise: true, validate_success_only: true)
211
208
 
212
209
  get "/characters"
213
210
 
@@ -235,6 +232,36 @@ describe Committee::Middleware::ResponseValidation do
235
232
  end
236
233
  end
237
234
 
235
+ describe 'response type validation' do
236
+ it "detects string value for number field when coerce_response_values is false" do
237
+ @app = new_response_rack({ integer: '726' }.to_json, {}, app_status: 400, schema: open_api_3_schema, raise: true, validate_success_only: false, coerce_response_values: false)
238
+
239
+ e = assert_raises(Committee::InvalidResponse) do
240
+ get "/characters"
241
+ end
242
+
243
+ assert_match(/expected integer, but received String/i, e.message)
244
+ end
245
+
246
+ it "passes string value for number field when coerce_response_values is true" do
247
+ @app = new_response_rack({ integer: '726' }.to_json, {}, app_status: 400, schema: open_api_3_schema, raise: true, validate_success_only: false, coerce_response_values: true)
248
+
249
+ get "/characters"
250
+
251
+ assert_equal 400, last_response.status
252
+ end
253
+
254
+ it "detects string value for number field by default (coerce_response_values defaults to false)" do
255
+ @app = new_response_rack({ integer: '726' }.to_json, {}, app_status: 400, schema: open_api_3_schema, raise: true, validate_success_only: false)
256
+
257
+ e = assert_raises(Committee::InvalidResponse) do
258
+ get "/characters"
259
+ end
260
+
261
+ assert_match(/expected integer, but received String/i, e.message)
262
+ end
263
+ end
264
+
238
265
  it 'does not suppress application error' do
239
266
  @app = Rack::Builder.new {
240
267
  use Committee::Middleware::ResponseValidation, { schema: open_api_3_schema, raise: true }
@@ -269,11 +296,7 @@ describe Committee::Middleware::ResponseValidation do
269
296
  end
270
297
 
271
298
  it "strict and invalid content type with raise" do
272
- @app = new_response_rack("abc",
273
- {},
274
- { schema: open_api_3_schema, strict: true, raise: true },
275
- { content_type: 'application/text' }
276
- )
299
+ @app = new_response_rack("abc", {}, { schema: open_api_3_schema, strict: true, raise: true }, { content_type: 'application/text' })
277
300
 
278
301
  assert_raises(Committee::InvalidResponse) do
279
302
  get "/characters"
@@ -199,10 +199,7 @@ describe Committee::Middleware::ResponseValidation do
199
199
  describe 'streaming response' do
200
200
  describe "text/event-stream; e.g. server-sent events" do
201
201
  it 'validates the response stream as a string' do
202
- options = {
203
- schema: open_api_3_streaming_response_schema,
204
- streaming_content_parsers: { 'text/event-stream' => ->(body) { body } },
205
- }
202
+ options = { schema: open_api_3_streaming_response_schema, streaming_content_parsers: { 'text/event-stream' => ->(body) { body } }, }
206
203
  status = 200
207
204
  headers = { 'content-type' => 'text/event-stream' }
208
205
  @app = Rack::Builder.new {
@@ -221,11 +218,7 @@ describe Committee::Middleware::ResponseValidation do
221
218
  it "successfully validates the response as a special stream using a customized parser" do
222
219
  error_handler_called = false
223
220
  error_handler = ->(_e, _env) { error_handler_called = true }
224
- options = {
225
- schema: open_api_3_streaming_response_schema,
226
- streaming_content_parsers: { 'application/x-json-stream' => ->(body) { JSON.parse!(body) } },
227
- error_handler: error_handler,
228
- }
221
+ options = { schema: open_api_3_streaming_response_schema, streaming_content_parsers: { 'application/x-json-stream' => ->(body) { JSON.parse!(body) } }, error_handler: error_handler, }
229
222
  status = 200
230
223
  headers = { 'content-type' => 'application/x-json-stream' }
231
224
  @app = Rack::Builder.new {
@@ -243,11 +236,7 @@ describe Committee::Middleware::ResponseValidation do
243
236
  it "fails to validate the response as a special stream using a customized parser due to a schema mismatch" do
244
237
  error_handler_called = false
245
238
  error_handler = ->(_e, _env) { error_handler_called = true }
246
- options = {
247
- schema: open_api_3_streaming_response_schema,
248
- streaming_content_parsers: { 'application/x-json-stream' => ->(body) { JSON.parse!(body) } },
249
- error_handler: error_handler,
250
- }
239
+ options = { schema: open_api_3_streaming_response_schema, streaming_content_parsers: { 'application/x-json-stream' => ->(body) { JSON.parse!(body) } }, error_handler: error_handler, }
251
240
  status = 200
252
241
  headers = { 'content-type' => 'application/x-json-stream' }
253
242
  @app = Rack::Builder.new {
@@ -137,22 +137,14 @@ describe Committee::RequestUnpacker do
137
137
  end
138
138
 
139
139
  it "includes request body when`use_get_body` is true" do
140
- env = {
141
- "rack.input" => StringIO.new('{"x":1, "y":2}'),
142
- "REQUEST_METHOD" => "GET",
143
- "QUERY_STRING" => "data=value&x=aaa",
144
- }
140
+ env = { "rack.input" => StringIO.new('{"x":1, "y":2}'), "REQUEST_METHOD" => "GET", "QUERY_STRING" => "data=value&x=aaa", }
145
141
  request = Rack::Request.new(env)
146
142
  unpacker = Committee::RequestUnpacker.new({ allow_query_params: true, allow_get_body: true })
147
143
  assert_equal([{ 'x' => 1, 'y' => 2 }, false], unpacker.unpack_request_params(request))
148
144
  end
149
145
 
150
146
  it "doesn't include request body when `use_get_body` is false" do
151
- env = {
152
- "rack.input" => StringIO.new('{"x":1, "y":2}'),
153
- "REQUEST_METHOD" => "GET",
154
- "QUERY_STRING" => "data=value&x=aaa",
155
- }
147
+ env = { "rack.input" => StringIO.new('{"x":1, "y":2}'), "REQUEST_METHOD" => "GET", "QUERY_STRING" => "data=value&x=aaa", }
156
148
  request = Rack::Request.new(env)
157
149
  unpacker = Committee::RequestUnpacker.new({ allow_query_params: true, use_get_body: false })
158
150
  assert_equal({ 'data' => 'value', 'x' => 'aaa' }, unpacker.unpack_query_params(request))
@@ -35,43 +35,7 @@ describe Committee::SchemaValidator::HyperSchema::ParameterCoercer do
35
35
  end
36
36
 
37
37
  it "pass array property" do
38
- params = {
39
- "array_property" => [
40
- {
41
- "update_time" => "2016-04-01T16:00:00.000+09:00",
42
- "per_page" => 1,
43
- "nested_coercer_object" => {
44
- "update_time" => "2016-04-01T16:00:00.000+09:00",
45
- "threshold" => 1.5
46
- },
47
- "nested_no_coercer_object" => {
48
- "per_page" => 1,
49
- "threshold" => 1.5
50
- },
51
- "nested_coercer_array" => [
52
- {
53
- "update_time" => "2016-04-01T16:00:00.000+09:00",
54
- "threshold" => 1.5
55
- }
56
- ],
57
- "nested_no_coercer_array" => [
58
- {
59
- "per_page" => 1,
60
- "threshold" => 1.5
61
- }
62
- ]
63
- },
64
- {
65
- "update_time" => "2016-04-01T16:00:00.000+09:00",
66
- "per_page" => 1,
67
- "threshold" => 1.5
68
- },
69
- {
70
- "threshold" => 1.5,
71
- "per_page" => 1
72
- }
73
- ],
74
- }
38
+ params = { "array_property" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "per_page" => 1, "nested_coercer_object" => { "update_time" => "2016-04-01T16:00:00.000+09:00", "threshold" => 1.5 }, "nested_no_coercer_object" => { "per_page" => 1, "threshold" => 1.5 }, "nested_coercer_array" => [{ "update_time" => "2016-04-01T16:00:00.000+09:00", "threshold" => 1.5 }], "nested_no_coercer_array" => [{ "per_page" => 1, "threshold" => 1.5 }] }, { "update_time" => "2016-04-01T16:00:00.000+09:00", "per_page" => 1, "threshold" => 1.5 }, { "threshold" => 1.5, "per_page" => 1 }], }
75
39
  call(params, coerce_date_times: true, coerce_recursive: true)
76
40
 
77
41
  first_data = params["array_property"][0]