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
@@ -1,14 +1,15 @@
1
- require_relative "test_helper"
1
+ # frozen_string_literal: true
2
2
 
3
- describe Committee::ResponseValidator do
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::HyperSchema::ResponseValidator do
4
6
  before do
5
7
  @status = 200
6
8
  @headers = {
7
9
  "Content-Type" => "application/json"
8
10
  }
9
11
  @data = ValidApp.dup
10
- @schema =
11
- JsonSchema.parse!(JSON.parse(File.read("./test/data/schema.json")))
12
+ @schema = JsonSchema.parse!(hyper_schema_data)
12
13
  @schema.expand_references!
13
14
  # GET /apps/:id
14
15
  @get_link = @link = @schema.properties["app"].links[2]
@@ -37,9 +38,38 @@ describe Committee::ResponseValidator do
37
38
  call
38
39
  end
39
40
 
40
- it "detects an improperly formatted list response" do
41
+ it "passes through a 304 Not Modified response" do
42
+ @status, @headers, @data = 304, {}, nil
43
+ call
44
+ end
45
+
46
+ it "passes through a valid list response for for rel instances links" do
47
+ @link = @list_link
48
+
49
+ # forces the link to use `parent`
50
+ @link.target_schema = nil
51
+
52
+ # We're testing for legacy behavior here: even without a `targetSchema` as
53
+ # long as `rel` is set to `instances` we still wrap the result in an
54
+ # array.
55
+ assert_equal "instances", @link.rel
56
+
57
+ @data = [@data]
58
+ @link = @list_link
59
+ call
60
+ end
61
+
62
+ it "detects an improperly formatted list response for rel instances link" do
41
63
  @link = @list_link
64
+
65
+ # forces the link to use `parent`
42
66
  @link.target_schema = nil
67
+
68
+ # We're testing for legacy behavior here: even without a `targetSchema` as
69
+ # long as `rel` is set to `instances` we still wrap the result in an
70
+ # array.
71
+ assert_equal "instances", @link.rel
72
+
43
73
  e = assert_raises(Committee::InvalidResponse) { call }
44
74
  message = "List endpoints must return an array of objects."
45
75
  assert_equal message, e.message
@@ -66,6 +96,11 @@ describe Committee::ResponseValidator do
66
96
  call
67
97
  end
68
98
 
99
+ it "allows no Content-Type for 304 Not Modified" do
100
+ @status, @headers = 304, {}
101
+ call
102
+ end
103
+
69
104
  it "raises errors generated by json_schema" do
70
105
  @data.merge!("name" => "%@!")
71
106
  e = assert_raises(Committee::InvalidResponse) { call }
@@ -76,6 +111,8 @@ describe Committee::ResponseValidator do
76
111
  private
77
112
 
78
113
  def call
79
- Committee::ResponseValidator.new(@link).call(@status, @headers, @data)
114
+ # hyper-schema link should be dropped into driver wrapper before it's used
115
+ link = Committee::Drivers::HyperSchema::Link.new(@link)
116
+ Committee::SchemaValidator::HyperSchema::ResponseValidator.new(link).call(@status, @headers, @data)
80
117
  end
81
118
  end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::HyperSchema::Router do
