Add except option for testing error responses#465
Add except option for testing error responses#465sadahiro-ono wants to merge 1 commit intointeragent:masterfrom
Conversation
|
It looks like there are some trial-and-error commits here. Could you squash them into a single commit? |
Add `except` option to `assert_request_schema_confirm` to support
testing error responses where required parameters are intentionally
omitted.
When testing error responses (e.g. 401 Unauthorized), required
parameters are often intentionally absent. Previously this caused
`assert_request_schema_confirm` to raise before the response could
be validated.
The `except` option temporarily injects dummy values for the specified
parameters during request validation and restores the original state
afterwards via an `ensure` clause.
assert_request_schema_confirm(except: { headers: ['authorization'] })
assert_response_schema_confirm(401)
Supported parameter types: headers, query, body (JSON,
application/x-www-form-urlencoded, multipart/form-data).
Dummy values are type/format/enum-aware for OpenAPI 3; OpenAPI 2 and
Hyper-Schema fall back to "dummy-{name}".
5842bc3 to
c3f4558
Compare
Thank you for the feedback! I've squashed all the trial-and-error commits into a |
| # Resolve the OpenAPI3 operation object for the current request. | ||
| # Returns nil for non-OpenAPI3 schemas or any lookup failure. | ||
| def resolve_operation | ||
| schema = @committee_options[:schema] |
There was a problem hiding this comment.
ask) Since resolve_operation only references @committee_options[:schema], if schema_path is specified in committee_options during testing, won't the schema retrieval always result in nil?
As a result, the dummy value falls back to “dummy-*”. In this state, even if the required typed parameter is excluded, won't the request validation fail because the type of the inserted fallback value is incorrect?
| operation = resolve_operation | ||
| return nil unless operation | ||
|
|
||
| params = operation.request_operation.operation_object&.parameters |
There was a problem hiding this comment.
ask) The parameter schema search only references operation_object.parameters and does not include parameters declared at the path item level.
In specifications that share query/header parameters in this way, except cannot find the schema and inserts the string fallback value. Therefore, wouldn't explicitly excepting a typed required parameter cause an InvalidRequest?
| request_body = operation.request_operation.operation_object&.request_body | ||
| return nil unless request_body | ||
|
|
||
| request_body.content&.each_value do |media_type| |
There was a problem hiding this comment.
ask) body_param_schema scans all schemas within requestBody.content and returns the first matching property name without considering the actual request's media type. If an endpoint supports multiple body Content-Types and fields with the same name but different types exist, wouldn't except insert dummy values from the incorrect schema, potentially causing validation to fail for the actual Content-Type?
Summary
Add
exceptoption toassert_request_schema_confirmto support testing error responses where required parameters are intentionally omitted.Background
When writing tests for error responses (e.g. 401 Unauthorized, 400 Bad Request), it is common to intentionally omit required parameters to trigger the error. However,
assert_request_schema_confirmwould fail on the missing parameters before the response could be validated — making it impossible to verify both the request schema and the error response in the same test.Usage
Changes
New file:
lib/committee/test/except_parameter.rbIntroduces
ExceptParameterand its handler classes that temporarily inject dummy values for excepted parameters and restore the original state after validation.Handler classes:
HeaderHandlerrequest.envdirectlyQueryHandlerrequest.GET(rack.request.query_hash)BodyHandlerrack.input(JSON) orrack.request.form_hash(form)Key design decisions:
StringDummyLookupmodule — shared by all handlers, providesresolve_operationfor OpenAPI3 schema lookup (prefix-stripping + operation resolution) and type/format/enum-aware dummy value generationBodyHandlerdispatches onContent-Type: treatsapplication/jsonand+jsonvariants (e.g.application/vnd.api+json) as JSON (replacesrack.input); callsrequest.POSTto force Rack to parse the form body first, then injects into the liverack.request.form_hashforapplication/x-www-form-urlencoded/multipart/form-data; no-op for other content types (e.g. binary). This logic mirrorsrequest_unpacker.rbexactly.HeaderHandlerhandles the CGI special cases whereContent-Type→CONTENT_TYPEandContent-Length→CONTENT_LENGTH(noHTTP_prefix)nil). Parameters that already carry a value are left untouched.type—integer/number/boolean/arrayget a zero value (native types for JSON bodies, string-encoded for query/header/form);objectgets{}for JSON bodies only and falls back to"dummy-{name}"for other parameter types;stringwith a recognized format (date-time,date,email,uuid) gets a format-aware string; everything else falls back to"dummy-{name}". Format-aware strings and type-based zero values are parallel branches of the samecasestatement, not sequential priorities.0,0.0,false,[];object→{}); for query/header/form, string-encoded values are used ("0","true") sincecoerce_valuehandles the conversion (objecttype is not string-encodable and falls back to"dummy-{name}")rescue StandardErrorin schema lookup methods (find_parameter_schema,body_param_schema) prevents unexpected failures in openapi_parser internals from breaking test assertions; falls back to"dummy-{name}"apply_jsonparses the body before committing any side-effects (@original_body, form cache deletion) so that aJSON::ParserErrordoes not trigger a spuriousrestore_jsoncallapply_formuses||=in its rescue clause to preserve any per-parameter originals already saved before the error, ensuringrestore_formcan undo partial injections"dummy-{name}"Modified:
lib/committee/test/methods.rbexcept: {}keyword argument toassert_request_schema_confirmwith_except_paramshelper; bothapplyandyieldare inside thebeginblock under anensureclause so that any partial dummy-value injection is always rolled back — even whenapplyitself raises (e.g.JSON::ParserErrorfrom an invalid request body)Modified:
test/data/openapi3/normal.yamlAdded test endpoints:
/get_endpoint_with_required_parameter— required string query param/test_except_validation— two required query params (to verify partial except)/get_endpoint_with_required_integer_query— required integer query param/test_except_body_params— required string + integer JSON body params/test_except_body_with_constraints— enum and date-time format constraints/test_except_form_params— required form-encoded body params/test_except_content_type_header— requiredContent-Typeheader/test_except_vnd_json_body—application/vnd.api+jsonbody (verify+jsonvariant handled as JSON)Modified:
test/test/methods_test.rbAdded 22 test cases covering:
Content-Type→CONTENT_TYPEmappingnil)application/vnd.api+jsonbody treated as JSONassert_request_schema_confirmwithoutexceptunchangedapplyraises mid-way (e.g. invalid JSON body)Modified:
README.mdDocumented the
exceptoption with usage examples, parameter type table (including supported content types forbody), and dummy value priority rules.