rename to rswag plus major refactor - almost a rewrite

This commit is contained in:
richie
2016-10-11 18:31:12 -07:00
parent f8d993356f
commit c558098c39
453 changed files with 52410 additions and 35793 deletions

2
rswag-specs/.rspec Normal file
View File

@@ -0,0 +1,2 @@
--color
--require spec_helper

20
rswag-specs/MIT-LICENSE Normal file
View File

@@ -0,0 +1,20 @@
Copyright 2015 domaindrivendev
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

27
rswag-specs/Rakefile Normal file
View File

@@ -0,0 +1,27 @@
#!/usr/bin/env rake
begin
require 'bundler/setup'
rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
begin
require 'rdoc/task'
rescue LoadError
require 'rdoc/rdoc'
require 'rake/rdoctask'
RDoc::Task = Rake::RDocTask
end
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'rswag-specs'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
end
Bundler::GemHelper.install_tasks

View File

@@ -0,0 +1,8 @@
Description:
Adds swagger_helper to enable Swagger DSL in integration specs
Example:
rails generate rswag:specs:install
This will create:
spec/swagger_helper.rb

View File

@@ -0,0 +1,14 @@
require 'rails/generators'
module Rswag
module Specs
class InstallGenerator < Rails::Generators::Base
source_root File.expand_path('../templates', __FILE__)
def add_swagger_helper
template('swagger_helper.rb', 'spec/swagger_helper.rb')
end
end
end
end

View File

@@ -0,0 +1,25 @@
require 'rails_helper'
RSpec.configure do |config|
# Specify a root folder where Swagger JSON files are generated
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
# to ensure that it's confiugred to server Swagger from the same folder
config.swagger_root = Rails.root.to_s + '/swagger'
# Define one or more Swagger documents and provide global metadata for each one
# When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
# be generated at the provided relative path under swagger_root
# By default, the operations defined in spec files are added to the first
# document below. You can override this behavior by adding a swagger_doc tag to the
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
config.swagger_docs = {
'v1/swagger.json' => {
swagger: '2.0',
info: {
title: 'API V1',
version: 'v1'
},
paths: {}
}
}
end

View File

@@ -0,0 +1,22 @@
require 'rspec/core'
require 'rswag/specs/version'
require 'rswag/specs/example_group_helpers'
require 'rswag/specs/example_helpers'
require 'rswag/specs/railtie' if defined?(Rails::Railtie)
module Rswag
module Specs
# Extend RSpec with a swagger-based DSL
::RSpec.configure do |c|
c.add_setting :swagger_root
c.add_setting :swagger_docs
c.extend ExampleGroupHelpers, type: :request
c.include ExampleHelpers, type: :request
end
# Support Rails 3+ and RSpec 2+ (sigh!)
RAILS_VERSION = Rails::VERSION::MAJOR
RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i
end
end

View File

@@ -0,0 +1,80 @@
module Rswag
module Specs
module ExampleGroupHelpers
def path(path, &block)
api_metadata = { path: path}
describe(path, api_metadata, &block)
end
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
define_method(verb) do |summary, &block|
api_metadata = { operation: { verb: verb, summary: summary } }
describe(verb, api_metadata, &block)
end
end
[ :operationId, :deprecated, :security ].each do |attr_name|
define_method(attr_name) do |value|
metadata[:operation][attr_name] = value
end
end
# NOTE: 'description' requires special treatment because ExampleGroup already
# defines a method with that name. Provide an override that supports the existing
# functionality while also setting the appropriate metadata if applicable
def description(value=nil)
return super() if value.nil?
metadata[:operation][:description] = value
end
# These are array properties - note the splat operator
[ :tags, :consumes, :produces, :schemes ].each do |attr_name|
define_method(attr_name) do |*value|
metadata[:operation][attr_name] = value
end
end
def parameter(attributes)
attributes[:required] = true if attributes[:in].to_sym == :path
metadata[:operation][:parameters] ||= []
metadata[:operation][:parameters] << attributes
end
def response(code, description, &block)
api_metadata = { response: { code: code, description: description } }
context(description, api_metadata, &block)
end
def schema(value)
metadata[:response][:schema] = value
end
def header(name, attributes)
metadata[:response][:headers] ||= {}
metadata[:response][:headers][name] = attributes
end
def run_test!
# NOTE: rspec 2.x support
if RSPEC_VERSION < 3
before do
submit_request(example.metadata)
end
it "returns a #{metadata[:response][:code]} response" do
assert_response_matches_metadata(example.metadata)
end
else
before do |example|
submit_request(example.metadata)
end
it "returns a #{metadata[:response][:code]} response" do |example|
assert_response_matches_metadata(example.metadata)
end
end
end
end
end
end