6
+ it "builds routes without parameters" do
7
+ link, _ = hyper_schema_router.find_link("GET", "/apps")
8
+ refute_nil link
9
+ end
10
+
11
+ it "builds routes with parameters" do
12
+ link, _ = hyper_schema_router.find_link("GET", "/apps/123")
13
+ refute_nil link
14
+ end
15
+
16
+ it "doesn't match anything on a /" do
17
+ link, _ = hyper_schema_router.find_link("GET", "/")
18
+ assert_nil link
19
+ end
20
+
21
+ it "takes a prefix" do
22
+ # this is a sociopathic example
23
+ link, _ = hyper_schema_router(prefix: "/kpi").find_link("GET", "/kpi/apps/123")
24
+ refute_nil link
25
+ end
26
+
27
+ it "includes all paths without a prefix" do
28
+ link, _ = hyper_schema_router.includes?("/")
29
+ refute_nil link
30
+
31
+ link, _ = hyper_schema_router.includes?("/apps")
32
+ refute_nil link
33
+ end
34
+
35
+ it "only includes the prefix path with a prefix" do
36
+ link, _ = hyper_schema_router(prefix: "/kpi").includes?("/")
37
+ assert_nil link
38
+
39
+ link, _ = hyper_schema_router(prefix: "/kpi").includes?("/kpi")
40
+ refute_nil link
41
+
42
+ link, _ = hyper_schema_router(prefix: "/kpi").includes?("/kpi/apps")
43
+ refute_nil link
44
+ end
45
+
46
+ it "provides named parameters" do
47
+ link, param_matches = open_api_2_router.find_link("GET", "/api/pets/fido")
48
+ refute_nil link
49
+ assert_equal '/api/pets/{id}', link.href
50
+ assert_equal({ "id" => "fido" }, param_matches)
51
+ end
52
+
53
+ it "doesn't provide named parameters where none are available" do
54
+ link, param_matches = open_api_2_router.find_link("GET", "/api/pets")
55
+ refute_nil link
56
+ assert_equal({}, param_matches)
57
+ end
58
+
59
+ it "finds a path which has fewer matches" do
60
+ link, _ = open_api_2_router.find_link("GET", "/api/pets/dog")
61
+ refute_nil link
62
+ assert_equal '/api/pets/dog', link.href
63
+ end
64
+
65
+ it "fewer match not support in HyperSchema" do
66
+ link, _ = hyper_schema_router.find_link("GET", "/apps/abc")
67
+ refute_nil link
68
+ assert_equal '/apps/{(%23%2Fdefinitions%2Fapp%2Fdefinitions%2Fname)}', link.href
69
+ end
70
+
71
+ def hyper_schema_router(options = {})
72
+ # TODO: delete when 5.0.0 released because default value changed
73
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
74
+ schema = Committee::Drivers::HyperSchema::Driver.new.parse(hyper_schema_data)
75
+ validator_option = Committee::SchemaValidator::Option.new(options, schema, :hyper_schema)
76
+
77
+ Committee::SchemaValidator::HyperSchema::Router.new(schema, validator_option)
78
+ end
79
+
80
+ def open_api_2_router(options = {})
81
+ # TODO: delete when 5.0.0 released because default value changed
82
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
83
+ schema = Committee::Drivers::OpenAPI2::Driver.new.parse(open_api_2_data)
84
+ validator_option = Committee::SchemaValidator::Option.new(options, schema, :hyper_schema)
85
+
86
+ Committee::SchemaValidator::HyperSchema::Router.new(schema, validator_option)
87
+ end
88
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::HyperSchema::StringParamsCoercer do
6
+ before do
7
+ @schema = JsonSchema.parse!(hyper_schema_data)
8
+ @schema.expand_references!
9
+ # GET /search/apps
10
+ @link = @schema.properties["app"].links[5]
11
+ end
12
+
13
+ it "doesn't coerce params not in the schema" do
14
+ check_convert("owner", "admin", "admin")
15
+ end
16
+
17
+ it "skips values for string param" do
18
+ check_convert("name", "foo", "foo")
19
+ end
20
+
21
+ it "coerces valid values for boolean param" do
22
+ check_convert("deleted", "true", true)
23
+ check_convert("deleted", "false", false)
24
+ check_convert("deleted", "1", true)
25
+ check_convert("deleted", "0", false)
26
+ end
27
+
28
+ it "skips invalid values for boolean param" do
29
+ check_convert("deleted", "foo", "foo")
30
+ end
31
+
32
+ it "coerces valid values for integer param" do
33
+ check_convert("per_page", "3", 3)
34
+ end
35
+
36
+ it "skips invalid values for integer param" do
37
+ check_convert("per_page", "3.5", "3.5")
38
+ check_convert("per_page", "false", "false")
39
+ check_convert("per_page", "", "")
40
+ end
41
+
42
+ it "coerces valid values for number param" do
43
+ check_convert("threshold", "3", 3.0)
44
+ check_convert("threshold", "3.5", 3.5)
45
+ end
46
+
47
+ it "skips invalid values for number param" do
48
+ check_convert("threshold", "false", "false")
49
+ end
50
+
51
+ it "coerces valid values for null param" do
52
+ check_convert("threshold", "", nil)
53
+ end
54
+
55
+ it "pass array property" do
56
+ params = {
57
+ "array_property" => [
58
+ {
59
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
60
+ "per_page" => "1",
61
+ "nested_coercer_object" => {
62
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
63
+ "threshold" => "1.5"
64
+ },
65
+ "nested_no_coercer_object" => {
66
+ "per_page" => "1",
67
+ "threshold" => "1.5"
68
+ },
69
+ "nested_coercer_array" => [
70
+ {
71
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
72
+ "threshold" => "1.5"
73
+ }
74
+ ],
75
+ "nested_no_coercer_array" => [
76
+ {
77
+ "per_page" => "1",
78
+ "threshold" => "1.5"
79
+ }
80
+ ]
81
+ },
82
+ {
83
+ "update_time" => "2016-04-01T16:00:00.000+09:00",
84
+ "per_page" => "1",
85
+ "threshold" => "1.5"
86
+ },
87
+ {
88
+ "threshold" => "1.5",
89
+ "per_page" => "1"
90
+ }
91
+ ],
92
+ }
93
+ call(params, coerce_recursive: true)
94
+
95
+ first_data = params["array_property"][0]
96
+ assert_kind_of String, first_data["update_time"]
97
+ assert_kind_of Integer, first_data["per_page"]
98
+
99
+ second_data = params["array_property"][1]
100
+ assert_kind_of String, second_data["update_time"]
101
+ assert_kind_of Integer, second_data["per_page"]
102
+ assert_kind_of Float, second_data["threshold"]
103
+
104
+ third_data = params["array_property"][1]
105
+ assert_kind_of Integer, third_data["per_page"]
106
+ assert_kind_of Float, third_data["threshold"]
107
+
108
+ assert_kind_of String, first_data["nested_coercer_object"]["update_time"]
109
+ assert_kind_of Float, first_data["nested_coercer_object"]["threshold"]
110
+
111
+ assert_kind_of Integer, first_data["nested_no_coercer_object"]["per_page"]
112
+ assert_kind_of Float, first_data["nested_no_coercer_object"]["threshold"]
113
+
114
+ assert_kind_of String, first_data["nested_coercer_array"].first["update_time"]
115
+ assert_kind_of Float, first_data["nested_coercer_array"].first["threshold"]
116
+
117
+ assert_kind_of Integer, first_data["nested_no_coercer_array"].first["per_page"]
118
+ assert_kind_of Float, first_data["nested_no_coercer_array"].first["threshold"]
119
+ end
120
+
121
+ private
122
+
123
+ def check_convert(key, before_value, after_value)
124
+ data = {key => before_value}
125
+ call(data)
126
+
127
+ if !after_value.nil?
128
+ assert_equal(data[key], after_value)
129
+ else
130
+ assert_nil(data[key])
131
+ end
132
+ end
133
+
134
+ def call(data, options={})
135
+ Committee::SchemaValidator::HyperSchema::StringParamsCoercer.new(data, @link.schema, options).call!
136
+ end
137
+ end
@@ -0,0 +1,218 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ require "stringio"
6
+
7
+ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
8
+ describe 'validate' do
9
+ before do
10
+ @path = '/validate'
11
+ @method = 'post'
12
+
13
+ # TODO: delete when 5.0.0 released because default value changed
14
+ options = {}
15
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
16
+
17
+ @validator_option = Committee::SchemaValidator::Option.new(options, open_api_3_schema, :open_api_3)
18
+ end
19
+
20
+ def operation_object
21
+ open_api_3_schema.operation_object(@path, @method)
22
+ end
23
+
24
+ HEADER = { 'Content-Type' => 'application/json' }
25
+
26
+ SCHEMA_PROPERTIES_PAIR = [
27
+ ['string', 'str'],
28
+ ['integer', 1],
29
+ ['boolean', true],
30
+ ['boolean', false],
31
+ ['number', 0.1],
32
+ ]
33
+
34
+ it 'correct data' do
35
+ operation_object.validate_request_params({}, {}, SCHEMA_PROPERTIES_PAIR.to_h, HEADER, @validator_option)
36
+ assert true
37
+ end
38
+
39
+ it 'correct object data' do
40
+ operation_object.validate_request_params(
41
+ {},
42
+ {},
43
+ {
44
+ "object_1" =>
45
+ {
46
+ "string_1" => nil,
47
+ "integer_1" => nil,
48
+ "boolean_1" => nil,
49
+ "number_1" => nil
50
+ }
51
+ },
52
+ HEADER,
53
+ @validator_option)
54
+
55
+ assert true
56
+ end
57
+
58
+ it 'invalid params' do
59
+ e = assert_raises(Committee::InvalidRequest) {
60
+ operation_object.validate_request_params({}, {}, {"string" => 1}, HEADER, @validator_option)
61
+ }
62
+
63
+ assert_match(/expected string, but received Integer: 1/i, e.message)
64
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
65
+ end
66
+
67
+ it 'support put method' do
68
+ @method = "put"
69
+ operation_object.validate_request_params({}, {}, {"string" => "str"}, HEADER, @validator_option)
70
+
71
+ e = assert_raises(Committee::InvalidRequest) {
72
+ operation_object.validate_request_params({}, {}, {"string" => 1}, HEADER, @validator_option)
73
+ }
74
+
75
+ assert_match(/expected string, but received Integer: 1/i, e.message)
76
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
77
+ end
78
+
79
+ it 'support patch method' do
80
+ @method = "patch"
81
+ operation_object.validate_request_params({}, {}, {"integer" => 1}, HEADER, @validator_option)
82
+
83
+ e = assert_raises(Committee::InvalidRequest) {
84
+ operation_object.validate_request_params({}, {}, {"integer" => "str"}, HEADER, @validator_option)
85
+ }
86
+
87
+ assert_match(/expected integer, but received String: "str"/i, e.message)
88
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
89
+ end
90
+
91
+ it 'unknown param' do
92
+ operation_object.validate_request_params({}, {}, {"unknown" => 1}, HEADER, @validator_option)
93
+ end
94
+
95
+ describe 'support get method' do
96
+ before do
97
+ @method = "get"
98
+ end
99
+
100
+ it 'correct' do
101
+ operation_object.validate_request_params(
102
+ {},
103
+ {"query_string" => "query", "query_integer_list" => [1, 2]},
104
+ {},
105
+ HEADER,
106
+ @validator_option
107
+ )
108
+
109
+ operation_object.validate_request_params(
110
+ {},
111
+ {"query_string" => "query", "query_integer_list" => [1, 2], "optional_integer" => 1},
112
+ {},
113
+ HEADER,
114
+ @validator_option
115
+ )
116
+
117
+ assert true
118
+ end
119
+
120
+ it 'not exist required' do
121
+ e = assert_raises(Committee::InvalidRequest) {
122
+ operation_object.validate_request_params({}, {"query_integer_list" => [1, 2]}, {}, HEADER, @validator_option)
123
+ }
124
+
125
+ assert_match(/missing required parameters: query_string/i, e.message)
126
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
127
+ end
128
+
129
+ it 'invalid type' do
130
+ e = assert_raises(Committee::InvalidRequest) {
131
+ operation_object.validate_request_params(
132
+ {},
133
+ {"query_string" => 1, "query_integer_list" => [1, 2], "optional_integer" => 1},
134
+ {},
135
+ HEADER,
136
+ @validator_option
137
+ )
138
+ }
139
+
140
+ assert_match(/expected string, but received Integer: 1/i, e.message)
141
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
142
+ end
143
+ end
144
+
145
+ describe 'support delete method' do
146
+ before do
147
+ @path = '/characters'
148
+ @method = "delete"
149
+ end
150
+
151
+ it 'correct' do
152
+ operation_object.validate_request_params({}, {"limit" => "1"}, {}, HEADER, @validator_option)
153
+
154
+ assert true
155
+ end
156
+
157
+ it 'invalid type' do
158
+ e = assert_raises(Committee::InvalidRequest) {
159
+ operation_object.validate_request_params({}, {"limit" => "a"}, {}, HEADER, @validator_option)
160
+ }
161
+
162
+ assert_match(/expected integer, but received String: "a"/i, e.message)
163
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
164
+ end
165
+ end
166
+
167
+ describe 'support head method' do
168
+ before do
169
+ @path = '/characters'
170
+ @method = 'head'
171
+ end
172
+
173
+ it 'correct' do
174
+ operation_object.validate_request_params({}, {"limit" => "1"}, {}, HEADER, @validator_option)
175
+
176
+ assert true
177
+ end
178
+
179
+ it 'invalid type' do
180
+ e = assert_raises(Committee::InvalidRequest) {
181
+ operation_object.validate_request_params({}, {"limit" => "a"}, {}, HEADER, @validator_option)
182
+ }
183
+
184
+ assert_match(/expected integer, but received String: "a"/i, e.message)
185
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
186
+ end
187
+ end
188
+
189
+ it 'support options method' do
190
+ @method = "options"
191
+ operation_object.validate_request_params({}, {}, {"integer" => 1}, HEADER, @validator_option)
192
+
193
+ e = assert_raises(Committee::InvalidRequest) {
194
+ operation_object.validate_request_params({}, {}, {"integer" => "str"}, HEADER, @validator_option)
195
+ }
196
+
197
+ assert_match(/expected integer, but received String: "str"/i, e.message)
198
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
199
+ end
200
+
201
+
202
+ describe '#content_types' do
203
+ it 'returns supported content types' do
204
+ @path = '/validate_content_types'
205
+ @method = 'post'
206
+
207
+ assert_equal ["application/json", "application/binary"], operation_object.request_content_types
208
+ end
209
+
210
+ it 'returns an empty array when the content of requestBody does not exist' do
211
+ @path = '/characters'
212
+ @method = 'get'
213
+
214
+ assert_equal [], operation_object.request_content_types
215
+ end
216
+ end
217
+ end
218
+ end
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+
5
+ describe Committee::SchemaValidator::OpenAPI3::RequestValidator do
6
+ describe 'OpenAPI3' do
7
+ include Rack::Test::Methods
8
+
9
+ def app
10
+ @app
11
+ end
12
+
13
+ it "skip validation when link does not exist" do
14
+ @app = new_rack_app(schema: open_api_3_schema)
15
+ params = {}
16
+ header "Content-Type", "application/json"
17
+ post "/unknown", JSON.generate(params)
18
+ assert_equal 200, last_response.status
19
+ end
20
+
21
+ it "optionally content_type check" do
22
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
23
+ params = {
24
+ "string_post_1" => "cloudnasium"
25
+ }
26
+ header "Content-Type", "text/html"
27
+ post "/characters", JSON.generate(params)
28
+ assert_equal 400, last_response.status
29
+
30
+ body = JSON.parse(last_response.body)
31
+ message =
32
+ %{"Content-Type" request header must be set to "application/json".}
33
+
34
+ assert_equal "bad_request", body['id']
35
+ assert_equal message, body['message']
36
+ end
37
+
38
+ it "validates content_type" do
39
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
40
+ params = {
41
+ "string_post_1" => "cloudnasium"
42
+ }
43
+ header "Content-Type", "text/html"
44
+ post "/validate_content_types", JSON.generate(params)
45
+ assert_equal 400, last_response.status
46
+
47
+ body = JSON.parse(last_response.body)
48
+ message =
49
+ %{"Content-Type" request header must be set to any of the following: ["application/json", "application/binary"].}
50
+
51
+ assert_equal message, body['message']
52
+ end
53
+
54
+ it "optionally skip content_type check" do
55
+ @app = new_rack_app(check_content_type: false, schema: open_api_3_schema)
56
+ params = {
57
+ "string_post_1" => "cloudnasium"
58
+ }
59
+ header "Content-Type", "text/html"
60
+ post "/characters", JSON.generate(params)
61
+ assert_equal 200, last_response.status
62
+ end
63
+
64
+ it "if not exist requestBody definition, skip content_type check" do
65
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
66
+ params = {
67
+ "string_post_1" => "cloudnasium"
68
+ }
69
+ header "Content-Type", "application/json"
70
+ patch "/validate_no_parameter", JSON.generate(params)
71
+ assert_equal 200, last_response.status
72
+ end
73
+
74
+ it "skips content_type check with an empty body" do
75
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
76
+ header "Content-Type", "application/x-www-form-urlencoded"
77
+ patch "/validate_empty_optional_body"
78
+ assert_equal 200, last_response.status
79
+ end
80
+
81
+ it "does not mix up parameters and requestBody" do
82
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
83
+ params = {
84
+ "last_name" => "Skywalker"
85
+ }
86
+ header "Content-Type", "application/json"
87
+ post "/additional_properties?first_name=Luke", JSON.generate(params)
88
+ assert_equal 200, last_response.status
89
+ end
90
+
91
+ it "error because content_type check with body" do
92
+ @app = new_rack_app(check_content_type: true, schema: open_api_3_schema)
93
+ header "Content-Type", "application/x-www-form-urlencoded"
94
+ patch "/validate_empty_optional_body", "{}"
95
+ assert_equal 400, last_response.status
96
+ end
97
+
98
+ def new_rack_app(options = {})
99
+ # TODO: delete when 5.0.0 released because default value changed
100
+ options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil
101
+
102
+ Rack::Builder.new {
103
+ use Committee::Middleware::RequestValidation, options
104
+ run lambda { |_|
105
+ [200, {}, []]
106
+ }
107
+ }
108
+ end
109
+ end
110
+ end