diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f595869..71664397 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Breaking: Trailing slashes are no longer ignored in dynamic paths. See [#403](https://github.com/ahx/openapi_first/issues/403). Before this change `GET /things/24/` matched `/things/{id}:`, but it no longer does. - Breaking: Failure type `:response_not_found` was split into two more specific types `:response_content_type_not_found` and `:response_status_not_found`. This should be mostly internal stuff. So if your custom error response used `response_not_found`, you will have to adapt. +- `OpenapiFirst::Test.app` now returns an instance of `OpenapiFirst::Test::App`, instead of `Rack::Builer` and delegates methods other than `#call` to the original app. This wrapper adds validated requests, responses to the rack env at `env[OpenapiFirst::Test::REQUEST]`, `env[OpenapiFirst::Test::RESPONSE]` ### Added - The Coverage feature in `OpenapiFirst::Test` now supports parallel tests via a DRB client/sever. Thanks to Richard! See [#394](https://github.com/ahx/openapi_first/issues/394). diff --git a/lib/openapi_first/test.rb b/lib/openapi_first/test.rb index 3d6aa83a..8167cc10 100644 --- a/lib/openapi_first/test.rb +++ b/lib/openapi_first/test.rb @@ -9,6 +9,7 @@ module Test autoload :Coverage, 'openapi_first/test/coverage' autoload :Methods, 'openapi_first/test/methods' autoload :Observe, 'openapi_first/test/observe' + autoload :App, 'openapi_first/test/app' extend Registry class CoverageError < Error; end @@ -89,11 +90,8 @@ def self.report_coverage(formatter: Coverage::TerminalFormatter, **) # the middlewares or manual request, response validation. def self.app(app, spec: nil, api: :default) spec ||= self[api] - Rack::Builder.app do - use OpenapiFirst::Middlewares::ResponseValidation, spec:, raise_error: false - use OpenapiFirst::Middlewares::RequestValidation, spec:, raise_error: false, error_response: false - run app - end + + App.new(app, api: spec) end def self.install diff --git a/lib/openapi_first/test/app.rb b/lib/openapi_first/test/app.rb new file mode 100644 index 00000000..c5b6b9a3 --- /dev/null +++ b/lib/openapi_first/test/app.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +require_relative 'observe' + +module OpenapiFirst + module Test + REQUEST = 'openapi.test.request' + RESPONSE = 'openapi.test.response' + + # A wrapper of the original app + # with silent request/response validation to track requests/responses. + class App < SimpleDelegator + def initialize(app, api:) + super(app) + @app = app + @definition = Test[api] + end + + def call(env) + request = Rack::Request.new(env) + env[Test::REQUEST] = @definition.validate_request(request, raise_error: false) + response = @app.call(env) + status, headers, body = response + env[Test::RESPONSE] = + @definition.validate_response(request, Rack::Response[status, headers, body], raise_error: false) + response + end + end + end +end diff --git a/spec/test/methods_spec.rb b/spec/test/methods_spec.rb index c5ef0aae..d070fd4e 100644 --- a/spec/test/methods_spec.rb +++ b/spec/test/methods_spec.rb @@ -59,7 +59,7 @@ def last_response = Rack::Response.new get('/') - expect(last_request.env[OpenapiFirst::REQUEST].operation_id).to eq('example#root') + expect(last_request.env[OpenapiFirst::Test::REQUEST].operation_id).to eq('example#root') end end end @@ -100,7 +100,7 @@ def last_response = Rack::Response.new test_app = minitest_class.new(1).app env = Rack::MockRequest.env_for('/') expect(test_app.call(env)).to eq(Rack::Response.new('hello').finish) - expect(env[OpenapiFirst::REQUEST]).to be_valid + expect(env[OpenapiFirst::Test::REQUEST]).to be_valid end it 'adds an app method that wraps the app for a specific API' do @@ -115,7 +115,7 @@ def last_response = Rack::Response.new test_app = minitest_class.new(1).app env = Rack::MockRequest.env_for('/') expect(test_app.call(env)).to eq(Rack::Response.new('hello').finish) - expect(env[OpenapiFirst::REQUEST]).to be_valid + expect(env[OpenapiFirst::Test::REQUEST]).to be_valid end it 'adds an assert_api_conform method that targets the specified API' do diff --git a/spec/test_spec.rb b/spec/test_spec.rb index 4312c549..06437ab5 100644 --- a/spec/test_spec.rb +++ b/spec/test_spec.rb @@ -302,11 +302,18 @@ def call(_env) let(:filename) { './spec/data/dice.yaml' } let(:oad) { OpenapiFirst.load(filename) } + let(:original_app) do + Class.new do + def call(_env) + [200, { 'content-type' => 'application/json' }, ['1']] + end + + def color = 'red' + end.new + end + let(:app) do - described_class.app( - ->(_env) { [200, { 'content-type' => 'application/json' }, ['1']] }, - spec: oad - ) + described_class.app(original_app, api: oad) end include Rack::Test::Methods @@ -326,6 +333,18 @@ def call(_env) expect(called).to eq(%i[request response]) end + it 'keeps instance methods of the original app intact' do + expect(app.color).to eq('red') + end + + it 'adds last validated request, reponse in the rack env' do + post '/roll' + + last_env = last_request.env + expect(last_env[described_class::REQUEST]).to be_a(OpenapiFirst::ValidatedRequest) + expect(last_env[described_class::RESPONSE]).to be_a(OpenapiFirst::ValidatedResponse) + end + context 'when using registered OAD' do let(:app) do described_class.app(