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
2 changes: 1 addition & 1 deletion examples/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ tags:
paths:
/:
get:
operationId: things#index
operationId: example#root
summary: Get metadata from the root of the API
tags: ["Metadata"]
responses:
Expand Down
2 changes: 1 addition & 1 deletion examples/rack_handler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

not_found = ->(_request) { [404, {}, []] }
handlers = {
'things#index' => lambda do |_request|
'example#root' => lambda do |_request|
[200, { Rack::CONTENT_TYPE => 'application/json' }, ['{"hello": "world"}']]
end
}
Expand Down
6 changes: 3 additions & 3 deletions lib/openapi_first/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ def initialize
yield self
end

def register(*)
Test.register(*)
def register(oad, as: :default)
Test.register(oad, as:)
end

attr_accessor :minimum_coverage, :coverage_formatter_options, :coverage_formatter
Expand All @@ -47,7 +47,7 @@ def handle_exit
end
return unless minimum_coverage > coverage

puts "API Coverage fails with exit 2, because API coverage of #{coverage}%" \
puts "API Coverage fails with exit 2, because API coverage of #{coverage}% " \
"is below minimum of #{minimum_coverage}%!"
exit 2
# :nocov:
Expand Down
4 changes: 2 additions & 2 deletions lib/openapi_first/test/coverage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,11 @@ def reset
end

def track_request(request, oad)
current_run[oad.key].track_request(request)
current_run[oad.key]&.track_request(request)
end

def track_response(response, _request, oad)
current_run[oad.key].track_response(response)
current_run[oad.key]&.track_response(response)
end

def result
Expand Down
49 changes: 41 additions & 8 deletions lib/openapi_first/test/methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,54 @@ module OpenapiFirst
module Test
# Methods to use in integration tests
module Methods
def self.[](*)
def self.included(base)
base.include(DefaultApiMethod)
base.include(AssertionMethod)
end

def self.[](application_under_test = nil, api: nil)
mod = Module.new do
def self.included(base)
OpenapiFirst::Test::Methods.included(base)
base.include OpenapiFirst::Test::Methods::AssertionMethod
end
end
mod.define_method(:app) { OpenapiFirst::Test.app(*) }

if api
mod.define_method(:openapi_first_default_api) { api }
else
mod.include(DefaultApiMethod)
end

if application_under_test
mod.define_method(:app) { OpenapiFirst::Test.app(application_under_test, api: openapi_first_default_api) }
end

mod
end

def self.included(base)
if Test.minitest?(base)
base.include(OpenapiFirst::Test::MinitestHelpers)
else
base.include(OpenapiFirst::Test::PlainHelpers)
# Default methods
module DefaultApiMethod
# This is the default api that is used by assert_api_conform
# :default is the default name that is used if you don't pass an `api:` option to `OpenapiFirst::Test.register`
# This is overwritten if you pass an `api:` option to `include OpenapiFirst::Test::Methods[…]`
def openapi_first_default_api
klass = self.class
if klass.respond_to?(:metadata) && klass.metadata[:api]
klass.metadata[:api]
else
:default
end
end
end

# @visibility private
module AssertionMethod
def self.included(base)
if Test.minitest?(base)
base.include(OpenapiFirst::Test::MinitestHelpers)
else
base.include(OpenapiFirst::Test::PlainHelpers)
end
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_first/test/minitest_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Test
# Assertion methods for Minitest
module MinitestHelpers
# :nocov:
def assert_api_conform(status: nil, api: :default)
def assert_api_conform(status: nil, api: openapi_first_default_api)
api = OpenapiFirst::Test[api]
request = respond_to?(:last_request) ? last_request : @request
response = respond_to?(:last_response) ? last_response : @response
Expand Down
2 changes: 1 addition & 1 deletion lib/openapi_first/test/plain_helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ module Test
# Assertion methods to use when no known test framework was found
# These methods just raise an exception if an error was found
module PlainHelpers
def assert_api_conform(status: nil, api: :default)
def assert_api_conform(status: nil, api: openapi_first_default_api)
api = OpenapiFirst::Test[api]
# :nocov:
request = respond_to?(:last_request) ? last_request : @request
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,12 +212,35 @@ def fixture_path(name)
end

