committee 4.1.0 → 4.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -435,6 +435,19 @@ describe Committee::Middleware::RequestValidation do
435
435
  assert_equal 200, last_response.status
436
436
  end
437
437
 
438
+ it "corce form params" do
439
+ check_parameter = lambda { |env|
440
+ assert_equal 3, env['committee.params']['age']
441
+ assert_equal 3, env['committee.request_body_hash']['age']
442
+ [200, {}, []]
443
+ }
444
+
445
+ @app = new_rack_app_with_lambda(check_parameter, schema: open_api_2_form_schema, raise: true, allow_form_params: true, coerce_form_params: true)
446
+ header "Content-Type", "application/x-www-form-urlencoded"
447
+ post "/api/pets", "age=3&name=ab"
448
+ assert_equal 200, last_response.status
449
+ end
450
+
438
451
  it "detects an invalid request for OpenAPI" do
439
452
  @app = new_rack_app(schema: open_api_2_schema)
440
453
  get "/api/pets?limit=foo", nil, { "HTTP_AUTH_TOKEN" => "xxx" }
@@ -125,7 +125,7 @@ describe Committee::Middleware::ResponseValidation do
125
125
  [
126
126
  { check_header: true, description: 'valid value', header: { 'integer' => 1 }, expected: { status: 200 } },
127
127
  { check_header: true, description: 'missing value', header: { 'integer' => nil }, expected: { error: 'headers/integer/schema does not allow null values' } },
128
- { check_header: true, description: 'invalid value', header: { 'integer' => 'x' }, expected: { error: 'headers/integer/schema expected integer, but received String: x' } },
128
+ { check_header: true, description: 'invalid value', header: { 'integer' => 'x' }, expected: { error: 'headers/integer/schema expected integer, but received String: "x"' } },
129
129
 
130
130
  { check_header: false, description: 'valid value', header: { 'integer' => 1 }, expected: { status: 200 } },
131
131
  { check_header: false, description: 'missing value', header: { 'integer' => nil }, expected: { status: 200 } },
@@ -177,7 +177,7 @@ describe Committee::Middleware::ResponseValidation do
177
177
  get "/characters"
178
178
  end
179
179
 
180
- assert_match(/but received String: 1/i, e.message)
180
+ assert_match(/but received String: \"1\"/i, e.message)
181
181
  end
182
182
 
183
183
  it "detects an invalid response status code with validate_success_only=true" do
@@ -215,6 +215,19 @@ describe Committee::Middleware::ResponseValidation do
215
215
  end
216
216
  end
217
217
 
218
+ it 'does not suppress application error' do
219
+ @app = Rack::Builder.new {
220
+ use Committee::Middleware::ResponseValidation, {schema: open_api_3_schema, raise: true}
221
+ run lambda { |_|
222
+ JSON.load('-') # invalid json
223
+ }
224
+ }
225
+
226
+ assert_raises(JSON::ParserError) do
227
+ get "/error", nil
228
+ end
229
+ end
230
+
218
231
  private
219
232
 
220
233
  def new_response_rack(response, headers = {}, options = {}, rack_options = {})
@@ -15,6 +15,14 @@ describe Committee::Middleware::ResponseValidation do
15
15
  assert_equal 200, last_response.status
16
16
  end
17
17
 
18
+ # TODO: remove 5.0.0
19
+ it "passes through a valid response" do
20
+ # will show deprecated message
21
+ @app = new_rack_app(JSON.generate([ValidApp]), {}, schema: hyper_schema, strict: true)
22
+ get "/apps"
23
+ assert_equal 200, last_response.status
24
+ end
25
+
18
26
  it "doesn't call error_handler (has a arg) when response is valid" do
19
27
  called = false
20
28
  pr = ->(_e) { called = true }
@@ -9,8 +9,18 @@ describe Committee::RequestUnpacker do
9
9
  "rack.input" => StringIO.new('{"x":"y"}'),
10
10
  }
11
11
  request = Rack::Request.new(env)
12
- params, _ = Committee::RequestUnpacker.new(request).call
13
- assert_equal({ "x" => "y" }, params)
12
+ unpacker = Committee::RequestUnpacker.new
13
+ assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
14
+ end
15
+
16
+ it "unpacks JSON on Content-Type: application/vnd.api+json" do
17
+ env = {
18
+ "CONTENT_TYPE" => "application/vnd.api+json",
19
+ "rack.input" => StringIO.new('{"x":"y"}'),
20
+ }
21
+ request = Rack::Request.new(env)
22
+ unpacker = Committee::RequestUnpacker.new
23
+ assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
14
24
  end
15
25
 
16
26
  it "unpacks JSON on no Content-Type" do
@@ -18,8 +28,18 @@ describe Committee::RequestUnpacker do
18
28
  "rack.input" => StringIO.new('{"x":"y"}'),
19
29
  }