View File

@@ -0,0 +1,43 @@
require 'rswag/specs/request_factory'
require 'rswag/specs/response_validator'
module Rswag
module Specs
module ExampleHelpers
def submit_request(api_metadata)
factory = RequestFactory.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
if RAILS_VERSION < 5
send(
api_metadata[:operation][:verb],
factory.build_fullpath(self),
factory.build_body(self),
factory.build_headers(self)
)
else
send(
api_metadata[:operation][:verb],
factory.build_fullpath(self),
{
params: factory.build_body(self),
headers: factory.build_headers(self)
}
)
end
end
def assert_response_matches_metadata(api_metadata)
validator = ResponseValidator.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
validator.validate!(response)
end
private
def global_metadata(swagger_doc)
swagger_docs = ::RSpec.configuration.swagger_docs
swagger_doc.nil? ? swagger_docs.values.first : swagger_docs[swagger_doc]
end
end
end
end

View File

@@ -0,0 +1,10 @@
module Rswag
module Specs
class Railtie < ::Rails::Railtie
rake_tasks do
load File.expand_path('../../../tasks/rswag-specs_tasks.rake', __FILE__)
end
end
end
end

View File

@@ -0,0 +1,70 @@
require 'active_support/core_ext/hash/slice'
require 'json'
module Rswag
module Specs
class RequestFactory
def initialize(api_metadata, global_metadata)
@api_metadata = api_metadata
@global_metadata = global_metadata
end
def build_fullpath(example)
@api_metadata[:path].dup.tap do |t|
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
t.concat(build_query(example))
t.prepend(@global_metadata[:basePath] || '')
end
end
def build_query(example)
query_string = parameters_in(:query)
.map { |p| "#{p[:name]}=#{example.send(p[:name])}" }
.join('&')
query_string.empty? ? '' : "?#{query_string}"
end
def build_body(example)
body_parameter = parameters_in(:body).first
body_parameter.nil? ? '' : example.send(body_parameter[:name]).to_json
end
def build_headers(example)
headers = Hash[ parameters_in(:header).map { |p| [ p[:name], example.send(p[:name]).to_s ] } ]
headers.tap do |h|
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
h['ACCEPT'] = produces.join(';') unless produces.nil?
h['CONTENT_TYPE'] = consumes.join(';') unless consumes.nil?
end
end
private
def parameters_in(location)
(@api_metadata[:operation][:parameters] || [])
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
.concat(resolve_api_key_parameters)
.select { |p| p[:in] == location }
end
def resolve_parameter(ref)
defined_params = @global_metadata[:parameters]
key = ref.sub('#/parameters/', '')
raise "Referenced parameter '#{ref}' must be defined" unless defined_params && defined_params[key]
defined_params[key]
end
def resolve_api_key_parameters
@api_key_params ||= begin
global_requirements = (@global_metadata[:security] || {})
requirements = global_requirements.merge(@api_metadata[:operation][:security] || {})
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements.keys)
definitions.values.select { |d| d[:type] == :apiKey }
end
end
end
end
end

View File

