Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
end
```

- Added `OpenapiFirst::ValidatedRequest#unknown?` and `OpenapiFirst::ValidatedResponse#unknown?`

- Added `OpenapiFirst::Test::Configuration#ignore_response_error` and `OpenapiFirst::Test::Configuration#ignore_request_error` to configure which request/response errors should not raise an error during testing.

## 3.1.1

- Changed: Return uniqe errors in default error responses
Expand Down
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,23 @@ Here is how to set it up:

### Configure test coverage

You can ignore errors for certain requests/responses:

```ruby
OpenapiFirst::Test.setup do |test|
test.ignore_request_error do |validated_request|
# Ignore unknown requests on certain paths
validated_request.path.start_with?('/api/v1') && validated_request.unknown?
end

test.ignore_response_error do |validated_response, rack_request|
# Ignore invalid response bodies on certain paths
validated_request.path.start_with?('/api/legacy/stuff') && validated_request.error.type == :invalid_body
end
end
```


OpenapiFirst::Test raises an error when a response status is not defined except for 404 and 500. You can change this:

```ruby
Expand Down
10 changes: 3 additions & 7 deletions lib/openapi_first/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def self.registered?(oad)
end

# Sets up OpenAPI test coverage and OAD registration.
# @yieldparam [OpenapiFirst::Test::Configuration] configuration A configuration to setup test integration
# @yield [OpenapiFirst::Test::Configuration] configuration A configuration to setup test integration
def self.setup
install
yield configuration if block_given?
Expand Down Expand Up @@ -154,20 +154,16 @@ def unknown_parameters_message(unknown_parameters, validated_request)

def raise_request_error?(validated_request)
return false if validated_request.valid?
return false unless configuration.raise_error_for_request.call(validated_request)
return false if validated_request.known?

!configuration.ignore_unknown_requests
configuration.raise_request_error?(validated_request)
end

def many?(array) = array.length > 1

def raise_response_error?(validated_response, rack_request)
return false if validated_response.valid?
return false unless configuration.response_raise_error
return false unless configuration.raise_error_for_response.call(validated_response, rack_request)

!configuration.ignore_response?(validated_response)
configuration.raise_response_error?(validated_response, rack_request)
end
end
end
Expand Down
42 changes: 34 additions & 8 deletions lib/openapi_first/test/configuration.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ def initialize
@ignore_unknown_response_status = false
@report_coverage = true
@ignore_unknown_requests = false
@raise_error_for_request = ->(_validated_request) { true }
@raise_error_for_response = ->(_validated_response, _rack_request) { true }
@ignore_request_error = nil
@ignore_response_error = nil
end

# Register OADs, but don't load them just yet
Expand All @@ -32,8 +32,7 @@ def observe(app, api: :default)
end

attr_accessor :coverage_formatter_options, :coverage_formatter, :response_raise_error,
:ignore_unknown_requests, :ignore_unknown_response_status, :minimum_coverage,
:raise_error_for_request, :raise_error_for_response
:ignore_unknown_requests, :ignore_unknown_response_status, :minimum_coverage
attr_reader :report_coverage, :ignored_unknown_status

# Set ignored unknown status codes.
Expand All @@ -53,6 +52,23 @@ def report_coverage=(value)
@report_coverage = value
end

# Ignore certain errors for certain requests
# @param block A Proc that will be called with [OpenapiFirst::ValidatedRequest]
def ignore_request_error(&block)
raise ArgumentError, 'You have to pass a block' unless block_given?

@ignore_request_error = block
end

# Ignore certain errors for certain responses
# @param block A Proc that will be called with [OpenapiFirst::ValidatedResponse, Rack::Request]
def ignore_response_error(&block)
raise ArgumentError, 'You have to pass a block' unless block_given?

@ignore_response_error = block
end

# @param block A Proc that will be called with [OpenapiFirst::ValidatedResponse, Rack::Request]
def skip_response_coverage(&block)
return @skip_response_coverage unless block_given?

Expand All @@ -66,12 +82,22 @@ def skip_coverage(&block)
end

alias ignore_unknown_response_status? ignore_unknown_response_status
alias ignore_unknown_requests? ignore_unknown_requests

def raise_request_error?(validated_request)
return false if @ignore_request_error&.call(validated_request)
return false if ignore_unknown_requests? && validated_request.unknown?

validated_request.unknown?
end

def ignore_response?(validated_response)
return false if validated_response.known?
return true if ignored_unknown_status.include?(validated_response.status)
def raise_response_error?(validated_response, rack_request)
return false if @ignore_response_error&.call(validated_response, rack_request)
return false if response_raise_error == false
return false if ignored_unknown_status.include?(validated_response.status)
return false if ignore_unknown_response_status? && validated_response.error.type == :response_status_not_found

ignore_unknown_response_status? && validated_response.error.type == :response_status_not_found
true
end
end
end
Expand Down
3 changes: 3 additions & 0 deletions lib/openapi_first/validated_request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ def invalid? = !valid?
# Returns true if the request is defined.
def known? = request_definition != nil

# Returns true if the request is not defined.
def unknown? = !known?

# Merged path, query, body parameters.
# Here path has the highest precedence, then query, then body.
# @return [Hash<String, anything>]
Expand Down
3 changes: 3 additions & 0 deletions lib/openapi_first/validated_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def valid?
# Returns true if the response is defined.
def known? = response_definition != nil

# Returns true if the response is not defined.
def unknown? = !known?

# Checks if the response is invalid.
# @return [Boolean]
def invalid?
Expand Down
57 changes: 22 additions & 35 deletions spec/test/configuration_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,61 +20,48 @@
end
end

describe 'raise_error_for_request' do
it 'has a default lambda that returns true always' do
expect(configuration.raise_error_for_request).to be_a(Proc)
expect(configuration.raise_error_for_request.call(nil)).to eq(true)
end
end

describe 'raise_error_for_response' do
it 'has a default lambda that returns true always' do
expect(configuration.raise_error_for_response).to be_a(Proc)
expect(configuration.raise_error_for_response.call(nil, nil)).to eq(true)
end
end

describe '#ignore_response?' do
let(:valid_response) { double(valid?: true, known?: true, status: 302) }
let(:invalid_response) { double(valid?: false, known?: true, status: 302) }
let(:unknown_response_status) { double(valid?: false, known?: false, status: 302, error: OpenapiFirst::Failure.new(:response_status_not_found)) }

it 'returns false by default for a valid responses' do
expect(configuration.ignore_response?(valid_response)).to eq(false)
describe '#raise_response_error?' do
let(:valid_response) { double(valid?: true, unknown?: false, status: 302) }
let(:invalid_response) { double(valid?: false, unknown?: false, status: 302) }
let(:unknown_response_status) { double(valid?: false, unknown?: true, status: 302, error: OpenapiFirst::Failure.new(:response_status_not_found)) }
let(:rack_request) { Rack::Request.new({}) }

it 'returns true by default for valid responses' do
expect(configuration.raise_response_error?(valid_response, rack_request)).to eq(true)
end

it 'returns false by default for an invalid responses' do
expect(configuration.ignore_response?(invalid_response)).to eq(false)
it 'returns true by default for invalid responses' do
expect(configuration.raise_response_error?(invalid_response, rack_request)).to eq(true)
end

it 'returns false by default for an unkonwn responses' do
expect(configuration.ignore_response?(unknown_response_status)).to eq(false)
it 'returns true by default for unkonwn responses' do
expect(configuration.raise_response_error?(unknown_response_status, rack_request)).to eq(true)
end

context 'when status is ignored' do
before { configuration.ignored_unknown_status << unknown_response_status.status }

it 'returns true for an unknown response with that status' do
expect(configuration.ignore_response?(unknown_response_status)).to eq(true)
it 'returns false for an unknown response with that status' do
expect(configuration.raise_response_error?(unknown_response_status, rack_request)).to eq(false)
end

it 'returns false for an unknown response with another status' do
unknown_response_status = double(valid?: false, known?: false, status: 409)
expect(configuration.ignore_response?(unknown_response_status)).to eq(false)
it 'returns true for an unknown response with another status' do
unknown_response_status = double(valid?: false, unknown?: true, status: 409, error: OpenapiFirst::Failure.new(:response_status_not_found))
expect(configuration.raise_response_error?(unknown_response_status, rack_request)).to eq(true)
end
end

context 'when all unknown response status are ignored' do
before { configuration.ignore_unknown_response_status = true }

it 'returns true for any unknown response status' do
expect(configuration.ignore_response?(unknown_response_status)).to eq(true)
it 'returns false for any unknown response status' do
expect(configuration.raise_response_error?(unknown_response_status, rack_request)).to eq(false)
end

it 'returns false for an unknown response with a known status' do
unknown_response_content_type = double(valid?: false, known?: false, status: 302, error: OpenapiFirst::Failure.new(:response_content_type_not_found))
it 'returns true for an unknown response with a known status' do
unknown_response_content_type = double(valid?: false, unknown?: true, status: 302, error: OpenapiFirst::Failure.new(:response_content_type_not_found))

expect(configuration.ignore_response?(unknown_response_content_type)).to eq(false)
expect(configuration.raise_response_error?(unknown_response_content_type, rack_request)).to eq(true)
end
end
end
Expand Down
46 changes: 23 additions & 23 deletions spec/test_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -425,9 +425,9 @@ def call(_env)
end.to raise_error(OpenapiFirst::RequestInvalidError)
end

it 'does not raise an error for an invalid request when raises_error_for_request returns false' do
it 'does not raise an error for an invalid request when request is ignored' do
described_class.setup do |test|
test.raise_error_for_request = ->(_validated_request) { false }
test.ignore_request_error { true }
end

config = OpenapiFirst.configuration
Expand Down Expand Up @@ -460,9 +460,9 @@ def call(_env)
end.to raise_error(OpenapiFirst::ResponseInvalidError)
end

it 'does not raise an error for an invalid response when raises_error_for_response returns false' do
it 'does not raise an error for an invalid response when response error is ignored' do
described_class.setup do |test|
test.raise_error_for_response = ->(_validated_response, _rack_request) { false }
test.ignore_response_error { true }
end

config = OpenapiFirst.configuration
Expand Down Expand Up @@ -595,22 +595,22 @@ def call(_env)
end.to raise_error(OpenapiFirst::NotFoundError)
end

context 'with raise_error_for_request returning false' do
context 'with ignore_request_error returning true' do
before(:each) do
described_class.uninstall
described_class.setup do |test|
test.register(definition)
test.ignore_unknown_requests = false
test.report_coverage = false
end
end

it 'does not raise an error' do
called = false
described_class.configuration.raise_error_for_request = lambda do |validated_request|
called = true
expect(validated_request).to be_a(OpenapiFirst::ValidatedRequest)
false
described_class.setup do |test|
test.register(definition)
test.ignore_unknown_requests = false
test.report_coverage = false
test.ignore_request_error do |validated_request|
called = true
expect(validated_request).to be_a(OpenapiFirst::ValidatedRequest)
true
end
end

expect do
Expand Down Expand Up @@ -736,21 +736,21 @@ def call(_env)
end.to raise_error(OpenapiFirst::ResponseInvalidError)
end

context 'with raise_error_for_response returning false' do
context 'with ignore_response_error? returning true' do
before(:each) do
described_class.uninstall
described_class.setup do |test|
test.register(definition)
end
end

it 'does not raise an error' do
called = false
described_class.configuration.raise_error_for_response = lambda do |validated_response, rack_request|
called = true
expect(validated_response).to be_a(OpenapiFirst::ValidatedResponse)
expect(rack_request).to be_a(Rack::Request)
false
described_class.setup do |test|
test.register(definition)
test.ignore_response_error do |validated_response, rack_request|
called = true
expect(validated_response).to be_a(OpenapiFirst::ValidatedResponse)
expect(rack_request).to be_a(Rack::Request)
true
end
end

expect do
Expand Down
10 changes: 10 additions & 0 deletions spec/validated_request_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,16 @@
end
end

describe '#unknown?' do
it 'returns false if request is known' do
expect(valid_request).not_to be_unknown
end

it 'returns true if request is known' do
expect(unknown_request).to be_unknown
end
end

describe '#parsed_params' do
it 'returns merged path, query and body params' do
parsed_request = double(
Expand Down
24 changes: 24 additions & 0 deletions spec/validated_response_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,28 @@
expect(validated.parsed_headers).to be_nil
end
end

describe '#known?' do
it 'returns false if response is unknown' do
unknown_response = described_class.new(double(:response), error: double(:error))
expect(unknown_response).not_to be_known
end

it 'returns true if response is known' do
valid_response = described_class.new(double(:response), error: nil, response_definition: double(:response_definition))
expect(valid_response).to be_known
end
end

describe '#unknown?' do
it 'returns true if response is unknown' do
unknown_response = described_class.new(double(:response), error: double(:error))
expect(unknown_response).to be_unknown
end

it 'returns false if response is known' do
valid_response = described_class.new(double(:response), error: nil, response_definition: double(:response_definition))
expect(valid_response).not_to be_unknown
end
end
end
Loading