20
30
  request = Rack::Request.new(env)
21
- params, _ = Committee::RequestUnpacker.new(request).call
22
- assert_equal({ "x" => "y" }, params)
31
+ unpacker = Committee::RequestUnpacker.new
32
+ assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
33
+ end
34
+
35
+ it "doesn't unpack JSON on application/x-ndjson" do
36
+ env = {
37
+ "CONTENT_TYPE" => "application/x-ndjson",
38
+ "rack.input" => StringIO.new('{"x":"y"}\n{"a":"b"}'),
39
+ }
40
+ request = Rack::Request.new(env)
41
+ unpacker = Committee::RequestUnpacker.new
42
+ assert_equal([{}, false], unpacker.unpack_request_params(request))
23
43
  end
24
44
 
25
45
  it "doesn't unpack JSON under other Content-Types" do
@@ -29,8 +49,8 @@ describe Committee::RequestUnpacker do
29
49
  "rack.input" => StringIO.new('{"x":"y"}'),
30
50
  }
31
51
  request = Rack::Request.new(env)
32
- params, _ = Committee::RequestUnpacker.new(request).call
33
- assert_equal({}, params)
52
+ unpacker = Committee::RequestUnpacker.new
53
+ assert_equal([{}, false], unpacker.unpack_request_params(request))
34
54
  end
35
55
  end
36
56
 
@@ -41,8 +61,8 @@ describe Committee::RequestUnpacker do
41
61
  "rack.input" => StringIO.new('{"x":"y"}'),
42
62
  }
43
63
  request = Rack::Request.new(env)
44
- params, _ = Committee::RequestUnpacker.new(request, optimistic_json: true).call
45
- assert_equal({ "x" => "y" }, params)
64
+ unpacker = Committee::RequestUnpacker.new(optimistic_json: true)
65
+ assert_equal([{ "x" => "y" }, false], unpacker.unpack_request_params(request))
46
66
  end
47
67
  end
48
68
 
@@ -53,8 +73,8 @@ describe Committee::RequestUnpacker do
53
73
  "rack.input" => StringIO.new('x=y&foo=42'),
54
74
  }
55
75
  request = Rack::Request.new(env)
56
- params, _ = Committee::RequestUnpacker.new(request, optimistic_json: true).call
57
- assert_equal({}, params)
76
+ unpacker = Committee::RequestUnpacker.new(optimistic_json: true)
77
+ assert_equal([{}, false], unpacker.unpack_request_params(request))
58
78
  end
59
79
  end
60
80
 
@@ -64,8 +84,8 @@ describe Committee::RequestUnpacker do
64
84
  "rack.input" => StringIO.new(""),
65
85
  }
66
86
  request = Rack::Request.new(env)
67
- params, _ = Committee::RequestUnpacker.new(request).call
68
- assert_equal({}, params)
87
+ unpacker = Committee::RequestUnpacker.new
88
+ assert_equal([{}, false], unpacker.unpack_request_params(request))
69
89
  end
70
90
 
71
91
  it "doesn't unpack form params" do
@@ -75,8 +95,8 @@ describe Committee::RequestUnpacker do
75
95
  "rack.input" => StringIO.new("x=y"),
76
96
  }
77
97
  request = Rack::Request.new(env)
78
- params, _ = Committee::RequestUnpacker.new(request).call
79
- assert_equal({}, params)
98
+ unpacker = Committee::RequestUnpacker.new
99
+ assert_equal([{}, false], unpacker.unpack_request_params(request))
80
100
  end
81
101
  end
82
102
 
@@ -87,96 +107,8 @@ describe Committee::RequestUnpacker do
87
107
  "rack.input" => StringIO.new("x=y"),
88
108
  }
89
109
  request = Rack::Request.new(env)