@@ -0,0 +1,38 @@
require 'json-schema'
module Rswag
module Specs
class ResponseValidator
def initialize(api_metadata, global_metadata)
@api_metadata = api_metadata
@global_metadata = global_metadata
end
def validate!(response)
validate_code!(response.code)
validate_body!(response.body)
end
private
def validate_code!(code)
if code.to_s != @api_metadata[:response][:code].to_s
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{@api_metadata[:response][:code]}'"
end
end
def validate_body!(body)
schema = @api_metadata[:response][:schema]
return if schema.nil?
begin
JSON::Validator.validate!(schema.merge(@global_metadata), body)
rescue JSON::Schema::ValidationError => ex
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
end
end
end
class UnexpectedResponse < StandardError; end
end
end

View File

@@ -0,0 +1,79 @@
require 'active_support/core_ext/hash/deep_merge'
require 'rspec/core/formatters/base_text_formatter'
require 'swagger_helper'
module Rswag
module Specs
class SwaggerFormatter
# NOTE: rspec 2.x support
if RSPEC_VERSION > 2
::RSpec::Core::Formatters.register self, :example_group_finished, :stop
end
def initialize(output)
@output = output
@swagger_root = ::RSpec.configuration.swagger_root
raise ConfigurationError, 'Missing swagger_root. See swagger_helper.rb' if @swagger_root.nil?
@swagger_docs = ::RSpec.configuration.swagger_docs || []
raise ConfigurationError, 'Missing swagger_docs. See swagger_helper.rb' if @swagger_docs.empty?
@output.puts 'Generating Swagger docs ...'
end
def example_group_finished(notification)
# NOTE: rspec 2.x support
if RSPEC_VERSION > 2
metadata = notification.group.metadata
else
metadata = notification.metadata
end
return unless metadata.has_key?(:response)
swagger_doc = get_swagger_doc(metadata[:swagger_doc])
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
end
def stop(notification=nil)
@swagger_docs.each do |url_path, doc|
file_path = File.join(@swagger_root, url_path)
dirname = File.dirname(file_path)
FileUtils.mkdir_p dirname unless File.exists?(dirname)
File.open(file_path, 'w') do |file|
file.write(JSON.pretty_generate(doc))
end
@output.puts "Swagger doc generated at #{file_path}"
end
end
private
def get_swagger_doc(tag)
return @swagger_docs.values.first if tag.nil?
raise ConfigurationError, "Unknown swagger_doc '#{tag}'" unless @swagger_docs.has_key?(tag)
@swagger_docs[tag]
end
def metadata_to_swagger(metadata)
response_code = metadata[:response][:code]
response = metadata[:response].reject { |k,v| k == :code }
verb = metadata[:operation][:verb]
operation = metadata[:operation]
.reject { |k,v| k == :verb }
.merge(responses: { response_code => response })
{
paths: {
metadata[:path] => {
verb => operation
}
}
}
end
end
class ConfigurationError < StandardError; end
end
end

View File

@@ -0,0 +1,5 @@
module Rswag
module Specs
VERSION = '1.0.0'
end
end

View File

@@ -0,0 +1,18 @@
require 'rspec/core/rake_task'
namespace :rswag do
namespace :specs do
desc 'Generate Swagger JSON files from integration specs'
RSpec::Core::RakeTask.new('swaggerize') do |t|
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
# NOTE: rspec 2.x support
if Rswag::Specs::RSPEC_VERSION > 2
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
else
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
end
end
end
end

View File

