mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-22 22:06:43 +00:00
Gets v3 request example saving as well as response example saving
Adds rubocop to the gemset adds guard to the gemset for testing
This commit is contained in:
parent
5d7fc44af4
commit
297cc447c8
19
Gemfile
19
Gemfile
@ -1,10 +1,12 @@
|
||||
source "https://rubygems.org"
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Allow the rails version to come from an ENV setting so Travis can test multiple versions.
|
||||
# See http://www.schneems.com/post/50991826838/testing-against-multiple-rails-versions/
|
||||
rails_version = ENV['RAILS_VERSION'] || '5.1.2'
|
||||
|
||||
gem 'rails', "#{rails_version}"
|
||||
gem 'rails', rails_version.to_s
|
||||
|
||||
case rails_version.split('.').first
|
||||
when '3'
|
||||
@ -19,17 +21,22 @@ gem 'rswag-api', path: './rswag-api'
|
||||
gem 'rswag-ui', path: './rswag-ui'
|
||||
|
||||
group :test do
|
||||
gem 'test-unit'
|
||||
gem 'rspec-rails'
|
||||
gem 'generator_spec'
|
||||
gem 'capybara'
|
||||
gem 'capybara-webkit'
|
||||
gem 'generator_spec'
|
||||
gem 'rspec-rails'
|
||||
gem 'rswag-specs', path: './rswag-specs'
|
||||
gem 'test-unit'
|
||||
end
|
||||
|
||||
group :development do
|
||||
gem 'guard-rspec', require: false
|
||||
gem 'rubocop'
|
||||
end
|
||||
|
||||
group :assets do
|
||||
gem 'uglifier'
|
||||
gem 'therubyracer'
|
||||
gem 'uglifier'
|
||||
end
|
||||
|
||||
gem 'byebug'
|
||||
|
||||
60
rswag-specs/Guardfile
Normal file
60
rswag-specs/Guardfile
Normal file
@ -0,0 +1,60 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# A sample Guardfile
|
||||
# More info at https://github.com/guard/guard#readme
|
||||
|
||||
## Uncomment and set this to only include directories you want to watch
|
||||
# directories %w(app lib config test spec features) \
|
||||
# .select{|d| Dir.exist?(d) ? d : UI.warning("Directory #{d} does not exist")}
|
||||
|
||||
## Note: if you are using the `directories` clause above and you are not
|
||||
## watching the project directory ('.'), then you will want to move
|
||||
## the Guardfile to a watched dir and symlink it back, e.g.
|
||||
#
|
||||
# $ mkdir config
|
||||
# $ mv Guardfile config/
|
||||
# $ ln -s config/Guardfile .
|
||||
#
|
||||
# and, you'll have to watch "config/Guardfile" instead of "Guardfile"
|
||||
|
||||
# Note: The cmd option is now required due to the increasing number of ways
|
||||
# rspec may be run, below are examples of the most common uses.
|
||||
# * bundler: 'bundle exec rspec'
|
||||
# * bundler binstubs: 'bin/rspec'
|
||||
# * spring: 'bin/rspec' (This will use spring if running and you have
|
||||
# installed the spring binstubs per the docs)
|
||||
# * zeus: 'zeus rspec' (requires the server to be started separately)
|
||||
# * 'just' rspec: 'rspec'
|
||||
|
||||
guard :rspec, cmd: 'bundle exec rspec' do
|
||||
require 'guard/rspec/dsl'
|
||||
dsl = Guard::RSpec::Dsl.new(self)
|
||||
|
||||
# Feel free to open issues for suggestions and improvements
|
||||
|
||||
# RSpec files
|
||||
rspec = dsl.rspec
|
||||
watch(rspec.spec_helper) { rspec.spec_dir }
|
||||
watch(rspec.spec_support) { rspec.spec_dir }
|
||||
watch(rspec.spec_files)
|
||||
|
||||
# Ruby files
|
||||
ruby = dsl.ruby
|
||||
dsl.watch_spec_files_for(ruby.lib_files)
|
||||
|
||||
# Rails files
|
||||
rails = dsl.rails(view_extensions: %w[erb haml slim])
|
||||
dsl.watch_spec_files_for(rails.app_files)
|
||||
dsl.watch_spec_files_for(rails.views)
|
||||
|
||||
watch(rails.controllers) do |m|
|
||||
[
|
||||
rspec.spec.call("routing/#{m[1]}_routing"),
|
||||
rspec.spec.call("controllers/#{m[1]}_controller"),
|
||||
rspec.spec.call("acceptance/#{m[1]}")
|
||||
]
|
||||
end
|
||||
|
||||
# Rails config changes
|
||||
watch(rails.spec_helper) { rspec.spec_dir }
|
||||
end
|
||||
@ -1,8 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
class Configuration
|
||||
|
||||
def initialize(rspec_config)
|
||||
@rspec_config = rspec_config
|
||||
end
|
||||
@ -12,6 +12,7 @@ module Rswag
|
||||
if @rspec_config.swagger_root.nil?
|
||||
raise ConfigurationError, 'No swagger_root provided. See swagger_helper.rb'
|
||||
end
|
||||
|
||||
@rspec_config.swagger_root
|
||||
end
|
||||
end
|
||||
@ -21,6 +22,7 @@ module Rswag
|
||||
if @rspec_config.swagger_docs.nil? || @rspec_config.swagger_docs.empty?
|
||||
raise ConfigurationError, 'No swagger_docs defined. See swagger_helper.rb'
|
||||
end
|
||||
|
||||
@rspec_config.swagger_docs
|
||||
end
|
||||
end
|
||||
@ -34,6 +36,7 @@ module Rswag
|
||||
def get_swagger_doc(name)
|
||||
return swagger_docs.values.first if name.nil?
|
||||
raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name]
|
||||
|
||||
swagger_docs[name]
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,20 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
module ExampleGroupHelpers
|
||||
|
||||
def path(template, metadata={}, &block)
|
||||
def path(template, metadata = {}, &block)
|
||||
metadata[:path_item] = { template: template }
|
||||
describe(template, metadata, &block)
|
||||
end
|
||||
|
||||
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
|
||||
%i[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|
|
||||
%i[operationId deprecated security].each do |attr_name|
|
||||
define_method(attr_name) do |value|
|
||||
metadata[:operation][attr_name] = value
|
||||
end
|
||||
@ -23,32 +24,61 @@ module Rswag
|
||||
# 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)
|
||||
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|
|
||||
%i[tags consumes produces schemes].each do |attr_name|
|
||||
define_method(attr_name) do |*value|
|
||||
metadata[:operation][attr_name] = value
|
||||
end
|
||||
end
|
||||
|
||||
#TODO: look at adding request_body method to handle diffs in Open API 2.0 to 3.0
|
||||
# NICE TO HAVE
|
||||
# TODO: update generator templates to include 3.0 syntax
|
||||
# TODO: setup travis CI?
|
||||
|
||||
# MUST HAVES
|
||||
# TODO: look at adding request_body method to handle diffs in Open API 2.0 to 3.0
|
||||
# TODO: look at adding examples in content request_body
|
||||
# https://swagger.io/docs/specification/describing-request-body/
|
||||
# need to make sure we output requestBody in the swagger generator .json
|
||||
# also need to make sure that it can handle content: , required: true/false, schema: ref
|
||||
|
||||
def request_body(attributes)
|
||||
# can make this generic, and accept any incoming hash (like parameter method)
|
||||
attributes.compact!
|
||||
metadata[:operation][:requestBody] = attributes
|
||||
end
|
||||
|
||||
|
||||
def request_body_json(schema:, required: true, description: nil, examples: nil)
|
||||
passed_examples = Array(examples)
|
||||
content_hash = { 'application/json' => { schema: schema, examples: examples }.compact! || {} }
|
||||
request_body(description: description, required: required, content: content_hash)
|
||||
if passed_examples.any?
|
||||
# the request_factory is going to have to resolve the different ways that the example can be given
|
||||
# it can contain a 'value' key which is a direct hash (easiest)
|
||||
# it can contain a 'external_value' key which makes an external call to load the json
|
||||
# it can contain a '$ref' key. Which points to #/components/examples/blog
|
||||
if passed_examples.first.is_a?(Symbol)
|
||||
example_key_name = passed_examples.first # can come up with better scheme here
|
||||
# TODO: write more tests around this adding to the parameter
|
||||
# if symbol try and use save_request_example
|
||||
param_attributes = { name: example_key_name, in: :body, required: required, param_value: example_key_name }
|
||||
parameter(param_attributes)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def parameter(attributes)
|
||||
if attributes[:in] && attributes[:in].to_sym == :path
|
||||
attributes[:required] = true
|
||||
end
|
||||
|
||||
if metadata.has_key?(:operation)
|
||||
if metadata.key?(:operation)
|
||||
metadata[:operation][:parameters] ||= []
|
||||
metadata[:operation][:parameters] << attributes
|
||||
else
|
||||
@ -57,7 +87,7 @@ module Rswag
|
||||
end
|
||||
end
|
||||
|
||||
def response(code, description, metadata={}, &block)
|
||||
def response(code, description, metadata = {}, &block)
|
||||
metadata[:response] = { code: code, description: description }
|
||||
context(description, metadata, &block)
|
||||
end
|
||||
@ -76,6 +106,7 @@ module Rswag
|
||||
# rspec-core ExampleGroup
|
||||
def examples(example = nil)
|
||||
return super() if example.nil?
|
||||
|
||||
metadata[:response][:examples] = example
|
||||
end
|
||||
|
||||
@ -99,6 +130,21 @@ module Rswag
|
||||
assert_response_matches_metadata(example.metadata, &block)
|
||||
example.instance_exec(response, &block) if block_given?
|
||||
end
|
||||
|
||||
after do |example|
|
||||
body_parameter = example.metadata[:operation]&.dig(:parameters)&.detect { |p| p[:in] == :body && p[:required] }
|
||||
|
||||
if body_parameter && respond_to?(body_parameter[:name]) && example.metadata[:operation][:requestBody][:content]['application/json']
|
||||
# save response examples by default
|
||||
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) } unless response.body.to_s.empty?
|
||||
|
||||
# save request examples using the let(:param_name) { REQUEST_BODY_HASH } syntax in the test
|
||||
example.metadata[:operation][:requestBody][:content]['application/json'] = { examples: {} } unless example.metadata[:operation][:requestBody][:content]['application/json'][:examples]
|
||||
json_request_examples = example.metadata[:operation][:requestBody][:content]['application/json'][:examples]
|
||||
json_request_examples[body_parameter[:name]] = { value: send(body_parameter[:name]) }
|
||||
example.metadata[:operation][:requestBody][:content]['application/json'][:examples] = json_request_examples
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/request_factory'
|
||||
require 'rswag/specs/response_validator'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
module ExampleHelpers
|
||||
|
||||
def submit_request(metadata)
|
||||
request = RequestFactory.new.build_request(metadata, self)
|
||||
|
||||
@ -19,10 +20,8 @@ module Rswag
|
||||
send(
|
||||
request[:verb],
|
||||
request[:path],
|
||||
{
|
||||
params: request[:payload],
|
||||
headers: request[:headers]
|
||||
}
|
||||
params: request[:payload],
|
||||
headers: request[:headers]
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'json-schema'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class ExtendedSchema < JSON::Schema::Draft4
|
||||
|
||||
def initialize
|
||||
super
|
||||
@attributes['type'] = ExtendedTypeAttribute
|
||||
@ -13,9 +14,9 @@ module Rswag
|
||||
end
|
||||
|
||||
class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute
|
||||
|
||||
def self.validate(current_schema, data, fragments, processor, validator, options={})
|
||||
def self.validate(current_schema, data, fragments, processor, validator, options = {})
|
||||
return if data.nil? && (current_schema.schema['nullable'] == true || current_schema.schema['x-nullable'] == true)
|
||||
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class Railtie < ::Rails::Railtie
|
||||
|
||||
rake_tasks do
|
||||
load File.expand_path('../../../tasks/rswag-specs_tasks.rake', __FILE__)
|
||||
load File.expand_path('../../tasks/rswag-specs_tasks.rake', __dir__)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'active_support/core_ext/hash/conversions'
|
||||
require 'json'
|
||||
@ -5,7 +7,6 @@ require 'json'
|
||||
module Rswag
|
||||
module Specs
|
||||
class RequestFactory
|
||||
|
||||
def initialize(config = ::Rswag::Specs.config)
|
||||
@config = config
|
||||
end
|
||||
@ -38,11 +39,11 @@ module Rswag
|
||||
|
||||
def derive_security_params(metadata, swagger_doc)
|
||||
requirements = metadata[:operation][:security] || swagger_doc[:security] || []
|
||||
scheme_names = requirements.flat_map { |r| r.keys }
|
||||
scheme_names = requirements.flat_map(&:keys)
|
||||
schemes = (swagger_doc[:components][:securitySchemes] || {}).slice(*scheme_names).values
|
||||
|
||||
schemes.map do |scheme|
|
||||
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
||||
param = scheme[:type] == :apiKey ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
||||
param.merge(type: :string, required: requirements.one?)
|
||||
end
|
||||
end
|
||||
@ -51,10 +52,11 @@ module Rswag
|
||||
key = ref.sub('#/parameters/', '').to_sym
|
||||
definitions = swagger_doc[:parameters]
|
||||
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
|
||||
|
||||
definitions[key]
|
||||
end
|
||||
|
||||
def add_verb(request, metadata)
|
||||
def add_verb(request, metadata)
|
||||
request[:verb] = metadata[:operation][:verb]
|
||||
end
|
||||
|
||||
@ -75,7 +77,7 @@ module Rswag
|
||||
|
||||
def build_query_string_part(param, value)
|
||||
name = param[:name]
|
||||
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
|
||||
return "#{name}=#{value}" unless param[:type].to_sym == :array
|
||||
|
||||
case param[:collectionFormat]
|
||||
when :ssv
|
||||
@ -93,44 +95,44 @@ module Rswag
|
||||
|
||||
def add_headers(request, metadata, swagger_doc, parameters, example)
|
||||
tuples = parameters
|
||||
.select { |p| p[:in] == :header }
|
||||
.map { |p| [ p[:name], example.send(p[:name]).to_s ] }
|
||||
.select { |p| p[:in] == :header }
|
||||
.map { |p| [p[:name], example.send(p[:name]).to_s] }
|
||||
|
||||
# Accept header
|
||||
produces = metadata[:operation][:produces] || swagger_doc[:produces]
|
||||
if produces
|
||||
accept = example.respond_to?(:'Accept') ? example.send(:'Accept') : produces.first
|
||||
tuples << [ 'Accept', accept ]
|
||||
accept = example.respond_to?(:Accept) ? example.send(:Accept) : produces.first
|
||||
tuples << ['Accept', accept]
|
||||
end
|
||||
|
||||
# Content-Type header
|
||||
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
|
||||
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
|
||||
if consumes
|
||||
content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first
|
||||
tuples << [ 'Content-Type', content_type ]
|
||||
tuples << ['Content-Type', content_type]
|
||||
end
|
||||
|
||||
# Rails test infrastructure requires rackified headers
|
||||
rackified_tuples = tuples.map do |pair|
|
||||
[
|
||||
case pair[0]
|
||||
when 'Accept' then 'HTTP_ACCEPT'
|
||||
when 'Content-Type' then 'CONTENT_TYPE'
|
||||
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
||||
else pair[0]
|
||||
when 'Accept' then 'HTTP_ACCEPT'
|
||||
when 'Content-Type' then 'CONTENT_TYPE'
|
||||
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
||||
else pair[0]
|
||||
end,
|
||||
pair[1]
|
||||
]
|
||||
end
|
||||
|
||||
request[:headers] = Hash[ rackified_tuples ]
|
||||
request[:headers] = Hash[rackified_tuples]
|
||||
end
|
||||
|
||||
def add_payload(request, parameters, example)
|
||||
content_type = request[:headers]['CONTENT_TYPE']
|
||||
return if content_type.nil?
|
||||
|
||||
if [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].include?(content_type)
|
||||
if ['application/x-www-form-urlencoded', 'multipart/form-data'].include?(content_type)
|
||||
request[:payload] = build_form_payload(parameters, example)
|
||||
else
|
||||
request[:payload] = build_json_payload(parameters, example)
|
||||
@ -143,14 +145,21 @@ module Rswag
|
||||
# Rails test infrastructure allows us to send the values directly as a hash
|
||||
# PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
|
||||
tuples = parameters
|
||||
.select { |p| p[:in] == :formData }
|
||||
.map { |p| [ p[:name], example.send(p[:name]) ] }
|
||||
Hash[ tuples ]
|
||||
.select { |p| p[:in] == :formData }
|
||||
.map { |p| [p[:name], example.send(p[:name])] }
|
||||
Hash[tuples]
|
||||
end
|
||||
|
||||
def build_json_payload(parameters, example)
|
||||
body_param = parameters.select { |p| p[:in] == :body }.first
|
||||
body_param ? example.send(body_param[:name]).to_json : nil
|
||||
return nil unless body_param
|
||||
|
||||
# p "example is #{example.send(body_param[:name]).to_json} ** AND body param is #{body_param}" if body_param
|
||||
# body_param ? example.send(body_param[:name]).to_json : nil
|
||||
|
||||
source_body_param = example.send(body_param[:name]) if body_param[:name] && example.respond_to?(body_param[:name])
|
||||
source_body_param ||= body_param[:param_value]
|
||||
source_body_param ? source_body_param.to_json : nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'json-schema'
|
||||
require 'json'
|
||||
@ -6,7 +8,6 @@ require 'rswag/specs/extended_schema'
|
||||
module Rswag
|
||||
module Specs
|
||||
class ResponseValidator
|
||||
|
||||
def initialize(config = ::Rswag::Specs.config)
|
||||
@config = config
|
||||
end
|
||||
@ -41,12 +42,12 @@ module Rswag
|
||||
response_schema = metadata[:response][:schema]
|
||||
return if response_schema.nil?
|
||||
|
||||
components_schemas = {components: {schemas: swagger_doc[:components][:schemas]}}
|
||||
components_schemas = { components: { schemas: swagger_doc[:components][:schemas] } }
|
||||
|
||||
validation_schema = response_schema
|
||||
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
||||
.merge(components_schemas)
|
||||
|
||||
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
||||
.merge(components_schemas)
|
||||
|
||||
errors = JSON::Validator.fully_validate(validation_schema, body)
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
|
||||
end
|
||||
|
||||
@ -1,10 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'active_support/core_ext/hash/deep_merge'
|
||||
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
|
||||
@ -19,22 +20,23 @@ module Rswag
|
||||
|
||||
def example_group_finished(notification)
|
||||
# NOTE: rspec 2.x support
|
||||
if RSPEC_VERSION > 2
|
||||
metadata = notification.group.metadata
|
||||
else
|
||||
metadata = notification.metadata
|
||||
end
|
||||
metadata = if RSPEC_VERSION > 2
|
||||
notification.group.metadata
|
||||
else
|
||||
notification.metadata
|
||||
end
|
||||
|
||||
return unless metadata.key?(:response)
|
||||
|
||||
return unless metadata.has_key?(:response)
|
||||
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||
swagger_doc.deep_merge!(metadata_to_swagger(metadata))
|
||||
end
|
||||
|
||||
def stop(notification=nil)
|
||||
def stop(_notification = nil)
|
||||
@config.swagger_docs.each do |url_path, doc|
|
||||
file_path = File.join(@config.swagger_root, url_path)
|
||||
dirname = File.dirname(file_path)
|
||||
FileUtils.mkdir_p dirname unless File.exists?(dirname)
|
||||
FileUtils.mkdir_p dirname unless File.exist?(dirname)
|
||||
|
||||
File.open(file_path, 'w') do |file|
|
||||
file.write(JSON.pretty_generate(doc))
|
||||
@ -48,17 +50,31 @@ module Rswag
|
||||
|
||||
def metadata_to_swagger(metadata)
|
||||
response_code = metadata[:response][:code]
|
||||
response = metadata[:response].reject { |k,v| k == :code }
|
||||
response = metadata[:response].reject { |k, _v| k == :code }
|
||||
|
||||
if response_code.to_s == '201'
|
||||
# need to merge in to resppnse
|
||||
if response[:examples]&.dig('application/json')
|
||||
example = response[:examples].dig('application/json').dup
|
||||
response.merge!(content: { 'application/json' => { example: example } })
|
||||
response.delete(:examples)
|
||||
end
|
||||
end
|
||||
|
||||
verb = metadata[:operation][:verb]
|
||||
operation = metadata[:operation]
|
||||
.reject { |k,v| k == :verb }
|
||||
.merge(responses: { response_code => response })
|
||||
.reject { |k, _v| k == :verb }
|
||||
.merge(responses: { response_code => response })
|
||||
|
||||
# can remove the 2.0 compliant body incoming parameters
|
||||
if operation&.dig(:parameters)
|
||||
operation[:parameters].reject! { |p| p[:in] == :body }
|
||||
end
|
||||
|
||||
path_template = metadata[:path_item][:template]
|
||||
path_item = metadata[:path_item]
|
||||
.reject { |k,v| k == :template }
|
||||
.merge(verb => operation)
|
||||
.reject { |k, _v| k == :template }
|
||||
.merge(verb => operation)
|
||||
|
||||
{ paths: { path_template => path_item } }
|
||||
end
|
||||
|
||||
@ -1,19 +1,22 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
# frozen_string_literal: true
|
||||
|
||||
$LOAD_PATH.push File.expand_path('lib', __dir__)
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag-specs"
|
||||
s.name = 'rswag-specs'
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
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.description = "Simplify API integration testing with a succinct rspec DSL and generate Swagger files directly from your rspecs"
|
||||
s.license = "MIT"
|
||||
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.description = 'Simplify API integration testing with a succinct rspec DSL and generate Swagger files directly from your rspecs'
|
||||
s.license = 'MIT'
|
||||
|
||||
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
s.files = Dir['{lib}/**/*'] + %w[MIT-LICENSE Rakefile]
|
||||
|
||||
s.add_dependency 'activesupport', '>= 3.1', '< 6.0'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||
s.add_dependency 'json-schema', '~> 2.2'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||
s.add_development_dependency 'guard-rspec'
|
||||
end
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/configuration'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe Configuration do
|
||||
subject { described_class.new(rspec_config) }
|
||||
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/example_group_helpers'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe ExampleGroupHelpers do
|
||||
subject { double('example_group') }
|
||||
|
||||
@ -48,12 +49,12 @@ module Rswag
|
||||
|
||||
it "adds to the 'operation' metadata" do
|
||||
expect(api_metadata[:operation]).to match(
|
||||
tags: [ 'Blogs', 'Admin' ],
|
||||
tags: %w[Blogs Admin],
|
||||
description: 'Some description',
|
||||
operationId: 'createBlog',
|
||||
consumes: [ 'application/json', 'application/xml' ],
|
||||
produces: [ 'application/json', 'application/xml' ],
|
||||
schemes: [ 'http', 'https' ],
|
||||
consumes: ['application/json', 'application/xml'],
|
||||
produces: ['application/json', 'application/xml'],
|
||||
schemes: %w[http https],
|
||||
deprecated: true
|
||||
)
|
||||
end
|
||||
@ -74,27 +75,59 @@ module Rswag
|
||||
|
||||
it "adds to the 'operation' metadata" do
|
||||
expect(api_metadata[:operation]).to match(
|
||||
tags: [ 'Blogs', 'Admin' ],
|
||||
tags: %w[Blogs Admin],
|
||||
description: 'Some description',
|
||||
operationId: 'createBlog',
|
||||
consumes: [ 'application/json', 'application/xml' ],
|
||||
produces: [ 'application/json', 'application/xml' ],
|
||||
schemes: [ 'http', 'https' ],
|
||||
consumes: ['application/json', 'application/xml'],
|
||||
produces: ['application/json', 'application/xml'],
|
||||
schemes: %w[http https],
|
||||
deprecated: true,
|
||||
security: { api_key: [] }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parameter(attributes)' do
|
||||
describe '#request_body_json(schema)' do
|
||||
let(:api_metadata) { { path_item: {}, operation: {} } } # i.e. operation defined
|
||||
context 'when required is not supplied' do
|
||||
before { subject.request_body_json(schema: { type: 'object' }) }
|
||||
|
||||
it 'adds required true by default' do
|
||||
expect(api_metadata[:operation][:requestBody]).to match(
|
||||
required: true, content: { 'application/json' => { schema: { type: 'object' } } }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when required is supplied' do
|
||||
before { subject.request_body_json(schema: { type: 'object' }, required: false) }
|
||||
|
||||
it 'adds required false' do
|
||||
expect(api_metadata[:operation][:requestBody]).to match(
|
||||
required: false, content: { 'application/json' => { schema: { type: 'object' } } }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when required is supplied' do
|
||||
before { subject.request_body_json(schema: { type: 'object' }, description: 'my description') }
|
||||
|
||||
it 'adds description' do
|
||||
expect(api_metadata[:operation][:requestBody]).to match(
|
||||
description: 'my description', required: true, content: { 'application/json' => { schema: { type: 'object' } } }
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#parameter(attributes)' do
|
||||
context "when called at the 'path' level" do
|
||||
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
|
||||
let(:api_metadata) { { path_item: {} } } # i.e. operation not defined yet
|
||||
|
||||
it "adds to the 'path_item parameters' metadata" do
|
||||
expect(api_metadata[:path_item][:parameters]).to match(
|
||||
[ name: :blog, in: :body, schema: { type: 'object' } ]
|
||||
[name: :blog, in: :body, schema: { type: 'object' }]
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -105,7 +138,7 @@ module Rswag
|
||||
|
||||
it "adds to the 'operation parameters' metadata" do
|
||||
expect(api_metadata[:operation][:parameters]).to match(
|
||||
[ name: :blog, in: :body, schema: { type: 'object' } ]
|
||||
[name: :blog, in: :body, schema: { type: 'object' }]
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -116,7 +149,7 @@ module Rswag
|
||||
|
||||
it "automatically sets the 'required' flag" do
|
||||
expect(api_metadata[:operation][:parameters]).to match(
|
||||
[ name: :id, in: :path, required: true ]
|
||||
[name: :id, in: :path, required: true]
|
||||
)
|
||||
end
|
||||
end
|
||||
@ -126,7 +159,7 @@ module Rswag
|
||||
let(:api_metadata) { { operation: {} } }
|
||||
|
||||
it "does not require the 'in' parameter key" do
|
||||
expect(api_metadata[:operation][:parameters]).to match([ name: :id ])
|
||||
expect(api_metadata[:operation][:parameters]).to match([name: :id])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/example_helpers'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe ExampleHelpers do
|
||||
subject { double('example') }
|
||||
|
||||
@ -12,7 +13,7 @@ module Rswag
|
||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||
stub_const('Rswag::Specs::RAILS_VERSION', 3)
|
||||
end
|
||||
let(:config) { double('config') }
|
||||
let(:config) { double('config') }
|
||||
let(:swagger_doc) do
|
||||
{
|
||||
securityDefinitions: {
|
||||
@ -30,7 +31,7 @@ module Rswag
|
||||
operation: {
|
||||
verb: :put,
|
||||
summary: 'Updates a blog',
|
||||
consumes: [ 'application/json' ],
|
||||
consumes: ['application/json'],
|
||||
parameters: [
|
||||
{ name: :blog_id, in: :path, type: 'integer' },
|
||||
{ name: 'id', in: :path, type: 'integer' },
|
||||
@ -58,8 +59,8 @@ module Rswag
|
||||
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\"}",
|
||||
{ 'CONTENT_TYPE' => 'application/json' }
|
||||
'{"text":"Some comment"}',
|
||||
'CONTENT_TYPE' => 'application/json'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/request_factory'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe RequestFactory do
|
||||
subject { RequestFactory.new(config) }
|
||||
|
||||
before do
|
||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||
end
|
||||
let(:config) { double('config') }
|
||||
let(:config) { double('config') }
|
||||
let(:swagger_doc) { {} }
|
||||
let(:example) { double('example') }
|
||||
let(:metadata) do
|
||||
@ -53,7 +54,7 @@ module Rswag
|
||||
allow(example).to receive(:q2).and_return('bar')
|
||||
end
|
||||
|
||||
it "builds the query string from example values" do
|
||||
it 'builds the query string from example values' do
|
||||
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||
end
|
||||
end
|
||||
@ -63,40 +64,40 @@ module Rswag
|
||||
metadata[:operation][:parameters] = [
|
||||
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format }
|
||||
]
|
||||
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
|
||||
allow(example).to receive(:things).and_return(%w[foo bar])
|
||||
end
|
||||
|
||||
context 'collectionFormat = csv' do
|
||||
let(:collection_format) { :csv }
|
||||
it "formats as comma separated values" do
|
||||
it 'formats as comma separated values' do
|
||||
expect(request[:path]).to eq('/blogs?things=foo,bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'collectionFormat = ssv' do
|
||||
let(:collection_format) { :ssv }
|
||||
it "formats as space separated values" do
|
||||
it 'formats as space separated values' do
|
||||
expect(request[:path]).to eq('/blogs?things=foo bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'collectionFormat = tsv' do
|
||||
let(:collection_format) { :tsv }
|
||||
it "formats as tab separated values" do
|
||||
it 'formats as tab separated values' do
|
||||
expect(request[:path]).to eq('/blogs?things=foo\tbar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'collectionFormat = pipes' do
|
||||
let(:collection_format) { :pipes }
|
||||
it "formats as pipe separated values" do
|
||||
it 'formats as pipe separated values' do
|
||||
expect(request[:path]).to eq('/blogs?things=foo|bar')
|
||||
end
|
||||
end
|
||||
|
||||
context 'collectionFormat = multi' do
|
||||
let(:collection_format) { :multi }
|
||||
it "formats as multiple parameter instances" do
|
||||
it 'formats as multiple parameter instances' do
|
||||
expect(request[:path]).to eq('/blogs?things=foo&things=bar')
|
||||
end
|
||||
end
|
||||
@ -104,12 +105,12 @@ module Rswag
|
||||
|
||||
context "'header' parameters" do
|
||||
before do
|
||||
metadata[:operation][:parameters] = [ { name: 'Api-Key', in: :header, type: :string } ]
|
||||
metadata[:operation][:parameters] = [{ name: 'Api-Key', in: :header, type: :string }]
|
||||
allow(example).to receive(:'Api-Key').and_return('foobar')
|
||||
end
|
||||
|
||||
it 'adds names and example values to headers' do
|
||||
expect(request[:headers]).to eq({ 'Api-Key' => 'foobar' })
|
||||
expect(request[:headers]).to eq('Api-Key' => 'foobar')
|
||||
end
|
||||
end
|
||||
|
||||
@ -127,9 +128,9 @@ module Rswag
|
||||
end
|
||||
end
|
||||
|
||||
context "consumes content" do
|
||||
context 'consumes content' do
|
||||
before do
|
||||
metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
|
||||
metadata[:operation][:consumes] = ['application/json', 'application/xml']
|
||||
end
|
||||
|
||||
context "no 'Content-Type' provided" do
|
||||
@ -150,18 +151,18 @@ module Rswag
|
||||
|
||||
context 'JSON payload' do
|
||||
before do
|
||||
metadata[:operation][:parameters] = [ { name: 'comment', in: :body, schema: { type: 'object' } } ]
|
||||
metadata[:operation][:parameters] = [{ name: 'comment', in: :body, schema: { type: 'object' } }]
|
||||
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
||||
end
|
||||
|
||||
it "serializes first 'body' parameter to JSON string" do
|
||||
expect(request[:payload]).to eq("{\"text\":\"Some comment\"}")
|
||||
expect(request[:payload]).to eq('{"text":"Some comment"}')
|
||||
end
|
||||
end
|
||||
|
||||
context 'form payload' do
|
||||
before do
|
||||
metadata[:operation][:consumes] = [ 'multipart/form-data' ]
|
||||
metadata[:operation][:consumes] = ['multipart/form-data']
|
||||
metadata[:operation][:parameters] = [
|
||||
{ name: 'f1', in: :formData, type: :string },
|
||||
{ name: 'f2', in: :formData, type: :string }
|
||||
@ -181,7 +182,7 @@ module Rswag
|
||||
|
||||
context 'produces content' do
|
||||
before do
|
||||
metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
|
||||
metadata[:operation][:produces] = ['application/json', 'application/xml']
|
||||
end
|
||||
|
||||
context "no 'Accept' value provided" do
|
||||
@ -192,7 +193,7 @@ module Rswag
|
||||
|
||||
context "explicit 'Accept' value provided" do
|
||||
before do
|
||||
allow(example).to receive(:'Accept').and_return('application/xml')
|
||||
allow(example).to receive(:Accept).and_return('application/xml')
|
||||
end
|
||||
|
||||
it "sets 'HTTP_ACCEPT' header to example value" do
|
||||
@ -204,7 +205,7 @@ module Rswag
|
||||
context 'basic auth' do
|
||||
before do
|
||||
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
|
||||
metadata[:operation][:security] = [ basic: [] ]
|
||||
metadata[:operation][:security] = [basic: []]
|
||||
allow(example).to receive(:Authorization).and_return('Basic foobar')
|
||||
end
|
||||
|
||||
@ -216,7 +217,7 @@ module Rswag
|
||||
context 'apiKey' do
|
||||
before do
|
||||
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: key_location } }
|
||||
metadata[:operation][:security] = [ apiKey: [] ]
|
||||
metadata[:operation][:security] = [apiKey: []]
|
||||
allow(example).to receive(:api_key).and_return('foobar')
|
||||
end
|
||||
|
||||
@ -256,8 +257,8 @@ module Rswag
|
||||
|
||||
context 'oauth2' do
|
||||
before do
|
||||
swagger_doc[:securityDefinitions] = { oauth2: { type: :oauth2, scopes: [ 'read:blogs' ] } }
|
||||
metadata[:operation][:security] = [ oauth2: [ 'read:blogs' ] ]
|
||||
swagger_doc[:securityDefinitions] = { oauth2: { type: :oauth2, scopes: ['read:blogs'] } }
|
||||
metadata[:operation][:security] = [oauth2: ['read:blogs']]
|
||||
allow(example).to receive(:Authorization).and_return('Bearer foobar')
|
||||
end
|
||||
|
||||
@ -272,26 +273,26 @@ module Rswag
|
||||
basic: { type: :basic },
|
||||
api_key: { type: :apiKey, name: 'api_key', in: :query }
|
||||
}
|
||||
metadata[:operation][:security] = [ { basic: [], api_key: [] } ]
|
||||
metadata[:operation][:security] = [{ basic: [], api_key: [] }]
|
||||
allow(example).to receive(:Authorization).and_return('Basic foobar')
|
||||
allow(example).to receive(:api_key).and_return('foobar')
|
||||
end
|
||||
|
||||
it "sets both params to example values" do
|
||||
it 'sets both params to example values' do
|
||||
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
|
||||
expect(request[:path]).to eq('/blogs?api_key=foobar')
|
||||
end
|
||||
end
|
||||
|
||||
context "path-level parameters" do
|
||||
context 'path-level parameters' do
|
||||
before do
|
||||
metadata[:operation][:parameters] = [ { name: 'q1', in: :query, type: :string } ]
|
||||
metadata[:path_item][:parameters] = [ { name: 'q2', in: :query, type: :string } ]
|
||||
metadata[:operation][:parameters] = [{ name: 'q1', in: :query, type: :string }]
|
||||
metadata[:path_item][: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 "populates operation and path level parameters " do
|
||||
it 'populates operation and path level parameters ' do
|
||||
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||
end
|
||||
end
|
||||
@ -299,7 +300,7 @@ module Rswag
|
||||
context 'referenced parameters' do
|
||||
before do
|
||||
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
|
||||
metadata[:operation][:parameters] = [ { '$ref' => '#/parameters/q1' } ]
|
||||
metadata[:operation][:parameters] = [{ '$ref' => '#/parameters/q1' }]
|
||||
allow(example).to receive(:q1).and_return('foo')
|
||||
end
|
||||
|
||||
@ -316,18 +317,18 @@ module Rswag
|
||||
end
|
||||
end
|
||||
|
||||
context "global consumes" do
|
||||
before { swagger_doc[:consumes] = [ 'application/xml' ] }
|
||||
context 'global consumes' do
|
||||
before { swagger_doc[:consumes] = ['application/xml'] }
|
||||
|
||||
it "defaults 'CONTENT_TYPE' to global value(s)" do
|
||||
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml')
|
||||
end
|
||||
end
|
||||
|
||||
context "global security requirements" do
|
||||
context 'global security requirements' do
|
||||
before do
|
||||
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: :query } }
|
||||
swagger_doc[:security] = [ apiKey: [] ]
|
||||
swagger_doc[:security] = [apiKey: []]
|
||||
allow(example).to receive(:api_key).and_return('foobar')
|
||||
end
|
||||
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/response_validator'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe ResponseValidator do
|
||||
subject { ResponseValidator.new(config) }
|
||||
|
||||
before do
|
||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||
end
|
||||
let(:config) { double('config') }
|
||||
let(:config) { double('config') }
|
||||
let(:swagger_doc) { {} }
|
||||
let(:example) { double('example') }
|
||||
let(:metadata) do
|
||||
@ -20,7 +21,7 @@ module Rswag
|
||||
schema: {
|
||||
type: :object,
|
||||
properties: { text: { type: :string } },
|
||||
required: [ 'text' ]
|
||||
required: ['text']
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -32,26 +33,26 @@ module Rswag
|
||||
OpenStruct.new(
|
||||
code: '200',
|
||||
headers: { 'X-Rate-Limit-Limit' => '10' },
|
||||
body: "{\"text\":\"Some comment\"}"
|
||||
body: '{"text":"Some comment"}'
|
||||
)
|
||||
end
|
||||
|
||||
context "response matches metadata" do
|
||||
context 'response matches metadata' do
|
||||
it { expect { call }.to_not raise_error }
|
||||
end
|
||||
|
||||
context "response code differs from metadata" do
|
||||
context 'response code differs from metadata' do
|
||||
before { response.code = '400' }
|
||||
it { expect { call }.to raise_error /Expected response code/ }
|
||||
end
|
||||
|
||||
context "response headers differ from metadata" do
|
||||
context 'response headers differ from metadata' do
|
||||
before { response.headers = {} }
|
||||
it { expect { call }.to raise_error /Expected response header/ }
|
||||
end
|
||||
|
||||
context "response body differs from metadata" do
|
||||
before { response.body = "{\"foo\":\"Some comment\"}" }
|
||||
context 'response body differs from metadata' do
|
||||
before { response.body = '{"foo":"Some comment"}' }
|
||||
it { expect { call }.to raise_error /Expected response body/ }
|
||||
end
|
||||
|
||||
@ -61,7 +62,7 @@ module Rswag
|
||||
'blog' => {
|
||||
type: :object,
|
||||
properties: { foo: { type: :string } },
|
||||
required: [ 'foo' ]
|
||||
required: ['foo']
|
||||
}
|
||||
}
|
||||
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rswag/specs/swagger_formatter'
|
||||
require 'ostruct'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
|
||||
describe SwaggerFormatter do
|
||||
subject { described_class.new(output, config) }
|
||||
|
||||
@ -13,7 +14,7 @@ module Rswag
|
||||
end
|
||||
let(:config) { double('config') }
|
||||
let(:output) { double('output').as_null_object }
|
||||
let(:swagger_root) { File.expand_path('../tmp/swagger', __FILE__) }
|
||||
let(:swagger_root) { File.expand_path('tmp/swagger', __dir__) }
|
||||
|
||||
describe '#example_group_finished(notification)' do
|
||||
before do
|
||||
@ -47,8 +48,8 @@ module Rswag
|
||||
end
|
||||
|
||||
describe '#stop' do
|
||||
before do
|
||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
||||
before do
|
||||
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||
allow(config).to receive(:swagger_docs).and_return(
|
||||
'v1/swagger.json' => { info: { version: 'v1' } },
|
||||
'v2/swagger.json' => { info: { version: 'v2' } }
|
||||
@ -64,7 +65,7 @@ module Rswag
|
||||
end
|
||||
|
||||
after do
|
||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
||||
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class Blog < ActiveRecord::Base
|
||||
validates :content, presence: true
|
||||
|
||||
def as_json(options)
|
||||
def as_json(_options)
|
||||
{
|
||||
id: id,
|
||||
title: title,
|
||||
content: nil,
|
||||
content: content,
|
||||
thumbnail: thumbnail
|
||||
}
|
||||
end
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'swagger_helper'
|
||||
|
||||
describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
|
||||
path '/auth-tests/basic' do
|
||||
post 'Authenticates with basic auth' do
|
||||
tags 'Auth Tests'
|
||||
operationId 'testBasicAuth'
|
||||
security [ basic_auth: [] ]
|
||||
security [basic_auth: []]
|
||||
|
||||
response '204', 'Valid credentials' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||
@ -24,7 +25,7 @@ describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
post 'Authenticates with an api key' do
|
||||
tags 'Auth Tests'
|
||||
operationId 'testApiKey'
|
||||
security [ api_key: [] ]
|
||||
security [api_key: []]
|
||||
|
||||
response '204', 'Valid credentials' do
|
||||
let(:api_key) { 'foobar' }
|
||||
@ -42,7 +43,7 @@ describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
post 'Authenticates with basic auth and api key' do
|
||||
tags 'Auth Tests'
|
||||
operationId 'testBasicAndApiKey'
|
||||
security [ { basic_auth: [], api_key: [] } ]
|
||||
security [{ basic_auth: [], api_key: [] }]
|
||||
|
||||
response '204', 'Valid credentials' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'swagger_helper'
|
||||
|
||||
describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
@ -10,9 +12,12 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
operationId 'createBlog'
|
||||
consumes 'application/json'
|
||||
produces 'application/json'
|
||||
parameter name: :blog, in: :body, schema: { '$ref' => '#/components/schemas/blog' }
|
||||
# parameter name: :blog, in: :body, schema: { '$ref' => '#/components/schemas/blog' }
|
||||
|
||||
let(:blog) { { title: 'foo', content: 'bar' } }
|
||||
request_body_json schema: { '$ref' => '#/components/schemas/blog' },
|
||||
examples: :blog
|
||||
|
||||
let(:blog) { { blog: { title: 'foo', content: 'bar' } } }
|
||||
|
||||
response '201', 'blog created' do
|
||||
run_test!
|
||||
@ -21,7 +26,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
response '422', 'invalid request' do
|
||||
schema '$ref' => '#/components/schemas/errors_object'
|
||||
|
||||
let(:blog) { { title: 'foo' } }
|
||||
let(:blog) { { blog: { title: 'foo' } } }
|
||||
run_test! do |response|
|
||||
expect(response.body).to include("can't be blank")
|
||||
end
|
||||
@ -42,7 +47,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
end
|
||||
|
||||
response '406', 'unsupported accept header' do
|
||||
let(:'Accept') { 'application/foo' }
|
||||
let(:Accept) { 'application/foo' }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
@ -68,11 +73,11 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
schema '$ref' => '#/components/schemas/blog'
|
||||
|
||||
examples 'application/json' => {
|
||||
id: 1,
|
||||
title: 'Hello world!',
|
||||
content: 'Hello world and hello universe. Thank you all very much!!!',
|
||||
thumbnail: "thumbnail.png"
|
||||
}
|
||||
id: 1,
|
||||
title: 'Hello world!',
|
||||
content: 'Hello world and hello universe. Thank you all very much!!!',
|
||||
thumbnail: 'thumbnail.png'
|
||||
}
|
||||
|
||||
let(:id) { blog.id }
|
||||
run_test!
|
||||
@ -96,10 +101,10 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
description 'Upload a thumbnail for specific blog by id'
|
||||
operationId 'uploadThumbnailBlog'
|
||||
consumes 'multipart/form-data'
|
||||
parameter name: :file, :in => :formData, :type => :file, required: true
|
||||
parameter name: :file, in: :formData, type: :file, required: true
|
||||
|
||||
response '200', 'blog updated' do
|
||||
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
|
||||
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join('spec/fixtures/thumbnail.png')) }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This file is copied to spec/ when you run 'rails generate rspec:install'
|
||||
ENV['RAILS_ENV'] ||= 'test'
|
||||
require File.expand_path('../../config/environment', __FILE__)
|
||||
require File.expand_path('../config/environment', __dir__)
|
||||
# Prevent database truncation if the environment is production
|
||||
abort("The Rails environment is running in production mode!") if Rails.env.production?
|
||||
abort('The Rails environment is running in production mode!') if Rails.env.production?
|
||||
require 'spec_helper'
|
||||
require 'rspec/rails'
|
||||
# Add additional requires below this line. Rails is not loaded until this point!
|
||||
@ -54,6 +56,4 @@ RSpec.configure do |config|
|
||||
Capybara.javascript_driver = :webkit
|
||||
end
|
||||
|
||||
Capybara::Webkit.configure do |config|
|
||||
config.block_unknown_urls
|
||||
end
|
||||
Capybara::Webkit.configure(&:block_unknown_urls)
|
||||
|
||||
@ -1,15 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
require 'rake'
|
||||
|
||||
describe 'rswag:specs:swaggerize' do
|
||||
let(:swagger_root) { Rails.root.to_s + '/swagger' }
|
||||
before do
|
||||
before do
|
||||
TestApp::Application.load_tasks
|
||||
FileUtils.rm_r(swagger_root) if File.exists?(swagger_root)
|
||||
FileUtils.rm_r(swagger_root) if File.exist?(swagger_root)
|
||||
end
|
||||
|
||||
it 'generates Swagger JSON files from integration specs' do
|
||||
expect { Rake::Task['rswag:specs:swaggerize'].invoke }.not_to raise_exception
|
||||
Rake::Task['rswag:specs:swaggerize'].invoke
|
||||
# expect { }.not_to raise_exception(StandardError)
|
||||
expect(File).to exist("#{swagger_root}/v1/swagger.json")
|
||||
end
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# This file was generated by the `rails generate rspec:install` command. Conventionally, all
|
||||
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
||||
# The generated `.rspec` file contains `--require spec_helper` which will cause
|
||||
@ -51,53 +53,51 @@ RSpec.configure do |config|
|
||||
File.delete("#{Rails.root}/tmp/thumbnail.png") if File.file?("#{Rails.root}/tmp/thumbnail.png")
|
||||
end
|
||||
|
||||
# The settings below are suggested to provide a good initial experience
|
||||
# with RSpec, but feel free to customize to your heart's content.
|
||||
=begin
|
||||
# This allows you to limit a spec run to individual examples or groups
|
||||
# you care about by tagging them with `:focus` metadata. When nothing
|
||||
# is tagged with `:focus`, all examples get run. RSpec also provides
|
||||
# aliases for `it`, `describe`, and `context` that include `:focus`
|
||||
# metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
||||
config.filter_run_when_matching :focus
|
||||
|
||||
# Allows RSpec to persist some state between runs in order to support
|
||||
# the `--only-failures` and `--next-failure` CLI options. We recommend
|
||||
# you configure your source control system to ignore this file.
|
||||
config.example_status_persistence_file_path = "spec/examples.txt"
|
||||
|
||||
# Limits the available syntax to the non-monkey patched syntax that is
|
||||
# recommended. For more details, see:
|
||||
# - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
||||
# - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
||||
# - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
||||
config.disable_monkey_patching!
|
||||
|
||||
# Many RSpec users commonly either run the entire suite or an individual
|
||||
# file, and it's useful to allow more verbose output when running an
|
||||
# individual spec file.
|
||||
if config.files_to_run.one?
|
||||
# Use the documentation formatter for detailed output,
|
||||
# unless a formatter has already been configured
|
||||
# (e.g. via a command-line flag).
|
||||
config.default_formatter = 'doc'
|
||||
end
|
||||
|
||||
# Print the 10 slowest examples and example groups at the
|
||||
# end of the spec run, to help surface which specs are running
|
||||
# particularly slow.
|
||||
config.profile_examples = 10
|
||||
|
||||
# Run specs in random order to surface order dependencies. If you find an
|
||||
# order dependency and want to debug it, you can fix the order by providing
|
||||
# the seed, which is printed after each run.
|
||||
# --seed 1234
|
||||
config.order = :random
|
||||
|
||||
# Seed global randomization in this process using the `--seed` CLI option.
|
||||
# Setting this allows you to use `--seed` to deterministically reproduce
|
||||
# test failures related to randomization by passing the same `--seed` value
|
||||
# as the one that triggered the failure.
|
||||
Kernel.srand config.seed
|
||||
=end
|
||||
# The settings below are suggested to provide a good initial experience
|
||||
# with RSpec, but feel free to customize to your heart's content.
|
||||
# # This allows you to limit a spec run to individual examples or groups
|
||||
# # you care about by tagging them with `:focus` metadata. When nothing
|
||||
# # is tagged with `:focus`, all examples get run. RSpec also provides
|
||||
# # aliases for `it`, `describe`, and `context` that include `:focus`
|
||||
# # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
|
||||
# config.filter_run_when_matching :focus
|
||||
#
|
||||
# # Allows RSpec to persist some state between runs in order to support
|
||||
# # the `--only-failures` and `--next-failure` CLI options. We recommend
|
||||
# # you configure your source control system to ignore this file.
|
||||
# config.example_status_persistence_file_path = "spec/examples.txt"
|
||||
#
|
||||
# # Limits the available syntax to the non-monkey patched syntax that is
|
||||
# # recommended. For more details, see:
|
||||
# # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
|
||||
# # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
|
||||
# # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
|
||||
# config.disable_monkey_patching!
|
||||
#
|
||||
# # Many RSpec users commonly either run the entire suite or an individual
|
||||
# # file, and it's useful to allow more verbose output when running an
|
||||
# # individual spec file.
|
||||
# if config.files_to_run.one?
|
||||
# # Use the documentation formatter for detailed output,
|
||||
# # unless a formatter has already been configured
|
||||
# # (e.g. via a command-line flag).
|
||||
# config.default_formatter = 'doc'
|
||||
# end
|
||||
#
|
||||
# # Print the 10 slowest examples and example groups at the
|
||||
# # end of the spec run, to help surface which specs are running
|
||||
# # particularly slow.
|
||||
# config.profile_examples = 10
|
||||
#
|
||||
# # Run specs in random order to surface order dependencies. If you find an
|
||||
# # order dependency and want to debug it, you can fix the order by providing
|
||||
# # the seed, which is printed after each run.
|
||||
# # --seed 1234
|
||||
# config.order = :random
|
||||
#
|
||||
# # Seed global randomization in this process using the `--seed` CLI option.
|
||||
# # Setting this allows you to use `--seed` to deterministically reproduce
|
||||
# # test failures related to randomization by passing the same `--seed` value
|
||||
# # as the one that triggered the failure.
|
||||
# Kernel.srand config.seed
|
||||
end
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'rails_helper'
|
||||
|
||||
RSpec.configure do |config|
|
||||
@ -5,7 +7,7 @@ RSpec.configure do |config|
|
||||
# NOTE: If you're using the rswag-api to serve API descriptions, you'll need
|
||||
# to ensure that it's configured to serve Swagger from the same folder
|
||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
config.swagger_dry_run = false
|
||||
# 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
|
||||
@ -21,54 +23,54 @@ RSpec.configure do |config|
|
||||
},
|
||||
paths: {},
|
||||
servers: [
|
||||
{
|
||||
url: "https://{defaultHost}",
|
||||
variables: {
|
||||
defaultHost: {
|
||||
default: "www.example.com"
|
||||
}
|
||||
}
|
||||
{
|
||||
url: 'https://{defaultHost}',
|
||||
variables: {
|
||||
defaultHost: {
|
||||
default: 'www.example.com'
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
components: {
|
||||
schemas: {
|
||||
errors_object: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
errors: { '$ref' => '#/components/schemas/errors_map' }
|
||||
}
|
||||
},
|
||||
errors_map: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'array',
|
||||
items: { type: 'string' }
|
||||
}
|
||||
},
|
||||
blog: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
title: { type: 'string' },
|
||||
content: { type: 'string', nullable: true },
|
||||
thumbnail: { type: 'string'}
|
||||
},
|
||||
required: [ 'id', 'title', 'content', 'thumbnail' ]
|
||||
errors_object: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
errors: { '$ref' => '#/components/schemas/errors_map' }
|
||||
}
|
||||
},
|
||||
errors_map: {
|
||||
type: 'object',
|
||||
additionalProperties: {
|
||||
type: 'array',
|
||||
items: { type: 'string' }
|
||||
}
|
||||
},
|
||||
blog: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
title: { type: 'string' },
|
||||
content: { type: 'string', nullable: true },
|
||||
thumbnail: { type: 'string' }
|
||||
},
|
||||
required: %w[id title content thumbnail]
|
||||
}
|
||||
},
|
||||
securitySchemes: {
|
||||
basic_auth: {
|
||||
type: :http,
|
||||
scheme: :basic
|
||||
},
|
||||
api_key: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
in: :query
|
||||
}
|
||||
basic_auth: {
|
||||
type: :http,
|
||||
scheme: :basic
|
||||
},
|
||||
api_key: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
in: :query
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
Loading…
Reference in New Issue
Block a user