From a5b5fef0757cc0c7f931b785322c3c77e1c79f11 Mon Sep 17 00:00:00 2001 From: ota42y Date: Sat, 15 May 2021 18:38:11 +0900 Subject: [PATCH] strict response validation --- README.md | 1 + .../middleware/response_validation.rb | 4 +- lib/committee/schema_validator/open_api_3.rb | 1 + .../response_validation_open_api_3_test.rb | 39 ++++++++++++++++++- .../open_api_3/response_validator_test.rb | 9 +++++ 5 files changed, 52 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a2c91b81..8ccbadeb 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ Option values and defaults: |validate_success_only| true | false | Also validate non-2xx responses only. | |ignore_error| false | false | Validate and ignore result even if validation is error. So always return original data. | |parse_response_by_content_type| false | false | Parse response body to JSON only if Content-Type header is 'application/json'. When false, this always optimistically parses as JSON without checking for Content-Type header. | +|strict| false | false | Puts the middleware into strict mode, meaning that response code and content type does not defined in the schema will be responded to with a 500 instead of application's status code. | No boolean option values: diff --git a/lib/committee/middleware/response_validation.rb b/lib/committee/middleware/response_validation.rb index cf4814a3..bb1f5853 100644 --- a/lib/committee/middleware/response_validation.rb +++ b/lib/committee/middleware/response_validation.rb @@ -7,6 +7,8 @@ class ResponseValidation < Base def initialize(app, options = {}) super + # TODO: show error message for old version + @strict = options[:strict] @validate_success_only = @schema.validator_option.validate_success_only end @@ -15,7 +17,7 @@ def handle(request) begin v = build_schema_validator(request) - v.response_validate(status, headers, response) if v.link_exist? && self.class.validate?(status, validate_success_only) + v.response_validate(status, headers, response, @strict) if v.link_exist? && self.class.validate?(status, validate_success_only) rescue Committee::InvalidResponse handle_exception($!, request.env) diff --git a/lib/committee/schema_validator/open_api_3.rb b/lib/committee/schema_validator/open_api_3.rb index fd73f6a2..755610c3 100644 --- a/lib/committee/schema_validator/open_api_3.rb +++ b/lib/committee/schema_validator/open_api_3.rb @@ -40,6 +40,7 @@ def response_validate(status, headers, response, test_method = false) full_body end + # TODO: refactoring name strict = test_method Committee::SchemaValidator::OpenAPI3::ResponseValidator. new(@operation_object, validator_option). diff --git a/test/middleware/response_validation_open_api_3_test.rb b/test/middleware/response_validation_open_api_3_test.rb index 896ce1a7..fdbbac2a 100644 --- a/test/middleware/response_validation_open_api_3_test.rb +++ b/test/middleware/response_validation_open_api_3_test.rb @@ -228,6 +228,42 @@ def app end end + it "strict and invalid status" do + @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, {schema: open_api_3_schema, strict: true}, {status: 201}) + get "/characters" + assert_equal 500, last_response.status + end + + it "strict and invalid status with raise" do + @app = new_response_rack(JSON.generate(CHARACTERS_RESPONSE), {}, {schema: open_api_3_schema, strict: true, raise: true}, {status: 201}) + + assert_raises(Committee::InvalidResponse) do + get "/characters" + end + end + + it "strict and invalid content type" do + @app = new_response_rack("abc", + {}, + {schema: open_api_3_schema, strict: true}, + {content_type: 'application/text'} + ) + get "/characters" + assert_equal 500, last_response.status + end + + it "strict and invalid content type with raise" do + @app = new_response_rack("abc", + {}, + {schema: open_api_3_schema, strict: true, raise: true}, + {content_type: 'application/text'} + ) + + assert_raises(Committee::InvalidResponse) do + get "/characters" + end + end + private def new_response_rack(response, headers = {}, options = {}, rack_options = {}) @@ -235,8 +271,9 @@ def new_response_rack(response, headers = {}, options = {}, rack_options = {}) options[:parse_response_by_content_type] = true if options[:parse_response_by_content_type] == nil status = rack_options[:status] || 200 + content_type = rack_options[:content_type] || "application/json" headers = { - "Content-Type" => "application/json" + "Content-Type" => content_type }.merge(headers) Rack::Builder.new { use Committee::Middleware::ResponseValidation, options diff --git a/test/schema_validator/open_api_3/response_validator_test.rb b/test/schema_validator/open_api_3/response_validator_test.rb index 16591138..6d0d9a32 100644 --- a/test/schema_validator/open_api_3/response_validator_test.rb +++ b/test/schema_validator/open_api_3/response_validator_test.rb @@ -42,6 +42,15 @@ assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error) end + it "raises InvalidResponse when a invalid status code with strict option" do + @status = 201 + e = assert_raises(Committee::InvalidResponse) { + call_response_validator(true) + } + + assert_kind_of(OpenAPIParser::OpenAPIError, e.original_error) + end + it "passes through a valid response with no Content-Type" do @headers = {} call_response_validator