@@ -0,0 +1,20 @@
$:.push File.expand_path("../lib", __FILE__)
# Maintain your gem's version:
require 'rswag/specs/version'
# Describe your gem and declare its dependencies:
Gem::Specification.new do |s|
s.name = "rswag-specs"
s.version = Rswag::Specs::VERSION
s.authors = ["Richie Morris"]
s.email = ["domaindrivendev@gmail.com"]
s.homepage = "https://github.com/domaindrivendev/rswag"
s.summary = "A Swagger-based DSL for rspec-rails & accompanying rake task for generating Swagger files"
s.files = Dir["{app,config,db,lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
s.add_dependency "rails", ">= 3.1", "< 5.1"
s.add_dependency 'json-schema'
s.add_development_dependency 'rspec-rails'
end

View File

@@ -0,0 +1,24 @@
require 'generator_spec'
require 'generators/rswag/specs/install/install_generator'
module Rswag
module Specs
describe InstallGenerator do
include GeneratorSpec::TestCase
destination File.expand_path('../tmp', __FILE__)
before(:all) do
prepare_destination
fixtures_dir = File.expand_path('../fixtures', __FILE__)
FileUtils.cp_r("#{fixtures_dir}/spec", destination_root)
run_generator
end
it 'installs the swagger_helper for rspec' do
assert_file('spec/swagger_helper.rb')
end
end
end
end

View File

@@ -0,0 +1,25 @@
require 'rails_helper'
RSpec.configure do |config|
# Specify a root folder where Swagger JSON files are generated
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
# to ensure that it's confiugred to server Swagger from the same folder
config.swagger_root = Rails.root.to_s + '/swagger'
# Define one or more Swagger documents and provide global metadata for each one
# When you run the 'rswag:specs:to_swagger' rake task, the complete Swagger will
# be generated at the provided relative path under swagger_root
# By default, the operations defined in spec files are added to the first
# document below. You can override this behavior by adding a swagger_doc tag to the
# the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
config.swagger_docs = {
'v1/swagger.json' => {
swagger: '2.0',
info: {
title: 'API V1',
version: 'v1'
},
paths: {}
}
}
end

View File

@@ -0,0 +1,143 @@
require 'rswag/specs/example_group_helpers'
module Rswag
module Specs
describe ExampleGroupHelpers do
subject { double('example_group') }
let(:api_metadata) { {} }
before do
subject.extend ExampleGroupHelpers
allow(subject).to receive(:describe)
allow(subject).to receive(:context)
allow(subject).to receive(:metadata).and_return(api_metadata)
end
describe '#path(path)' do
before { subject.path('/blogs') }
it "delegates to 'describe' with 'path' metadata" do
expect(subject).to have_received(:describe).with(
'/blogs', path: '/blogs'
)
end
end
describe '#get|post|patch|put|delete|head(verb, summary)' do
before { subject.post('Creates a blog') }
it "delegates to 'describe' with 'operation' metadata" do
expect(subject).to have_received(:describe).with(
:post, operation: { verb: :post, summary: 'Creates a blog' }
)
end
end
describe '#tags|description|operationId|consumes|produces|schemes|deprecated(value)' do
let(:api_metadata) { { operation: {} } }
before do
subject.tags('Blogs', 'Admin')
subject.description('Some description')
subject.operationId('createBlog')
subject.consumes('application/json', 'application/xml')
subject.produces('application/json', 'application/xml')
subject.schemes('http', 'https')
subject.deprecated(true)
end
it "adds to the 'operation' metadata" do
expect(api_metadata[:operation]).to match(
tags: [ 'Blogs', 'Admin' ],
description: 'Some description',
operationId: 'createBlog',
consumes: [ 'application/json', 'application/xml' ],
produces: [ 'application/json', 'application/xml' ],
schemes: [ 'http', 'https' ],
deprecated: true
)
end
end
describe '#tags|description|operationId|consumes|produces|schemes|deprecated|security(value)' do
let(:api_metadata) { { operation: {} } }
before do
subject.tags('Blogs', 'Admin')
subject.description('Some description')
subject.operationId('createBlog')
subject.consumes('application/json', 'application/xml')
subject.produces('application/json', 'application/xml')
subject.schemes('http', 'https')
subject.deprecated(true)
subject.security(api_key: [])
end
it "adds to the 'operation' metadata" do
expect(api_metadata[:operation]).to match(
tags: [ 'Blogs', 'Admin' ],
description: 'Some description',
operationId: 'createBlog',
consumes: [ 'application/json', 'application/xml' ],
produces: [ 'application/json', 'application/xml' ],
schemes: [ 'http', 'https' ],
deprecated: true,
security: { api_key: [] }
)
end
end
describe '#parameter(attributes)' do
let(:api_metadata) { { operation: {} } }
context 'always' do
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
it "adds to the 'operation parameters' metadata" do
expect(api_metadata[:operation][:parameters]).to match(
[ name: :blog, in: :body, schema: { type: 'object' } ]
)
end
end
context "'path' parameter" do
before { subject.parameter(name: :id, in: :path) }
it "automatically sets the 'required' flag" do
expect(api_metadata[:operation][:parameters]).to match(
[ name: :id, in: :path, required: true ]
)
end
end
end
describe '#response(code, description)' do
before { subject.response('201', 'success') }
it "delegates to 'context' with 'response' metadata" do
expect(subject).to have_received(:context).with(
'success', response: { code: '201', description: 'success' }
)
end
end
describe '#schema(value)' do
let(:api_metadata) { { response: {} } }
before { subject.schema(type: 'object') }
it "adds to the 'response' metadata" do
expect(api_metadata[:response][:schema]).to match(type: 'object')
end
end
describe '#header(name, attributes)' do
let(:api_metadata) { { response: {} } }
before { subject.header('Date', type: 'string') }
it "adds to the 'response headers' metadata" do
expect(api_metadata[:response][:headers]).to match(
'Date' => { type: 'string' }
)
end
end
end
end
end

View File

@@ -0,0 +1,66 @@
require 'rswag/specs/example_helpers'
module Rswag
module Specs
describe ExampleHelpers do
let(:api_metadata) do
{
path: '/blogs/{blog_id}/comments/{id}',
operation: {
verb: :put,
summary: 'Updates a blog',
parameters: [
{ name: :blog_id, in: :path, type: 'integer' },
{ name: 'id', in: :path, type: 'integer' },
{ name: 'q1', in: :query, type: 'string' },
{ name: :blog, in: :body, schema: { type: 'object' } }
],
security: {
api_key: []
}
}
}
end
let(:global_metadata) do
{
securityDefinitions: {
api_key: {
type: :apiKey,
name: 'api_key',
in: :query
}
}
}
end
subject { double('example') }
before do
subject.extend ExampleHelpers
allow(subject).to receive(:blog_id).and_return(1)
allow(subject).to receive(:id).and_return(2)
allow(subject).to receive(:q1).and_return('foo')
allow(subject).to receive(:api_key).and_return('fookey')
allow(subject).to receive(:blog).and_return(text: 'Some comment')
allow(subject).to receive(:global_metadata).and_return(global_metadata)
allow(subject).to receive(:put)
end
describe '#submit_request(api_metadata)' do
before do
stub_const('Rails::VERSION::MAJOR', 3)
subject.submit_request(api_metadata)
end
it "submits a request built from metadata and 'let' values" do
expect(subject).to have_received(:put).with(
'/blogs/1/comments/2?q1=foo&api_key=fookey',
"{\"text\":\"Some comment\"}",
{}
)
end
end
end
end
end

View File

@@ -0,0 +1,171 @@
require 'rswag/specs/request_factory'
module Rswag
module Specs
describe RequestFactory do
let(:api_metadata) do
{
path: '/blogs/{blog_id}/comments/{id}',
operation: {
verb: :put,
summary: 'Updates a blog',
parameters: [
{ name: :blog_id, in: :path, type: 'integer' },
{ name: 'id', in: :path, type: 'integer' }
]
}
}
end
let(:global_metadata) { {} }
subject { RequestFactory.new(api_metadata, global_metadata) }
let(:example) { double('example') }
before do
allow(example).to receive(:blog_id).and_return(1)
allow(example).to receive(:id).and_return('2')
end
describe '#build_fullpath(example)' do
let(:path) { subject.build_fullpath(example) }
context 'always' do
it "builds a path using metadata and example values" do
expect(path).to eq('/blogs/1/comments/2')
end
end
context "'query' parameters" do
before do
api_metadata[:operation][:parameters] << { name: 'q1', in: :query, type: 'string' }
api_metadata[:operation][:parameters] << { name: 'q2', in: :query, type: 'string' }
allow(example).to receive(:q1).and_return('foo')
allow(example).to receive(:q2).and_return('bar')
end
it "appends a query string using metadata and example values" do
expect(path).to eq('/blogs/1/comments/2?q1=foo&q2=bar')
end
end
context "global definition for 'api_key in query'" do
before do
global_metadata[:securityDefinitions] = { api_key: { type: :apiKey, name: 'api_key', in: :query } }
allow(example).to receive(:api_key).and_return('fookey')
end
context 'global requirement' do
before { global_metadata[:security] = { api_key: [] } }
it "appends the api_key using metadata and example value" do
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
end
end
context 'operation-specific requirement' do
before { api_metadata[:operation][:security] = { api_key: [] } }
it "appends the api_key using metadata and example value" do
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
end
end
end
context 'global basePath' do
before { global_metadata[:basePath] = '/foobar' }
it 'prepends the basePath' do
expect(path).to eq('/foobar/blogs/1/comments/2')
end
end
end
describe '#build_body(example)' do
let(:body) { subject.build_body(example) }
context "no 'body' parameter" do
it "returns ''" do
expect(body).to eq('')
end
end
context "'body' parameter" do
before do
api_metadata[:operation][:parameters] << { name: 'comment', in: :body, schema: { type: 'object' } }
allow(example).to receive(:comment).and_return(text: 'Some comment')
end
it 'returns the example value as a json string' do
expect(body).to eq("{\"text\":\"Some comment\"}")
end
end
context "referenced 'body' parameter" do
before do
api_metadata[:operation][:parameters] << { '$ref' => '#/parameters/comment' }
global_metadata[:parameters] = {
'comment' => { name: 'comment', in: :body, schema: { type: 'object' } }
}
allow(example).to receive(:comment).and_return(text: 'Some comment')
end
it 'returns the example value as a json string' do
expect(body).to eq("{\"text\":\"Some comment\"}")
end
end
end
describe '#build_headers' do
let(:headers) { subject.build_headers(example) }
context "no 'header' params" do
it 'returns an empty hash' do
expect(headers).to eq({})
end
end
context "'header' params" do
before do
api_metadata[:operation][:parameters] << { name: 'Api-Key', in: :header, type: 'string' }
allow(example).to receive(:'Api-Key').and_return('foobar')
end
it 'returns a hash of names with example values' do
expect(headers).to eq({ 'Api-Key' => 'foobar' })
end
end
context 'consumes & produces' do
before do
api_metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
api_metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
end
it "includes corresponding 'Accept' & 'Content-Type' headers" do
expect(headers).to match(
'ACCEPT' => 'application/json;application/xml',
'CONTENT_TYPE' => 'application/json;application/xml'
)
end
end
context 'global consumes & produces' do
let(:global_metadata) do
{
consumes: [ 'application/json', 'application/xml' ],
produces: [ 'application/json', 'application/xml' ]
}
end
it "includes corresponding 'Accept' & 'Content-Type' headers" do
expect(headers).to match(
'ACCEPT' => 'application/json;application/xml',
'CONTENT_TYPE' => 'application/json;application/xml'
)
end
end
end
end
end
end

View File

@@ -0,0 +1,72 @@
require 'rswag/specs/response_validator'
module Rswag
module Specs
describe ResponseValidator do
let(:api_metadata) { { response: { code: 200 } } }
let(:global_metadata) { {} }
subject { ResponseValidator.new(api_metadata, global_metadata) }
describe '#validate!(response)' do
let(:call) { subject.validate!(response) }
context "no 'schema' provided" do
context 'response code matches' do
let(:response) { OpenStruct.new(code: 200, body: '') }
it { expect { call }.to_not raise_error }
end
context 'response code does not match' do
let(:response) { OpenStruct.new(code: 201, body: '') }
it { expect { call }.to raise_error UnexpectedResponse }
end
end
context "'schema' provided" do
before do
api_metadata[:response][:schema] = {
type: 'object',
properties: { text: { type: 'string' } },
required: [ 'text' ]
}
end
context 'response code & body matches' do
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some comment\"}") }
it { expect { call }.to_not raise_error }
end
context 'response code matches & body does not' do
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") }
it { expect { call }.to raise_error UnexpectedResponse }
end
end
context "referenced 'schema' provided" do
before do
api_metadata[:response][:schema] = { '$ref' => '#/definitions/author' }
global_metadata[:definitions] = {
author: {
type: 'object',
properties: { name: { type: 'string' } },
required: [ 'name' ]
}
}
end
context 'response code & body matches' do
let(:response) { OpenStruct.new(code: 200, body: "{\"name\":\"Some name\"}") }
it { expect { call }.to_not raise_error }
end
context 'response code matches & body does not' do
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some name\"}") }
it { expect { call }.to raise_error UnexpectedResponse }
end
end
end
end
end
end