it 'accepts an empty request body' do
header Rack::CONTENT_TYPE, 'application/json'
post path

expect(last_response.status).to be(200), last_response.body
expect(last_request.env[OpenapiFirst::REQUEST].parsed_body).to eq nil
end

it 'accepts an empty request body without content-type' do
post path

expect(last_response.status).to be(200), last_response.body
expect(last_request.env[OpenapiFirst::REQUEST].parsed_body).to eq nil
end

it 'accepts an unknown content-type and an empty request body' do
header Rack::CONTENT_TYPE, 'foo/bar'
post path

expect(last_response.status).to be(200), last_response.body
expect(last_request.env[OpenapiFirst::REQUEST].parsed_body).to eq nil
end

it 'returns 400 if content-type is unknown and request body is invalid' do
header Rack::CONTENT_TYPE, 'foo/bar'
post path, JSON.generate({ say: 'no ' })

expect(last_response.status).to be(400), last_response.body
end

it 'returns 400 if request body is invalid' do
header Rack::CONTENT_TYPE, 'application/json'
post path, JSON.generate({ say: 'no ' })
Expand Down
14 changes: 14 additions & 0 deletions spec/test/coverage_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,18 @@
specify { expect(result.coverage).to eq(50) }
end
end

describe '.track_request' do
it 'ignores unregistered OADs' do
oad = double(key: 'unknown')
described_class.track_request(double, oad)
end
end

describe '.track_response' do
it 'ignores unregistered OADs' do
oad = double(key: 'unknown')
described_class.track_response(double(:response), double(:request), oad)
end
end
end
181 changes: 147 additions & 34 deletions spec/test/methods_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,170 @@
require 'minitest'

RSpec.describe OpenapiFirst::Test::Methods do
it 'can be included' do
minitest_class = Class.new(Minitest::Test) do
it 'includes PlainHelpers when included' do
test_class = Class.new do
include OpenapiFirst::Test::Methods
end
expect(minitest_class.included_modules).to include(OpenapiFirst::Test::MinitestHelpers)

other_class = Class.new do
include OpenapiFirst::Test::Methods
end
expect(other_class.included_modules).to include(OpenapiFirst::Test::PlainHelpers)
expect(test_class.included_modules).to include(OpenapiFirst::Test::PlainHelpers)
end

it 'adds an app method that wraps the app' do
it 'raises OpenapiFirst::Error when assertion fails' do
OpenapiFirst::Test.register('./examples/openapi.yaml')
myapp = ->(_env) { Rack::Response.new('hello').finish }
minitest_class = Class.new(Minitest::Test) do
include OpenapiFirst::Test::Methods[myapp]
end
expect(minitest_class.included_modules).to include(OpenapiFirst::Test::MinitestHelpers)

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
end

it 'detects wrong response status for Minitest' do
OpenapiFirst::Test.register('./examples/openapi.yaml')
minitest_class = Class.new(Minitest::Test) do
test_class = Class.new do
include OpenapiFirst::Test::Methods

def last_request = Rack::Request.new(Rack::MockRequest.env_for('/'))
def last_response = Rack::Response.new
end

expect do
minitest_class.new('hey').assert_api_conform(status: 444)
end.to raise_error(Minitest::Assertion)
test_class.new.assert_api_conform(status: 444)
end.to raise_error(OpenapiFirst::Error)
end

it 'detects wrong response status for non Minitest' do
OpenapiFirst::Test.register('./examples/openapi.yaml')
minitest_class = Class.new do
context 'with RSpec' do
let(:app) do
lambda do |_|
res = Rack::Response.new(JSON.generate(hello: 'there'))
res.content_type = 'application/json'
res.finish
end
end

context 'with metadata', api: :v1 do
include OpenapiFirst::Test::Methods
include Rack::Test::Methods

def last_request = Rack::Request.new(Rack::MockRequest.env_for('/'))
def last_response = Rack::Response.new
it 'targets that api when calling assert_api_conform' do
expect do
assert_api_conform(status: 200)
end.to raise_error(OpenapiFirst::Test::NotRegisteredError) do |ex|
expect(ex.message).to start_with("API description ':v1' not found.")
end
end
end

