From b7cf5f0076fbec618d0919539b05e9632a28afbe Mon Sep 17 00:00:00 2001 From: Konstantin Munteanu <637499+mkon@users.noreply.github.com> Date: Thu, 15 Sep 2022 11:25:41 +0200 Subject: [PATCH] Support dynamic paths in response lookup (#19) --- README.md | 13 ++++++++ lib/openapi_contracts/matchers.rb | 4 +-- .../matchers/match_openapi_doc.rb | 5 +-- .../openapi/components/schemas/Message.yaml | 20 +++++++++++ spec/fixtures/openapi/openapi.yaml | 2 ++ spec/fixtures/openapi/paths/message.yaml | 33 +++++++++++++++++++ spec/integration/rspec_spec.rb | 23 +++++++++++++ .../matchers/match_openapi_doc_spec.rb | 2 +- 8 files changed, 97 insertions(+), 5 deletions(-) create mode 100644 spec/fixtures/openapi/components/schemas/Message.yaml create mode 100644 spec/fixtures/openapi/paths/message.yaml diff --git a/README.md b/README.md index e9a8b63..e71556c 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,19 @@ it 'responds with 200 and matches the doc' do } ``` +### Options + +The `match_openapi_doc($doc)` method allows passing options as a 2nd argument. +This allows overriding the default request.path lookup in case this does not find +the correct response definition in your schema. This is especially important with +dynamic paths. + +Example: + +```ruby +it { is_expected.to match_openapi_doc($api_doc, path: '/messages/{id}').with_http_status(:ok) } +``` + ### How it works It uses the `request.path`, `request.method`, `status` and `headers` on the test subject (which must be the response) to find the response schema in the OpenAPI document. Then it does the following checks: diff --git a/lib/openapi_contracts/matchers.rb b/lib/openapi_contracts/matchers.rb index 425594d..f33db2b 100644 --- a/lib/openapi_contracts/matchers.rb +++ b/lib/openapi_contracts/matchers.rb @@ -2,8 +2,8 @@ module OpenapiContracts module Matchers autoload :MatchOpenapiDoc, 'openapi_contracts/matchers/match_openapi_doc' - def match_openapi_doc(doc) - MatchOpenapiDoc.new(doc) + def match_openapi_doc(doc, options = {}) + MatchOpenapiDoc.new(doc, options) end end end diff --git a/lib/openapi_contracts/matchers/match_openapi_doc.rb b/lib/openapi_contracts/matchers/match_openapi_doc.rb index 77d8328..b80dd5d 100644 --- a/lib/openapi_contracts/matchers/match_openapi_doc.rb +++ b/lib/openapi_contracts/matchers/match_openapi_doc.rb @@ -1,8 +1,9 @@ module OpenapiContracts module Matchers class MatchOpenapiDoc - def initialize(doc) + def initialize(doc, options) @doc = doc + @options = options @errors = [] end @@ -43,7 +44,7 @@ def response_desc def response_spec @response_spec ||= @doc.response_for( - @response.request.path, + @options.fetch(:path, @response.request.path), @response.request.request_method.downcase, @response.status.to_s ) diff --git a/spec/fixtures/openapi/components/schemas/Message.yaml b/spec/fixtures/openapi/components/schemas/Message.yaml new file mode 100644 index 0000000..16480bc --- /dev/null +++ b/spec/fixtures/openapi/components/schemas/Message.yaml @@ -0,0 +1,20 @@ +type: object +properties: + id: + type: string + example: acd3751 + type: + type: string + attributes: + type: object + properties: + body: + type: string + additionalProperties: false + required: + - body +additionalProperties: false +required: + - id + - type + - attributes diff --git a/spec/fixtures/openapi/openapi.yaml b/spec/fixtures/openapi/openapi.yaml index 871265b..c535fbe 100644 --- a/spec/fixtures/openapi/openapi.yaml +++ b/spec/fixtures/openapi/openapi.yaml @@ -32,5 +32,7 @@ paths: $ref: components/responses/BadRequest.yaml '500': description: Server Error + /messages/{id}: + $ref: 'paths/message.yaml' /user: $ref: 'paths/user.yaml' diff --git a/spec/fixtures/openapi/paths/message.yaml b/spec/fixtures/openapi/paths/message.yaml new file mode 100644 index 0000000..6961280 --- /dev/null +++ b/spec/fixtures/openapi/paths/message.yaml @@ -0,0 +1,33 @@ +get: + tags: + - Message + summary: Get Message + description: Get Message + operationId: get_message + parameters: + - name: id + in: path + description: Id of the message. + required: true + schema: + type: string + responses: + '200': + description: OK + headers: + x-request-id: + schema: + type: string + required: true + content: + application/json: + schema: + type: object + properties: + data: + $ref: ../components/schemas/Message.yaml + required: + - data + additionalProperties: false + '400': + $ref: ../components/responses/BadRequest.yaml diff --git a/spec/integration/rspec_spec.rb b/spec/integration/rspec_spec.rb index 0a3a716..1506db5 100644 --- a/spec/integration/rspec_spec.rb +++ b/spec/integration/rspec_spec.rb @@ -51,6 +51,29 @@ it { is_expected.to match_openapi_doc(doc).with_http_status(:bad_request) } end + context 'when using dynamic paths' do + let(:request_env) do + { + 'PATH_INFO' => '/messages/ef278', + 'REQUEST_METHOD' => 'GET' + } + end + let(:response_status) { 200 } + let(:response_body) do + { + data: { + id: '1ef', + type: 'messages', + attributes: { + body: 'foo' + } + } + } + end + + it { is_expected.to match_openapi_doc(doc, path: '/messages/{id}').with_http_status(:ok) } + end + context 'when a required header is missing' do before { response_headers.delete('X-Request-Id') } diff --git a/spec/openapi_contracts/matchers/match_openapi_doc_spec.rb b/spec/openapi_contracts/matchers/match_openapi_doc_spec.rb index 7294d7d..1ad3a62 100644 --- a/spec/openapi_contracts/matchers/match_openapi_doc_spec.rb +++ b/spec/openapi_contracts/matchers/match_openapi_doc_spec.rb @@ -1,5 +1,5 @@ RSpec.describe OpenapiContracts::Matchers::MatchOpenapiDoc do - subject(:matcher) { described_class.new(doc) } + subject(:matcher) { described_class.new(doc, {}) } let(:doc) { OpenapiContracts::Doc.parse(FIXTURES_PATH.join('openapi')) }