90
- params, _ = Committee::RequestUnpacker.new(request, allow_form_params: true).call
91
- assert_equal({ "x" => "y" }, params)
92
- end
93
- end
94
-
95
- it "coerces form params with coerce_form_params and a schema" do
96
- %w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
97
- env = {
98
- "CONTENT_TYPE" => content_type,
99
- "rack.input" => StringIO.new("x=1"),
100
- }
101
- request = Rack::Request.new(env)
102
-
103
- options = {}
104
- # TODO: delete when 5.0.0 released because default value changed
105
- options[:parse_response_by_content_type] = false
106
- router = hyper_schema.build_router(options)
107
- validator = router.build_schema_validator(request)
108
-
109
- schema = JsonSchema::Schema.new
110
- schema.properties = { "x" => JsonSchema::Schema.new }
111
- schema.properties["x"].type = ["integer"]
112
-
113
- link_class = Struct.new(:schema)
114
- link_object = link_class.new(schema)
115
-
116
- validator.instance_variable_set(:@link, link_object)
117
-
118
- params, _ = Committee::RequestUnpacker.new(
119
- request,
120
- allow_form_params: true,
121
- coerce_form_params: true,
122
- schema_validator: validator,
123
- ).call
124
- assert_equal({ "x" => 1 }, params)
125
- end
126
- end
127
-
128
- it "coerces form params with coerce_form_params and an OpenAPI3 schema" do
129
- %w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
130
- env = {
131
- "CONTENT_TYPE" => content_type,
132
- "rack.input" => StringIO.new("limit=20"),
133
- "PATH_INFO" => "/characters",
134
- "SCRIPT_NAME" => "",
135
- "REQUEST_METHOD" => "GET",
136
- }
137
- request = Rack::Request.new(env)
138
-
139
- options = {}
140
- # TODO: delete when 5.0.0 released because default value changed
141
- options[:parse_response_by_content_type] = false
142
- router = open_api_3_schema.build_router(options)
143
- validator = router.build_schema_validator(request)
144
-
145
- params, _ = Committee::RequestUnpacker.new(
146
- request,
147
- allow_form_params: true,
148
- coerce_form_params: true,
149
- schema_validator: validator,
150
- ).call
151
- # openapi3 not support coerce in request unpacker
152
- assert_equal({ "limit" => '20' }, params)
153
- end
154
- end
155
-
156
- it "coerces error params with coerce_form_params and a OpenAPI3 schema" do
157
- %w[application/x-www-form-urlencoded multipart/form-data].each do |content_type|
158
- env = {
159
- "CONTENT_TYPE" => content_type,
160
- "rack.input" => StringIO.new("limit=twenty"),
161
- "PATH_INFO" => "/characters",
162
- "SCRIPT_NAME" => "",
163
- "REQUEST_METHOD" => "GET",
164
- }
165
- request = Rack::Request.new(env)
166
-
167
- options = {}
168
- # TODO: delete when 5.0.0 released because default value changed
169
- options[:parse_response_by_content_type] = false
170
- router = open_api_3_schema.build_router(options)
171
- validator = router.build_schema_validator(request)
172
-
173
- params, _ = Committee::RequestUnpacker.new(
174
- request,
175
- allow_form_params: true,
176
- coerce_form_params: true,
177
- schema_validator: validator,
178
- ).call
179
- assert_equal({ "limit" => "twenty" }, params)
110
+ unpacker = Committee::RequestUnpacker.new(allow_form_params: true)
111
+ assert_equal([{ "x" => "y" }, true], unpacker.unpack_request_params(request))
180
112
  end
181
113
  end
182
114
 
@@ -188,8 +120,8 @@ describe Committee::RequestUnpacker do
188
120
  "QUERY_STRING" => "a=b"
189
121
  }
190
122
  request = Rack::Request.new(env)
191
- params, _ = Committee::RequestUnpacker.new(request, allow_form_params: true, allow_query_params: true).call
192
- assert_equal({ "x" => "y", "a" => "b" }, params)
123
+ unpacker = Committee::RequestUnpacker.new(allow_form_params: true, allow_query_params: true)
124
+ assert_equal([ { "x" => "y"}, true], unpacker.unpack_request_params(request))
193
125
  end
194
126
  end
195
127
 
@@ -199,8 +131,8 @@ describe Committee::RequestUnpacker do
199
131
  "QUERY_STRING" => "a=b"