expect do
minitest_class.new.assert_api_conform(status: 444)
end.to raise_error(OpenapiFirst::Error)
context 'with an [api:] option', api: :v2 do
include OpenapiFirst::Test::Methods[api: :v1]

it 'targets the api from the argument when calling assert_api_conform' do
expect do
assert_api_conform(status: 200)
end.to raise_error(OpenapiFirst::Test::NotRegisteredError) do |ex|
expect(ex.message).to start_with("API description ':v1' not found.")
end
end
end

context 'with an [Application] argument and metadata', api: :v2 do
include OpenapiFirst::Test::Methods[->(_) { Rack::Response.new('hey').finish }]
include Rack::Test::Methods

it 'targets that api when calling the app' do
OpenapiFirst::Test.register('./examples/openapi.yaml', as: :v2)

get('/')

expect(last_request.env[OpenapiFirst::REQUEST].operation_id).to eq('example#root')
end
end
end

context 'with Minitest' do
it 'includes MinitestHelpers when included' do
minitest_class = Class.new(Minitest::Test) do
include OpenapiFirst::Test::Methods
end
expect(minitest_class.included_modules).to include(OpenapiFirst::Test::MinitestHelpers)
end

it 'raises Minitest::Assertion when assertion fails' do
OpenapiFirst::Test.register('./examples/openapi.yaml')
minitest_class = Class.new(Minitest::Test) do
include OpenapiFirst::Test::Methods

def last_request = Rack::Request.new(Rack::MockRequest.env_for('/'))
def last_response = Rack::Response.new
end

expect do
minitest_class.new('hey').assert_api_conform(status: 444)
end.to raise_error(Minitest::Assertion)
end
end

context 'with [arguments]' do
it 'adds an app method that wraps the default API' do
OpenapiFirst::Test.register('./examples/openapi.yaml')
myapp = ->(_env) { Rack::Response.new('hello').finish }

minitest_class = Class.new(Minitest::Test) do
include OpenapiFirst::Test::Methods[myapp]
end

expect(minitest_class.included_modules).to include(OpenapiFirst::Test::MinitestHelpers)
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
end

it 'adds an app method that wraps the app for a specific API' do
OpenapiFirst::Test.register('./examples/openapi.yaml', as: :v1)
myapp = ->(_env) { Rack::Response.new('hello').finish }

minitest_class = Class.new(Minitest::Test) do
include OpenapiFirst::Test::Methods[myapp, api: :v1]
end

expect(minitest_class.included_modules).to include(OpenapiFirst::Test::MinitestHelpers)
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
end

it 'adds an assert_api_conform method that targets the specified API' do
OpenapiFirst::Test.register('./examples/openapi.yaml', as: :v1)
test_class = Class.new do
include OpenapiFirst::Test::Methods[api: :v1]

def last_request = Rack::Request.new(Rack::MockRequest.env_for('/'))
def last_response = Rack::Response.new
end

expect(test_class.new.openapi_first_default_api).to eq(:v1)

expect do
test_class.new.assert_api_conform(status: 444)
end.to raise_error(OpenapiFirst::Error)
end

it 'adds an assert_api_conform method that still can target another API' do
OpenapiFirst::Test.register('./examples/openapi.yaml', as: :v1)
test_class = Class.new do
include OpenapiFirst::Test::Methods[api: :v1]

def last_request = Rack::Request.new(Rack::MockRequest.env_for('/'))
def last_response = Rack::Response.new
end

expect do
test_class.new.assert_api_conform(status: 444, api: :other)
end.to raise_error(OpenapiFirst::Test::NotRegisteredError) do |ex|
expect(ex.message).to start_with("API description ':other' not found.")
end
end

it 'does not add an app method if app is nil' do
OpenapiFirst::Test.register('./examples/openapi.yaml', as: :v1)

minitest_class = Class.new(Minitest::Test) do
include OpenapiFirst::Test::Methods[api: :v1]
end

expect(minitest_class.included_modules).to include(OpenapiFirst::Test::MinitestHelpers)
expect(minitest_class.new(1).respond_to?(:app)).to eq(false)
end
end
end
Loading