View File

@@ -0,0 +1,125 @@
require 'rswag/specs/swagger_formatter'
require 'ostruct'
module Rswag
module Specs
describe SwaggerFormatter do
# Mock infrastructure - output, RSpec.configuration etc.
let(:output) { double('output').as_null_object }
let(:swagger_root) { File.expand_path('../tmp', __FILE__) }
let(:swagger_docs) do
{
'v1/swagger.json' => { info: { version: 'v1' } }
}
end
let(:config) { OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs) }
before { allow(RSpec).to receive(:configuration).and_return(config) }
subject { described_class.new(output) }
describe '::new(output)' do
context 'swagger_root not configured' do
let(:swagger_root) { nil }
it { expect { subject }.to raise_error ConfigurationError }
end
context 'swagger_docs not configured' do
let(:swagger_docs) { nil }
it { expect { subject }.to raise_error ConfigurationError }
end
end
describe '#example_group_finished(notification)' do
# Mock notification parameter
let(:api_metadata) do
{
path: '/blogs',
operation: { verb: :post, summary: 'Creates a blog' },
response: { code: '201', description: 'blog created' }
}
end
let(:notification) { OpenStruct.new(group: OpenStruct.new(metadata: api_metadata)) }
let(:call) { subject.example_group_finished(notification) }
context 'single swagger_doc' do
before { call }
it 'converts metadata to swagger and merges into the doc' do
expect(swagger_docs.values.first).to match(
info: { version: 'v1' },
paths: {
'/blogs' => {
post: {
summary: 'Creates a blog',
responses: {
'201' => { description: 'blog created' }
}
}
}
}
)
end
end
context 'multiple swagger_docs' do
let(:swagger_docs) do
{
'v1/swagger.json' => {},
'v2/swagger.json' => {}
}
end
context "no 'swagger_doc' tag" do
before { call }
it 'merges into the first doc' do
expect(swagger_docs.values.first).to have_key(:paths)
end
end
context "matching 'swagger_doc' tag" do
before do
api_metadata[:swagger_doc] = 'v2/swagger.json'
call
end
it 'merges into the matched doc' do
expect(swagger_docs.values.last).to have_key(:paths)
end
end
context "non matching 'swagger_doc' tag" do
before { api_metadata[:swagger_doc] = 'foobar' }
it { expect { call }.to raise_error ConfigurationError }
end
end
end
describe '#stop' do
let(:notification) { double('notification') }
let(:swagger_docs) do
{
'v1/swagger.json' => { info: { version: 'v1' } },
'v2/swagger.json' => { info: { version: 'v2' } },
}
end
before do
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
subject.stop(notification)
end
it 'writes the swagger_doc(s) to file' do
expect(File).to exist("#{swagger_root}/v1/swagger.json")
expect(File).to exist("#{swagger_root}/v2/swagger.json")
end
after do
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
end
end
end
end
end

View File

@@ -0,0 +1,7 @@
module Rails
module VERSION
MAJOR = 3
end
end
require 'rswag/specs'

View File

@@ -0,0 +1,2 @@
# NOTE: For the specs in this gem, all configuration is completely mocked out
# The file just needs to be present because it gets required by the swagger_formatter