200
132
  }
201
133
  request = Rack::Request.new(env)
202
- params, _ = Committee::RequestUnpacker.new(request, allow_query_params: true).call
203
- assert_equal({ "a" => "b" }, params)
134
+ unpacker = Committee::RequestUnpacker.new(allow_query_params: true)
135
+ assert_equal({ "a" => "b" }, unpacker.unpack_query_params(request))
204
136
  end
205
137
 
206
138
  it "errors if JSON is not an object" do
@@ -210,7 +142,7 @@ describe Committee::RequestUnpacker do
210
142
  }
211
143
  request = Rack::Request.new(env)
212
144
  assert_raises(Committee::BadRequest) do
213
- Committee::RequestUnpacker.new(request).call
145
+ Committee::RequestUnpacker.new.unpack_request_params(request)
214
146
  end
215
147
  end
216
148
 
@@ -220,8 +152,8 @@ describe Committee::RequestUnpacker do
220
152
  "rack.input" => StringIO.new('{"x":"y"}'),
221
153
  }
222
154
  request = Rack::Request.new(env)
223
- params, _ = Committee::RequestUnpacker.new(request).call
224
- assert_equal({}, params)
155
+ unpacker = Committee::RequestUnpacker.new
156
+ assert_equal([{}, false], unpacker.unpack_request_params(request))
225
157
  end
226
158
 
227
159
  # this is mostly here for line coverage
@@ -230,8 +162,8 @@ describe Committee::RequestUnpacker do
230
162
  "rack.input" => StringIO.new('{"x":[]}'),
231
163
  }
232
164
  request = Rack::Request.new(env)
233
- params, _ = Committee::RequestUnpacker.new(request).call
234
- assert_equal({ "x" => [] }, params)
165
+ unpacker = Committee::RequestUnpacker.new
166
+ assert_equal([{ "x" => [] }, false], unpacker.unpack_request_params(request))
235
167
  end
236
168
 
237
169
  it "unpacks http header" do
@@ -240,8 +172,8 @@ describe Committee::RequestUnpacker do
240
172
  "rack.input" => StringIO.new(""),
241
173
  }
242
174
  request = Rack::Request.new(env)
243
- _, headers = Committee::RequestUnpacker.new(request, { allow_header_params: true }).call
244
- assert_equal({ "FOO-BAR" => "some header value" }, headers)
175
+ unpacker = Committee::RequestUnpacker.new({ allow_header_params: true })
176
+ assert_equal({ "FOO-BAR" => "some header value" }, unpacker.unpack_headers(request))
245
177
  end
246
178
 
247
179
  it "includes request body when`use_get_body` is true" do
@@ -251,8 +183,8 @@ describe Committee::RequestUnpacker do
251
183
  "QUERY_STRING"=>"data=value&x=aaa",
252
184
  }
253
185
  request = Rack::Request.new(env)
254
- params, _ = Committee::RequestUnpacker.new(request, { allow_query_params: true, allow_get_body: true }).call
255
- assert_equal({ 'data' => 'value', 'x' => 1, 'y' => 2 }, params)
186
+ unpacker = Committee::RequestUnpacker.new({ allow_query_params: true, allow_get_body: true })
187
+ assert_equal([{ 'x' => 1, 'y' => 2 }, false], unpacker.unpack_request_params(request))
256
188
  end
257
189
 
258
190
  it "doesn't include request body when `use_get_body` is false" do
@@ -262,7 +194,7 @@ describe Committee::RequestUnpacker do
262
194
  "QUERY_STRING"=>"data=value&x=aaa",
263
195
  }
264
196
  request = Rack::Request.new(env)
265
- params, _ = Committee::RequestUnpacker.new(request, { allow_query_params: true, use_get_body: false }).call
266
- assert_equal({ 'data' => 'value', 'x' => 'aaa' }, params)
197
+ unpacker = Committee::RequestUnpacker.new({ allow_query_params: true, use_get_body: false })
198
+ assert_equal({ 'data' => 'value', 'x' => 'aaa' }, unpacker.unpack_query_params(request))
267
199
  end
268
200
  end
@@ -58,6 +58,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
58
58
  }
59
59
 
60
60
  assert_match(/expected string, but received Integer: 1/i, e.message)
61
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
61
62
  end
62
63
 
63
64
  it 'support put method' do
@@ -69,6 +70,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
69
70
  }
70
71
 
71
72
  assert_match(/expected string, but received Integer: 1/i, e.message)
73
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
72
74
  end
73
75
 
74
76
  it 'support patch method' do
@@ -79,7 +81,8 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
79
81
  operation_object.validate_request_params({"integer" => "str"}, HEADER, @validator_option)
80
82
  }
81
83
 
82
- assert_match(/expected integer, but received String: str/i, e.message)
84
+ assert_match(/expected integer, but received String: "str"/i, e.message)
85
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
83
86
  end
84
87
 
85
88
  it 'unknown param' do
@@ -113,6 +116,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
113
116
  }
114
117
 
115
118
  assert_match(/missing required parameters: query_string/i, e.message)
119
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
116
120
  end
117
121
 
118
122
  it 'invalid type' do
@@ -125,6 +129,7 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
125
129
  }
126
130
 
127
131
  assert_match(/expected string, but received Integer: 1/i, e.message)
132
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
128
133
  end
129
134
  end
130
135
 
@@ -145,7 +150,8 @@ describe Committee::SchemaValidator::OpenAPI3::OperationWrapper do
145
150
  operation_object.validate_request_params({"limit" => "a"}, HEADER, @validator_option)
146
151
  }
147
152
 
148
- assert_match(/expected integer, but received String: a/i, e.message)
153
+ assert_match(/expected integer, but received String: "a"/i, e.message)
154
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
149
155
  end
150
156
  end
151
157
 
@@ -36,9 +36,10 @@ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
36
36
 
37
37
  it "raises InvalidResponse when a valid response with no registered body with strict option" do
38
38
  @headers = { "Content-Type" => "application/xml" }
39
- assert_raises(Committee::InvalidResponse) {
39
+ e = assert_raises(Committee::InvalidResponse) {
40
40
  call_response_validator(true)
41
41
  }
42
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
42
43
  end
43
44
 
44
45
  it "passes through a valid response with no Content-Type" do
@@ -48,9 +49,10 @@ describe Committee::SchemaValidator::OpenAPI3::ResponseValidator do
48
49
 
49
50
  it "raises InvalidResponse when a valid response with no Content-Type headers with strict option" do
50
51
  @headers = {}
51
- assert_raises(Committee::InvalidResponse) {
52
+ e = assert_raises(Committee::InvalidResponse) {
52
53
  call_response_validator(true)
53
54
  }
55
+ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error)
54
56
  end
55
57
 
56
58
  it "passes through a valid list response" do
@@ -39,7 +39,7 @@ describe Committee::Test::Methods do
39
39
  it "passes through a valid response" do
40
40
  @app = new_rack_app(JSON.generate([ValidApp]))
41
41
  get "/apps"
42
- assert_schema_conform
42
+ assert_schema_conform(200)
43
43
  end
44
44
 
45
45
  it "passes with prefix" do
@@ -47,18 +47,27 @@ describe Committee::Test::Methods do
47
47
 
48
48
  @app = new_rack_app(JSON.generate([ValidApp]))
49
49
  get "/v1/apps"
50
- assert_schema_conform
50
+ assert_schema_conform(200)
51
51
  end
52
52
 
53
53
  it "detects an invalid response Content-Type" do
54
54
  @app = new_rack_app(JSON.generate([ValidApp]), 200, {})
55
55
  get "/apps"
56
56
  e = assert_raises(Committee::InvalidResponse) do
57
- assert_schema_conform
57
+ assert_schema_conform(200)
58
58
  end
59
59
  assert_match(/response header must be set to/i, e.message)
60
60
  end
61
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
+
62
71
  it "detects an invalid response Content-Type but ignore because it's not success status code" do
63
72
  @committee_options.merge!(validate_success_only: true)
64
73
  @app = new_rack_app(JSON.generate([ValidApp]), 400, {})
@@ -70,7 +79,7 @@ describe Committee::Test::Methods do
70
79
  @app = new_rack_app(JSON.generate([ValidApp]), 400, {})
71
80
  get "/apps"
72
81
  e = assert_raises(Committee::InvalidResponse) do
73
- assert_schema_conform
82
+ assert_schema_conform(400)
74
83
  end
75
84
  assert_match(/response header must be set to/i, e.message)
76
85
  end