mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-25 15:22:56 +00:00
Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8e9ab3221 | ||
|
|
26a3bf5079 | ||
|
|
12daacf9a5 | ||
|
|
05e1e2271f | ||
|
|
07ae261e54 | ||
|
|
06d00de992 | ||
|
|
ad9cd5de66 | ||
|
|
d91601b02c | ||
|
|
037c0e374a | ||
|
|
8f16492462 | ||
|
|
732cab994c | ||
|
|
452d9176cc | ||
|
|
7f0e437f8b | ||
|
|
700b227231 | ||
|
|
b16198377b | ||
|
|
e18a001e9b | ||
|
|
6ee53b08ae | ||
|
|
97c2a39cfa | ||
|
|
f7c29267fe | ||
|
|
8ede7f78f1 | ||
|
|
8b937e3585 | ||
|
|
1515ce4fcb | ||
|
|
44840ab836 | ||
|
|
182ee093f4 |
@@ -1 +1 @@
|
|||||||
2.3.0
|
2.3.1
|
||||||
|
|||||||
1
Gemfile
1
Gemfile
@@ -28,6 +28,7 @@ group :test do
|
|||||||
gem 'generator_spec'
|
gem 'generator_spec'
|
||||||
gem 'capybara'
|
gem 'capybara'
|
||||||
gem 'capybara-webkit'
|
gem 'capybara-webkit'
|
||||||
|
gem 'puma'
|
||||||
end
|
end
|
||||||
|
|
||||||
group :assets do
|
group :assets do
|
||||||
|
|||||||
@@ -82,6 +82,11 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|
|||||||
let(:id) { 'invalid' }
|
let(:id) { 'invalid' }
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
response '406', 'unsupported accept header' do
|
||||||
|
let(:'Accept') { 'application/foo' }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
module ExampleGroupHelpers
|
module ExampleGroupHelpers
|
||||||
|
|
||||||
def path(template, &block)
|
def path(template, metadata={}, &block)
|
||||||
api_metadata = { path_item: { template: template } }
|
metadata[:path_item] = { template: template }
|
||||||
describe(template, api_metadata, &block)
|
describe(template, metadata, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
|
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
|
||||||
@@ -36,7 +36,9 @@ module Rswag
|
|||||||
end
|
end
|
||||||
|
|
||||||
def parameter(attributes)
|
def parameter(attributes)
|
||||||
attributes[:required] = true if attributes[:in].to_sym == :path
|
if attributes[:in] && attributes[:in].to_sym == :path
|
||||||
|
attributes[:required] = true
|
||||||
|
end
|
||||||
|
|
||||||
if metadata.has_key?(:operation)
|
if metadata.has_key?(:operation)
|
||||||
metadata[:operation][:parameters] ||= []
|
metadata[:operation][:parameters] ||= []
|
||||||
@@ -47,9 +49,9 @@ module Rswag
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def response(code, description, &block)
|
def response(code, description, metadata={}, &block)
|
||||||
api_metadata = { response: { code: code, description: description } }
|
metadata[:response] = { code: code, description: description }
|
||||||
context(description, api_metadata, &block)
|
context(description, metadata, &block)
|
||||||
end
|
end
|
||||||
|
|
||||||
def schema(value)
|
def schema(value)
|
||||||
@@ -77,7 +79,8 @@ module Rswag
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "returns a #{metadata[:response][:code]} response" do
|
it "returns a #{metadata[:response][:code]} response" do
|
||||||
assert_response_matches_metadata(example.metadata, &block)
|
assert_response_matches_metadata(metadata)
|
||||||
|
block.call(response) if block_given?
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
before do |example|
|
before do |example|
|
||||||
@@ -86,6 +89,7 @@ module Rswag
|
|||||||
|
|
||||||
it "returns a #{metadata[:response][:code]} response" do |example|
|
it "returns a #{metadata[:response][:code]} response" do |example|
|
||||||
assert_response_matches_metadata(example.metadata, &block)
|
assert_response_matches_metadata(example.metadata, &block)
|
||||||
|
example.instance_exec(response, &block) if block_given?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,55 +5,30 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
module ExampleHelpers
|
module ExampleHelpers
|
||||||
|
|
||||||
def submit_request(api_metadata)
|
def submit_request(metadata)
|
||||||
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
|
request = RequestFactory.new.build_request(metadata, self)
|
||||||
factory = RequestFactory.new(api_metadata, global_metadata)
|
|
||||||
|
|
||||||
if RAILS_VERSION < 5
|
if RAILS_VERSION < 5
|
||||||
send(
|
send(
|
||||||
api_metadata[:operation][:verb],
|
request[:verb],
|
||||||
factory.build_fullpath(self),
|
request[:path],
|
||||||
factory.build_body(self),
|
request[:payload],
|
||||||
rackify_headers(factory.build_headers(self)) # Rails test infrastructure requires Rack headers
|
request[:headers]
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
send(
|
send(
|
||||||
api_metadata[:operation][:verb],
|
request[:verb],
|
||||||
factory.build_fullpath(self),
|
request[:path],
|
||||||
{
|
{
|
||||||
params: factory.build_body(self),
|
params: request[:payload],
|
||||||
headers: factory.build_headers(self)
|
headers: request[:headers]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_response_matches_metadata(api_metadata, &block)
|
def assert_response_matches_metadata(metadata)
|
||||||
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
|
ResponseValidator.new.validate!(metadata, response)
|
||||||
validator = ResponseValidator.new(api_metadata, global_metadata)
|
|
||||||
validator.validate!(response, &block)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def rackify_headers(headers)
|
|
||||||
name_value_pairs = headers.map do |name, value|
|
|
||||||
[
|
|
||||||
case name
|
|
||||||
when 'Accept' then 'HTTP_ACCEPT'
|
|
||||||
when 'Content-Type' then 'CONTENT_TYPE'
|
|
||||||
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
|
||||||
else name
|
|
||||||
end,
|
|
||||||
value
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
Hash[ name_value_pairs ]
|
|
||||||
end
|
|
||||||
|
|
||||||
def rswag_config
|
|
||||||
::Rswag::Specs.config
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,98 +1,82 @@
|
|||||||
require 'active_support/core_ext/hash/slice'
|
require 'active_support/core_ext/hash/slice'
|
||||||
|
require 'active_support/core_ext/hash/conversions'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
module Rswag
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
class RequestFactory
|
class RequestFactory
|
||||||
|
|
||||||
def initialize(api_metadata, global_metadata)
|
def initialize(config = ::Rswag::Specs.config)
|
||||||
@api_metadata = api_metadata
|
@config = config
|
||||||
@global_metadata = global_metadata
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_fullpath(example)
|
def build_request(metadata, example)
|
||||||
@api_metadata[:path_item][:template].dup.tap do |t|
|
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||||
t.prepend(@global_metadata[:basePath] || '')
|
parameters = expand_parameters(metadata, swagger_doc, example)
|
||||||
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
|
|
||||||
t.concat(build_query_string(example))
|
{}.tap do |request|
|
||||||
|
add_verb(request, metadata)
|
||||||
|
add_path(request, metadata, swagger_doc, parameters, example)
|
||||||
|
add_headers(request, metadata, swagger_doc, parameters, example)
|
||||||
|
add_payload(request, parameters, example)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_query_string(example)
|
|
||||||
query_string = parameters_in(:query)
|
|
||||||
.map { |p| build_query_string_part(p, 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)
|
|
||||||
name_value_pairs = parameters_in(:header).map do |param|
|
|
||||||
[
|
|
||||||
param[:name],
|
|
||||||
example.send(param[:name]).to_s
|
|
||||||
]
|
|
||||||
end
|
|
||||||
|
|
||||||
# Add MIME type headers based on produces/consumes metadata
|
|
||||||
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
|
|
||||||
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
|
|
||||||
name_value_pairs << [ 'Accept', produces.join(';') ] unless produces.nil?
|
|
||||||
name_value_pairs << [ 'Content-Type', consumes.join(';') ] unless consumes.nil?
|
|
||||||
|
|
||||||
Hash[ name_value_pairs ]
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def parameters_in(location)
|
def expand_parameters(metadata, swagger_doc, example)
|
||||||
path_item_params = @api_metadata[:path_item][:parameters] || []
|
operation_params = metadata[:operation][:parameters] || []
|
||||||
operation_params = @api_metadata[:operation][:parameters] || []
|
path_item_params = metadata[:path_item][:parameters] || []
|
||||||
applicable_params = operation_params
|
security_params = derive_security_params(metadata, swagger_doc)
|
||||||
.concat(path_item_params)
|
|
||||||
.uniq { |p| p[:name] } # operation params should override path_item params
|
|
||||||
|
|
||||||
applicable_params
|
# NOTE: Use of + instead of concat to avoid mutation of the metadata object
|
||||||
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
|
(operation_params + path_item_params + security_params)
|
||||||
.concat(security_parameters)
|
.map { |p| p['$ref'] ? resolve_parameter(p['$ref'], swagger_doc) : p }
|
||||||
.select { |p| p[:in] == location }
|
.uniq { |p| p[:name] }
|
||||||
|
.reject { |p| p[:required] == false && !example.respond_to?(p[:name]) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_parameter(ref)
|
def derive_security_params(metadata, swagger_doc)
|
||||||
defined_params = @global_metadata[:parameters]
|
requirements = metadata[:operation][:security] || swagger_doc[:security] || []
|
||||||
key = ref.sub('#/parameters/', '')
|
scheme_names = requirements.flat_map { |r| r.keys }
|
||||||
raise "Referenced parameter '#{ref}' must be defined" unless defined_params && defined_params[key]
|
schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
|
||||||
defined_params[key]
|
|
||||||
end
|
|
||||||
|
|
||||||
def security_parameters
|
schemes.map do |scheme|
|
||||||
applicable_security_schemes.map do |scheme|
|
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
||||||
if scheme[:type] == :apiKey
|
param.merge(type: :string, required: requirements.one?)
|
||||||
{ name: scheme[:name], type: :string, in: scheme[:in] }
|
|
||||||
else
|
|
||||||
{ name: 'Authorization', type: :string, in: :header } # use auth header for basic & oauth2
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def applicable_security_schemes
|
def resolve_parameter(ref, swagger_doc)
|
||||||
# First figure out the security requirement applicable to the operation
|
key = ref.sub('#/parameters/', '').to_sym
|
||||||
requirements = @api_metadata[:operation][:security] || @global_metadata[:security]
|
definitions = swagger_doc[:parameters]
|
||||||
scheme_names = requirements ? requirements.map { |r| r.keys.first } : []
|
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
|
||||||
|
definitions[key]
|
||||||
|
end
|
||||||
|
|
||||||
# Then obtain the scheme definitions for those requirements
|
def add_verb(request, metadata)
|
||||||
(@global_metadata[:securityDefinitions] || {}).slice(*scheme_names).values
|
request[:verb] = metadata[:operation][:verb]
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_path(request, metadata, swagger_doc, parameters, example)
|
||||||
|
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
|
||||||
|
|
||||||
|
request[:path] = template.tap do |template|
|
||||||
|
parameters.select { |p| p[:in] == :path }.each do |p|
|
||||||
|
template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
|
||||||
|
template.concat(i == 0 ? '?' : '&')
|
||||||
|
template.concat(build_query_string_part(p, example.send(p[:name])))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_query_string_part(param, value)
|
def build_query_string_part(param, value)
|
||||||
return "#{param[:name]}=#{value.to_s}" unless param[:type].to_sym == :array
|
|
||||||
|
|
||||||
name = param[:name]
|
name = param[:name]
|
||||||
|
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
|
||||||
|
|
||||||
case param[:collectionFormat]
|
case param[:collectionFormat]
|
||||||
when :ssv
|
when :ssv
|
||||||
"#{name}=#{value.join(' ')}"
|
"#{name}=#{value.join(' ')}"
|
||||||
@@ -106,6 +90,68 @@ module Rswag
|
|||||||
"#{name}=#{value.join(',')}" # csv is default
|
"#{name}=#{value.join(',')}" # csv is default
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
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 ] }
|
||||||
|
|
||||||
|
# Accept header
|
||||||
|
produces = metadata[:operation][:produces] || swagger_doc[:produces]
|
||||||
|
if produces
|
||||||
|
accept = example.respond_to?(:'Accept') ? example.send(:'Accept') : produces.first
|
||||||
|
tuples << [ 'Accept', accept ]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Content-Type header
|
||||||
|
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 ]
|
||||||
|
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]
|
||||||
|
end,
|
||||||
|
pair[1]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
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)
|
||||||
|
request[:payload] = build_form_payload(parameters, example)
|
||||||
|
else
|
||||||
|
request[:payload] = build_json_payload(parameters, example)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_form_payload(parameters, example)
|
||||||
|
# See http://seejohncode.com/2012/04/29/quick-tip-testing-multipart-uploads-with-rspec/
|
||||||
|
# Rather that serializing with the appropriate encoding (e.g. multipart/form-data),
|
||||||
|
# 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 ]
|
||||||
|
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
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -7,47 +7,43 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
class ResponseValidator
|
class ResponseValidator
|
||||||
|
|
||||||
def initialize(api_metadata, global_metadata)
|
def initialize(config = ::Rswag::Specs.config)
|
||||||
@api_metadata = api_metadata
|
@config = config
|
||||||
@global_metadata = global_metadata
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate!(response, &block)
|
def validate!(metadata, response)
|
||||||
validate_code!(response.code)
|
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||||
validate_headers!(response.headers)
|
|
||||||
validate_body!(response.body, &block)
|
validate_code!(metadata, response.code)
|
||||||
block.call(response) if block_given?
|
validate_headers!(metadata, response.headers)
|
||||||
|
validate_body!(metadata, swagger_doc, response.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def validate_code!(code)
|
def validate_code!(metadata, code)
|
||||||
if code.to_s != @api_metadata[:response][:code].to_s
|
expected = metadata[:response][:code].to_s
|
||||||
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{@api_metadata[:response][:code]}'"
|
if code != expected
|
||||||
|
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{expected}'"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_headers!(headers)
|
def validate_headers!(metadata, headers)
|
||||||
header_schema = @api_metadata[:response][:headers]
|
expected = (metadata[:response][:headers] || {}).keys
|
||||||
return if header_schema.nil?
|
expected.each do |name|
|
||||||
|
raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil?
|
||||||
header_schema.keys.each do |header_name|
|
|
||||||
raise UnexpectedResponse, "Expected response header #{header_name} to be present" if headers[header_name.to_s].nil?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_body!(body)
|
def validate_body!(metadata, swagger_doc, body)
|
||||||
response_schema = @api_metadata[:response][:schema]
|
response_schema = metadata[:response][:schema]
|
||||||
return if response_schema.nil?
|
return if response_schema.nil?
|
||||||
|
|
||||||
begin
|
|
||||||
validation_schema = response_schema
|
validation_schema = response_schema
|
||||||
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
||||||
.merge(@global_metadata.slice(:definitions))
|
.merge(swagger_doc.slice(:definitions))
|
||||||
JSON::Validator.validate!(validation_schema, body)
|
errors = JSON::Validator.fully_validate(validation_schema, body)
|
||||||
rescue JSON::Schema::ValidationError => ex
|
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
|
||||||
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -120,6 +120,15 @@ module Rswag
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when 'in' parameter key is not defined" do
|
||||||
|
before { subject.parameter(name: :id) }
|
||||||
|
let(:api_metadata) { { operation: {} } }
|
||||||
|
|
||||||
|
it "does not require the 'in' parameter key" do
|
||||||
|
expect(api_metadata[:operation][:parameters]).to match([ name: :id ])
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#response(code, description)' do
|
describe '#response(code, description)' do
|
||||||
|
|||||||
@@ -7,19 +7,30 @@ module Rswag
|
|||||||
subject { double('example') }
|
subject { double('example') }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
subject.extend ExampleHelpers
|
subject.extend(ExampleHelpers)
|
||||||
# Mock out some infrastructure
|
allow(Rswag::Specs).to receive(:config).and_return(config)
|
||||||
stub_const('Rails::VERSION::MAJOR', 3)
|
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||||
rswag_config = double('rswag_config')
|
stub_const('Rswag::Specs::RAILS_VERSION', 3)
|
||||||
allow(rswag_config).to receive(:get_swagger_doc).and_return(global_metadata)
|
|
||||||
allow(subject).to receive(:rswag_config).and_return(rswag_config)
|
|
||||||
end
|
end
|
||||||
let(:api_metadata) do
|
let(:config) { double('config') }
|
||||||
|
let(:swagger_doc) do
|
||||||
|
{
|
||||||
|
securityDefinitions: {
|
||||||
|
api_key: {
|
||||||
|
type: :apiKey,
|
||||||
|
name: 'api_key',
|
||||||
|
in: :query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:metadata) do
|
||||||
{
|
{
|
||||||
path_item: { template: '/blogs/{blog_id}/comments/{id}' },
|
path_item: { template: '/blogs/{blog_id}/comments/{id}' },
|
||||||
operation: {
|
operation: {
|
||||||
verb: :put,
|
verb: :put,
|
||||||
summary: 'Updates a blog',
|
summary: 'Updates a blog',
|
||||||
|
consumes: [ 'application/json' ],
|
||||||
parameters: [
|
parameters: [
|
||||||
{ name: :blog_id, in: :path, type: 'integer' },
|
{ name: :blog_id, in: :path, type: 'integer' },
|
||||||
{ name: 'id', in: :path, type: 'integer' },
|
{ name: 'id', in: :path, type: 'integer' },
|
||||||
@@ -32,19 +43,8 @@ module Rswag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:global_metadata) do
|
|
||||||
{
|
|
||||||
securityDefinitions: {
|
|
||||||
api_key: {
|
|
||||||
type: :apiKey,
|
|
||||||
name: 'api_key',
|
|
||||||
in: :query
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#submit_request(api_metadata)' do
|
describe '#submit_request(metadata)' do
|
||||||
before do
|
before do
|
||||||
allow(subject).to receive(:blog_id).and_return(1)
|
allow(subject).to receive(:blog_id).and_return(1)
|
||||||
allow(subject).to receive(:id).and_return(2)
|
allow(subject).to receive(:id).and_return(2)
|
||||||
@@ -52,14 +52,14 @@ module Rswag
|
|||||||
allow(subject).to receive(:api_key).and_return('fookey')
|
allow(subject).to receive(:api_key).and_return('fookey')
|
||||||
allow(subject).to receive(:blog).and_return(text: 'Some comment')
|
allow(subject).to receive(:blog).and_return(text: 'Some comment')
|
||||||
allow(subject).to receive(:put)
|
allow(subject).to receive(:put)
|
||||||
subject.submit_request(api_metadata)
|
subject.submit_request(metadata)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "submits a request built from metadata and 'let' values" do
|
it "submits a request built from metadata and 'let' values" do
|
||||||
expect(subject).to have_received(:put).with(
|
expect(subject).to have_received(:put).with(
|
||||||
'/blogs/1/comments/2?q1=foo&api_key=fookey',
|
'/blogs/1/comments/2?q1=foo&api_key=fookey',
|
||||||
"{\"text\":\"Some comment\"}",
|
"{\"text\":\"Some comment\"}",
|
||||||
{}
|
{ 'CONTENT_TYPE' => 'application/json' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,249 +4,335 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe RequestFactory do
|
describe RequestFactory do
|
||||||
subject { RequestFactory.new(api_metadata, global_metadata) }
|
subject { RequestFactory.new(config) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(example).to receive(:blog_id).and_return(1)
|
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||||
allow(example).to receive(:id).and_return('2')
|
|
||||||
end
|
end
|
||||||
let(:api_metadata) do
|
let(:config) { double('config') }
|
||||||
{
|
let(:swagger_doc) { {} }
|
||||||
path_item: { template: '/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) { {} }
|
|
||||||
let(:example) { double('example') }
|
let(:example) { double('example') }
|
||||||
|
let(:metadata) do
|
||||||
|
{
|
||||||
|
path_item: { template: '/blogs' },
|
||||||
|
operation: { verb: :get }
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
describe '#build_fullpath(example)' do
|
describe '#build_request(metadata, example)' do
|
||||||
let(:path) { subject.build_fullpath(example) }
|
let(:request) { subject.build_request(metadata, example) }
|
||||||
|
|
||||||
context 'always' do
|
it 'builds request hash for given example' do
|
||||||
it "builds a path using metadata and example values" do
|
expect(request[:verb]).to eq(:get)
|
||||||
expect(path).to eq('/blogs/1/comments/2')
|
expect(request[:path]).to eq('/blogs')
|
||||||
|
end
|
||||||
|
|
||||||
|
context "'path' parameters" do
|
||||||
|
before do
|
||||||
|
metadata[:path_item][:template] = '/blogs/{blog_id}/comments/{id}'
|
||||||
|
metadata[:operation][:parameters] = [
|
||||||
|
{ name: 'blog_id', in: :path, type: :number },
|
||||||
|
{ name: 'id', in: :path, type: :number }
|
||||||
|
]
|
||||||
|
allow(example).to receive(:blog_id).and_return(1)
|
||||||
|
allow(example).to receive(:id).and_return(2)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'builds the path from example values' do
|
||||||
|
expect(request[:path]).to eq('/blogs/1/comments/2')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "'query' parameters" do
|
context "'query' parameters" do
|
||||||
before do
|
before do
|
||||||
api_metadata[:operation][:parameters] << { name: 'q1', in: :query, type: 'string' }
|
metadata[:operation][:parameters] = [
|
||||||
api_metadata[:operation][:parameters] << { name: 'q2', in: :query, type: 'string' }
|
{ name: 'q1', in: :query, type: :string },
|
||||||
|
{ name: 'q2', in: :query, type: :string }
|
||||||
|
]
|
||||||
allow(example).to receive(:q1).and_return('foo')
|
allow(example).to receive(:q1).and_return('foo')
|
||||||
allow(example).to receive(:q2).and_return('bar')
|
allow(example).to receive(:q2).and_return('bar')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "appends a query string using metadata and example values" do
|
it "builds the query string from example values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?q1=foo&q2=bar')
|
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "'query' parameter of type 'array'" do
|
context "'query' parameters of type 'array'" do
|
||||||
before do
|
before do
|
||||||
api_metadata[:operation][:parameters] << {
|
metadata[:operation][:parameters] = [
|
||||||
name: 'things',
|
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format }
|
||||||
in: :query,
|
]
|
||||||
type: :array,
|
|
||||||
collectionFormat: collectionFormat
|
|
||||||
}
|
|
||||||
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
|
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = csv' do
|
context 'collectionFormat = csv' do
|
||||||
let(:collectionFormat) { :csv }
|
let(:collection_format) { :csv }
|
||||||
it "formats as comma separated values" do
|
it "formats as comma separated values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo,bar')
|
expect(request[:path]).to eq('/blogs?things=foo,bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = ssv' do
|
context 'collectionFormat = ssv' do
|
||||||
let(:collectionFormat) { :ssv }
|
let(:collection_format) { :ssv }
|
||||||
it "formats as space separated values" do
|
it "formats as space separated values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo bar')
|
expect(request[:path]).to eq('/blogs?things=foo bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = tsv' do
|
context 'collectionFormat = tsv' do
|
||||||
let(:collectionFormat) { :tsv }
|
let(:collection_format) { :tsv }
|
||||||
it "formats as tab separated values" do
|
it "formats as tab separated values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo\tbar')
|
expect(request[:path]).to eq('/blogs?things=foo\tbar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = pipes' do
|
context 'collectionFormat = pipes' do
|
||||||
let(:collectionFormat) { :pipes }
|
let(:collection_format) { :pipes }
|
||||||
it "formats as pipe separated values" do
|
it "formats as pipe separated values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo|bar')
|
expect(request[:path]).to eq('/blogs?things=foo|bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = multi' do
|
context 'collectionFormat = multi' do
|
||||||
let(:collectionFormat) { :multi }
|
let(:collection_format) { :multi }
|
||||||
it "formats as multiple parameter instances" do
|
it "formats as multiple parameter instances" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo&things=bar')
|
expect(request[:path]).to eq('/blogs?things=foo&things=bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "global definition for 'api_key in query'" do
|
context "'header' parameters" do
|
||||||
before do
|
before do
|
||||||
global_metadata[:securityDefinitions] = { api_key: { type: :apiKey, name: 'api_key', in: :query } }
|
metadata[:operation][:parameters] = [ { name: 'Api-Key', in: :header, type: :string } ]
|
||||||
allow(example).to receive(:api_key).and_return('fookey')
|
allow(example).to receive(:'Api-Key').and_return('foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'global requirement' do
|
it 'adds names and example values to headers' do
|
||||||
before { global_metadata[:security] = [ { api_key: [] } ] }
|
expect(request[:headers]).to eq({ 'Api-Key' => 'foobar' })
|
||||||
|
|
||||||
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
|
end
|
||||||
|
|
||||||
context 'operation-specific requirement' do
|
context 'optional parameters not provided' do
|
||||||
before { api_metadata[:operation][:security] = [ { api_key: [] } ] }
|
before do
|
||||||
|
metadata[:operation][:parameters] = [
|
||||||
it "appends the api_key using metadata and example value" do
|
{ name: 'q1', in: :query, type: :string, required: false },
|
||||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
{ name: 'Api-Key', in: :header, type: :string, required: false }
|
||||||
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'builds request hash without them' do
|
||||||
|
expect(request[:path]).to eq('/blogs')
|
||||||
|
expect(request[:headers]).to eq({})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "consumes content" do
|
||||||
|
before do
|
||||||
|
metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
|
||||||
|
end
|
||||||
|
|
||||||
|
context "no 'Content-Type' provided" do
|
||||||
|
it "sets 'CONTENT_TYPE' header to first in list" do
|
||||||
|
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/json')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "explicit 'Content-Type' provided" do
|
||||||
|
before do
|
||||||
|
allow(example).to receive(:'Content-Type').and_return('application/xml')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets 'CONTENT_TYPE' header to example value" do
|
||||||
|
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'JSON payload' do
|
||||||
|
before do
|
||||||
|
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\"}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'form payload' do
|
||||||
|
before do
|
||||||
|
metadata[:operation][:consumes] = [ 'multipart/form-data' ]
|
||||||
|
metadata[:operation][:parameters] = [
|
||||||
|
{ name: 'f1', in: :formData, type: :string },
|
||||||
|
{ name: 'f2', in: :formData, type: :string }
|
||||||
|
]
|
||||||
|
allow(example).to receive(:f1).and_return('foo blah')
|
||||||
|
allow(example).to receive(:f2).and_return('bar blah')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'sets payload to hash of names and example values' do
|
||||||
|
expect(request[:payload]).to eq(
|
||||||
|
'f1' => 'foo blah',
|
||||||
|
'f2' => 'bar blah'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'produces content' do
|
||||||
|
before do
|
||||||
|
metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
|
||||||
|
end
|
||||||
|
|
||||||
|
context "no 'Accept' value provided" do
|
||||||
|
it "sets 'HTTP_ACCEPT' header to first in list" do
|
||||||
|
expect(request[:headers]).to eq('HTTP_ACCEPT' => 'application/json')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "explicit 'Accept' value provided" do
|
||||||
|
before do
|
||||||
|
allow(example).to receive(:'Accept').and_return('application/xml')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets 'HTTP_ACCEPT' header to example value" do
|
||||||
|
expect(request[:headers]).to eq('HTTP_ACCEPT' => 'application/xml')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'basic auth' do
|
||||||
|
before do
|
||||||
|
swagger_doc[:securityDefinitions] = { basic: { type: :basic } }
|
||||||
|
metadata[:operation][:security] = [ basic: [] ]
|
||||||
|
allow(example).to receive(:Authorization).and_return('Basic foobar')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "sets 'HTTP_AUTHORIZATION' header to example value" do
|
||||||
|
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'apiKey' do
|
||||||
|
before do
|
||||||
|
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: key_location } }
|
||||||
|
metadata[:operation][:security] = [ apiKey: [] ]
|
||||||
|
allow(example).to receive(:api_key).and_return('foobar')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in query' do
|
||||||
|
let(:key_location) { :query }
|
||||||
|
|
||||||
|
it 'adds name and example value to the query string' do
|
||||||
|
expect(request[:path]).to eq('/blogs?api_key=foobar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in header' do
|
||||||
|
let(:key_location) { :header }
|
||||||
|
|
||||||
|
it 'adds name and example value to the headers' do
|
||||||
|
expect(request[:headers]).to eq('api_key' => 'foobar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'in header with auth param already added' do
|
||||||
|
let(:key_location) { :header }
|
||||||
|
before do
|
||||||
|
metadata[:operation][:parameters] = [
|
||||||
|
{ name: 'q1', in: :query, type: :string },
|
||||||
|
{ name: 'api_key', in: :header, type: :string }
|
||||||
|
]
|
||||||
|
allow(example).to receive(:q1).and_return('foo')
|
||||||
|
allow(example).to receive(:api_key).and_return('foobar')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'adds authorization parameter only once' do
|
||||||
|
expect(request[:headers]).to eq('api_key' => 'foobar')
|
||||||
|
expect(metadata[:operation][:parameters].size).to eq 2
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'oauth2' do
|
||||||
|
before do
|
||||||
|
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
|
||||||
|
|
||||||
|
it "sets 'HTTP_AUTHORIZATION' header to example value" do
|
||||||
|
expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Bearer foobar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'paired security requirements' do
|
||||||
|
before do
|
||||||
|
swagger_doc[:securityDefinitions] = {
|
||||||
|
basic: { type: :basic },
|
||||||
|
api_key: { type: :apiKey, name: 'api_key', in: :query }
|
||||||
|
}
|
||||||
|
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
|
||||||
|
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
|
||||||
|
before do
|
||||||
|
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
|
||||||
|
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'referenced parameters' do
|
||||||
|
before do
|
||||||
|
swagger_doc[:parameters] = { q1: { name: 'q1', in: :query, type: :string } }
|
||||||
|
metadata[:operation][:parameters] = [ { '$ref' => '#/parameters/q1' } ]
|
||||||
|
allow(example).to receive(:q1).and_return('foo')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses the referenced metadata to build the request' do
|
||||||
|
expect(request[:path]).to eq('/blogs?q1=foo')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'global basePath' do
|
context 'global basePath' do
|
||||||
before { global_metadata[:basePath] = '/foobar' }
|
before { swagger_doc[:basePath] = '/api' }
|
||||||
|
|
||||||
it 'prepends the basePath' do
|
it 'prepends to the path' do
|
||||||
expect(path).to eq('/foobar/blogs/1/comments/2')
|
expect(request[:path]).to eq('/api/blogs')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "defined at the 'path' level" do
|
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
|
||||||
before do
|
before do
|
||||||
api_metadata[:path_item][:parameters] = [ { name: :blog_id, in: :path } ]
|
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: :query } }
|
||||||
api_metadata[:operation][:parameters] = [ { name: :id, in: :path } ]
|
swagger_doc[:security] = [ apiKey: [] ]
|
||||||
|
allow(example).to receive(:api_key).and_return('foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "builds path from parameters defined at path and operation levels" do
|
it 'applieds the scheme by default' do
|
||||||
expect(path).to eq('/blogs/1/comments/2')
|
expect(request[:path]).to eq('/blogs?api_key=foobar')
|
||||||
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 "global definition for 'basic auth'" do
|
|
||||||
before do
|
|
||||||
global_metadata[:securityDefinitions] = { basic_auth: { type: :basic} }
|
|
||||||
allow(example).to receive(:'Authorization').and_return('Basic foobar')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'global requirement' do
|
|
||||||
before { global_metadata[:security] = [ { basic_auth: [] } ] }
|
|
||||||
|
|
||||||
it "includes a corresponding Authorization header" do
|
|
||||||
expect(headers).to match(
|
|
||||||
'Authorization' => 'Basic foobar'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'operation-specific requirement' do
|
|
||||||
before { api_metadata[:operation][:security] = [ { basic_auth: [] } ] }
|
|
||||||
|
|
||||||
it "includes a corresponding Authorization header" do
|
|
||||||
expect(headers).to match(
|
|
||||||
'Authorization' => 'Basic foobar'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
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
|
||||||
|
|||||||
@@ -4,121 +4,71 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe ResponseValidator do
|
describe ResponseValidator do
|
||||||
subject { ResponseValidator.new(api_metadata, global_metadata) }
|
subject { ResponseValidator.new(config) }
|
||||||
|
|
||||||
let(:api_metadata) { { response: { code: 200 } } }
|
|
||||||
let(: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
|
before do
|
||||||
api_metadata[:response][:schema] = {
|
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||||
type: 'object',
|
end
|
||||||
properties: { text: { type: 'string' } },
|
let(:config) { double('config') }
|
||||||
|
let(:swagger_doc) { {} }
|
||||||
|
let(:example) { double('example') }
|
||||||
|
let(:metadata) do
|
||||||
|
{
|
||||||
|
response: {
|
||||||
|
code: 200,
|
||||||
|
headers: { 'X-Rate-Limit-Limit' => { type: :integer } },
|
||||||
|
schema: {
|
||||||
|
type: :object,
|
||||||
|
properties: { text: { type: :string } },
|
||||||
required: [ 'text' ]
|
required: [ 'text' ]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'response code & body matches' do
|
describe '#validate!(metadata, response)' do
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some comment\"}") }
|
let(:call) { subject.validate!(metadata, response) }
|
||||||
|
let(:response) do
|
||||||
|
OpenStruct.new(
|
||||||
|
code: '200',
|
||||||
|
headers: { 'X-Rate-Limit-Limit' => '10' },
|
||||||
|
body: "{\"text\":\"Some comment\"}"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "response matches metadata" do
|
||||||
it { expect { call }.to_not raise_error }
|
it { expect { call }.to_not raise_error }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'response code matches & body does not' do
|
context "response code differs from metadata" do
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") }
|
before { response.code = '400' }
|
||||||
it { expect { call }.to raise_error UnexpectedResponse }
|
it { expect { call }.to raise_error /Expected response code/ }
|
||||||
end
|
end
|
||||||
|
|
||||||
context "'block' provided" do
|
context "response headers differ from metadata" do
|
||||||
let(:call) do
|
before { response.headers = {} }
|
||||||
subject.validate!(response) do |response|
|
it { expect { call }.to raise_error /Expected response header/ }
|
||||||
data = JSON.parse(response.body)
|
|
||||||
expect(data['text']).to eq('Some comment')
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'the block validation passes' do
|
context "response body differs from metadata" do
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some comment\"}") }
|
before { response.body = "{\"foo\":\"Some comment\"}" }
|
||||||
it { expect { call }.to_not raise_error }
|
it { expect { call }.to raise_error /Expected response body/ }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'the block validation fails' do
|
context 'referenced schemas' do
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some other comment\"}") }
|
|
||||||
it { expect { call }.to raise_error(RSpec::Expectations::ExpectationNotMetError) }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "referenced 'schema' provided" do
|
|
||||||
before do
|
before do
|
||||||
api_metadata[:response][:schema] = { '$ref' => '#/definitions/author' }
|
swagger_doc[:definitions] = {
|
||||||
global_metadata[:definitions] = {
|
'blog' => {
|
||||||
author: {
|
type: :object,
|
||||||
type: 'object',
|
properties: { foo: { type: :string } },
|
||||||
properties: { name: { type: 'string' } },
|
required: [ 'foo' ]
|
||||||
required: ['name']
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'response code & body matches' do
|
it 'uses the referenced schema to validate the response body' do
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"name\":\"Some name\"}") }
|
expect { call }.to raise_error /Expected response body/
|
||||||
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
|
|
||||||
|
|
||||||
context "'headers' provided" do
|
|
||||||
before do
|
|
||||||
api_metadata[:response][:headers] = {
|
|
||||||
'X-Rate-Limit-Limit' => {
|
|
||||||
description: 'The number of allowed requests in the current period',
|
|
||||||
type: 'integer'
|
|
||||||
},
|
|
||||||
'X-Rate-Limit-Remaining' => {
|
|
||||||
description: 'The number of remaining requests in the current period',
|
|
||||||
type: 'integer'
|
|
||||||
},
|
|
||||||
'X-Rate-Limit-Reset' => {
|
|
||||||
description: 'The number of seconds left in the current period',
|
|
||||||
type: 'integer'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'response code & body matches' do
|
|
||||||
let(:response) { OpenStruct.new(code: 200, body: '{}', headers: {
|
|
||||||
'X-Rate-Limit-Limit' => 1,
|
|
||||||
'X-Rate-Limit-Remaining' => 1,
|
|
||||||
'X-Rate-Limit-Reset' => 1
|
|
||||||
}) }
|
|
||||||
it { expect { call }.to_not raise_error }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'response code matches & body does not' do
|
|
||||||
let(:response) { OpenStruct.new(code: 200, body: '{}', headers: {
|
|
||||||
'X-Rate-Limit-Limit' => 1,
|
|
||||||
'X-Rate-Limit-Remaining' => 1
|
|
||||||
}) }
|
|
||||||
it { expect { call }.to raise_error UnexpectedResponse }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,4 +4,9 @@ class ApplicationController < ActionController::Base
|
|||||||
protect_from_forgery with: :null_session
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
wrap_parameters format: [ :json ]
|
wrap_parameters format: [ :json ]
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
rescue_from 'ActionController::UnknownFormat' do |ex|
|
||||||
|
head :not_acceptable
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,13 +1,30 @@
|
|||||||
class AuthTestsController < ApplicationController
|
class AuthTestsController < ApplicationController
|
||||||
wrap_parameters Blog
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
# POST /auth-tests/basic
|
# POST /auth-tests/basic
|
||||||
def basic
|
def basic
|
||||||
if authenticate_with_http_basic { |u, p| u == 'jsmith' && p == 'jspass' }
|
return head :unauthorized unless authenticate_basic
|
||||||
head :no_content
|
head :no_content
|
||||||
else
|
end
|
||||||
request_http_basic_authentication
|
|
||||||
end
|
# POST /auth-tests/api-key
|
||||||
|
def api_key
|
||||||
|
return head :unauthorized unless authenticate_api_key
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
# POST /auth-tests/basic-and-api-key
|
||||||
|
def basic_and_api_key
|
||||||
|
return head :unauthorized unless authenticate_basic and authenticate_api_key
|
||||||
|
head :no_content
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def authenticate_basic
|
||||||
|
authenticate_with_http_basic { |u, p| u == 'jsmith' && p == 'jspass' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def authenticate_api_key
|
||||||
|
params['api_key'] == 'foobar'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
require 'fileutils'
|
||||||
|
|
||||||
class BlogsController < ApplicationController
|
class BlogsController < ApplicationController
|
||||||
wrap_parameters Blog
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
# POST /blogs
|
# POST /blogs
|
||||||
def create
|
def create
|
||||||
@@ -8,6 +8,14 @@ class BlogsController < ApplicationController
|
|||||||
respond_with @blog
|
respond_with @blog
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Put /blogs/1
|
||||||
|
def upload
|
||||||
|
@blog = Blog.find_by_id(params[:id])
|
||||||
|
return head :not_found if @blog.nil?
|
||||||
|
@blog.thumbnail = save_uploaded_file params[:file]
|
||||||
|
head @blog.save ? :ok : :unprocsessible_entity
|
||||||
|
end
|
||||||
|
|
||||||
# GET /blogs
|
# GET /blogs
|
||||||
def index
|
def index
|
||||||
@blogs = Blog.all
|
@blogs = Blog.all
|
||||||
@@ -24,4 +32,13 @@ class BlogsController < ApplicationController
|
|||||||
respond_with @blog, status: :not_found and return unless @blog
|
respond_with @blog, status: :not_found and return unless @blog
|
||||||
respond_with @blog
|
respond_with @blog
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def save_uploaded_file(field)
|
||||||
|
return if field.nil?
|
||||||
|
file = File.join('tmp', field.original_filename)
|
||||||
|
FileUtils.cp field.tempfile.path, file
|
||||||
|
field.original_filename
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ class Blog < ActiveRecord::Base
|
|||||||
{
|
{
|
||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
content: nil
|
content: nil,
|
||||||
|
thumbnail: thumbnail
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
TestApp::Application.routes.draw do
|
TestApp::Application.routes.draw do
|
||||||
resources :blogs, defaults: { :format => :json }
|
resources :blogs
|
||||||
|
put '/blogs/:id/upload', to: 'blogs#upload'
|
||||||
|
|
||||||
post 'auth-tests/basic', to: 'auth_tests#basic'
|
post 'auth-tests/basic', to: 'auth_tests#basic'
|
||||||
|
post 'auth-tests/api-key', to: 'auth_tests#api_key'
|
||||||
|
post 'auth-tests/basic-and-api-key', to: 'auth_tests#basic_and_api_key'
|
||||||
|
|
||||||
mount Rswag::Api::Engine => 'api-docs'
|
mount Rswag::Api::Engine => 'api-docs'
|
||||||
mount Rswag::Ui::Engine => 'api-docs'
|
mount Rswag::Ui::Engine => 'api-docs'
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class CreateBlogs < migration_class
|
|||||||
create_table :blogs do |t|
|
create_table :blogs do |t|
|
||||||
t.string :title
|
t.string :title
|
||||||
t.text :content
|
t.text :content
|
||||||
|
t.string :thumbnail
|
||||||
|
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# encoding: UTF-8
|
||||||
# This file is auto-generated from the current state of the database. Instead
|
# This file is auto-generated from the current state of the database. Instead
|
||||||
# of editing this file, please use the migrations feature of Active Record to
|
# of editing this file, please use the migrations feature of Active Record to
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
# incrementally modify your database, and then regenerate this schema definition.
|
||||||
@@ -8,15 +9,16 @@
|
|||||||
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
||||||
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended to check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20160218212104) do
|
ActiveRecord::Schema.define(:version => 20160218212104) do
|
||||||
|
|
||||||
create_table "blogs", force: :cascade do |t|
|
create_table "blogs", :force => true do |t|
|
||||||
t.string "title"
|
t.string "title"
|
||||||
t.text "content"
|
t.text "content"
|
||||||
t.datetime "created_at"
|
t.string "thumbnail"
|
||||||
t.datetime "updated_at"
|
t.datetime "created_at", :null => false
|
||||||
|
t.datetime "updated_at", :null => false
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
BIN
test-app/spec/fixtures/thumbnail.png
vendored
Normal file
BIN
test-app/spec/fixtures/thumbnail.png
vendored
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
@@ -4,7 +4,7 @@ describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
|
|
||||||
path '/auth-tests/basic' do
|
path '/auth-tests/basic' do
|
||||||
post 'Authenticates with basic auth' do
|
post 'Authenticates with basic auth' do
|
||||||
tags 'Auth Test'
|
tags 'Auth Tests'
|
||||||
operationId 'testBasicAuth'
|
operationId 'testBasicAuth'
|
||||||
security [ basic_auth: [] ]
|
security [ basic_auth: [] ]
|
||||||
|
|
||||||
@@ -19,4 +19,42 @@ describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
path '/auth-tests/api-key' do
|
||||||
|
post 'Authenticates with an api key' do
|
||||||
|
tags 'Auth Tests'
|
||||||
|
operationId 'testApiKey'
|
||||||
|
security [ api_key: [] ]
|
||||||
|
|
||||||
|
response '204', 'Valid credentials' do
|
||||||
|
let(:api_key) { 'foobar' }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
response '401', 'Invalid credentials' do
|
||||||
|
let(:api_key) { 'barfoo' }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
path '/auth-tests/basic-and-api-key' do
|
||||||
|
post 'Authenticates with basic auth and api key' do
|
||||||
|
tags 'Auth Tests'
|
||||||
|
operationId 'testBasicAndApiKey'
|
||||||
|
security [ { basic_auth: [], api_key: [] } ]
|
||||||
|
|
||||||
|
response '204', 'Valid credentials' do
|
||||||
|
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||||
|
let(:api_key) { 'foobar' }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
|
||||||
|
response '401', 'Invalid credentials' do
|
||||||
|
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||||
|
let(:api_key) { 'barfoo' }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
description 'Creates a new blog from provided data'
|
description 'Creates a new blog from provided data'
|
||||||
operationId 'createBlog'
|
operationId 'createBlog'
|
||||||
consumes 'application/json'
|
consumes 'application/json'
|
||||||
parameter name: :blog, :in => :body, schema: { '$ref' => '#/definitions/blog' }
|
produces 'application/json'
|
||||||
|
parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' }
|
||||||
|
|
||||||
|
let(:blog) { { title: 'foo', content: 'bar' } }
|
||||||
|
|
||||||
response '201', 'blog created' do
|
response '201', 'blog created' do
|
||||||
let(:blog) { { title: 'foo', content: 'bar' } }
|
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -20,7 +22,9 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
schema '$ref' => '#/definitions/errors_object'
|
schema '$ref' => '#/definitions/errors_object'
|
||||||
|
|
||||||
let(:blog) { { title: 'foo' } }
|
let(:blog) { { title: 'foo' } }
|
||||||
run_test!
|
run_test! do |response|
|
||||||
|
expect(response.body).to include("can't be blank")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -31,17 +35,24 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
produces 'application/json'
|
produces 'application/json'
|
||||||
parameter name: :keywords, in: :query, type: 'string'
|
parameter name: :keywords, in: :query, type: 'string'
|
||||||
|
|
||||||
|
let(:keywords) { 'foo bar' }
|
||||||
|
|
||||||
response '200', 'success' do
|
response '200', 'success' do
|
||||||
schema type: 'array', items: { '$ref' => '#/definitions/blog' }
|
schema type: 'array', items: { '$ref' => '#/definitions/blog' }
|
||||||
|
end
|
||||||
|
|
||||||
let(:keywords) { 'foo bar' }
|
response '406', 'unsupported accept header' do
|
||||||
|
let(:'Accept') { 'application/foo' }
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
path '/blogs/{id}' do
|
path '/blogs/{id}' do
|
||||||
parameter name: :id, :in => :path, :type => :string
|
parameter name: :id, in: :path, type: :string
|
||||||
|
|
||||||
|
let(:id) { blog.id }
|
||||||
|
let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') }
|
||||||
|
|
||||||
get 'Retrieves a blog' do
|
get 'Retrieves a blog' do
|
||||||
tags 'Blogs'
|
tags 'Blogs'
|
||||||
@@ -59,10 +70,10 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
examples 'application/json' => {
|
examples 'application/json' => {
|
||||||
id: 1,
|
id: 1,
|
||||||
title: 'Hello world!',
|
title: 'Hello world!',
|
||||||
content: 'Hello world and hello universe. Thank you all very much!!!'
|
content: 'Hello world and hello universe. Thank you all very much!!!',
|
||||||
|
thumbnail: "thumbnail.png"
|
||||||
}
|
}
|
||||||
|
|
||||||
let(:blog) { Blog.create(title: 'foo', content: 'bar') }
|
|
||||||
let(:id) { blog.id }
|
let(:id) { blog.id }
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
@@ -73,4 +84,24 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
path '/blogs/{id}/upload' do
|
||||||
|
parameter name: :id, in: :path, type: :string
|
||||||
|
|
||||||
|
let(:id) { blog.id }
|
||||||
|
let(:blog) { Blog.create(title: 'foo', content: 'bar') }
|
||||||
|
|
||||||
|
put 'Uploads a blog thumbnail' do
|
||||||
|
tags 'Blogs'
|
||||||
|
description 'Upload a thumbnail for specific blog by id'
|
||||||
|
operationId 'uploadThumbnailBlog'
|
||||||
|
consumes 'multipart/form-data'
|
||||||
|
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")) }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ RSpec.configure do |config|
|
|||||||
# triggering implicit auto-inclusion in groups with matching metadata.
|
# triggering implicit auto-inclusion in groups with matching metadata.
|
||||||
config.shared_context_metadata_behavior = :apply_to_host_groups
|
config.shared_context_metadata_behavior = :apply_to_host_groups
|
||||||
|
|
||||||
|
config.after(:suite) do
|
||||||
|
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
|
# The settings below are suggested to provide a good initial experience
|
||||||
# with RSpec, but feel free to customize to your heart's content.
|
# with RSpec, but feel free to customize to your heart's content.
|
||||||
=begin
|
=begin
|
||||||
|
|||||||
@@ -39,9 +39,10 @@ RSpec.configure do |config|
|
|||||||
properties: {
|
properties: {
|
||||||
id: { type: 'integer' },
|
id: { type: 'integer' },
|
||||||
title: { type: 'string' },
|
title: { type: 'string' },
|
||||||
content: { type: 'string', 'x-nullable': true }
|
content: { type: 'string', 'x-nullable': true },
|
||||||
|
thumbnail: { type: 'string'}
|
||||||
},
|
},
|
||||||
required: [ 'id', 'title', 'content' ]
|
required: [ 'id', 'title', 'content', 'thumbnail' ]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
securityDefinitions: {
|
securityDefinitions: {
|
||||||
@@ -53,10 +54,7 @@ RSpec.configure do |config|
|
|||||||
name: 'api_key',
|
name: 'api_key',
|
||||||
in: :query
|
in: :query
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
security: [
|
|
||||||
{ api_key: [] }
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"post": {
|
"post": {
|
||||||
"summary": "Authenticates with basic auth",
|
"summary": "Authenticates with basic auth",
|
||||||
"tags": [
|
"tags": [
|
||||||
"Auth Test"
|
"Auth Tests"
|
||||||
],
|
],
|
||||||
"operationId": "testBasicAuth",
|
"operationId": "testBasicAuth",
|
||||||
"security": [
|
"security": [
|
||||||
@@ -29,6 +29,57 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"/auth-tests/api-key": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Authenticates with an api key",
|
||||||
|
"tags": [
|
||||||
|
"Auth Tests"
|
||||||
|
],
|
||||||
|
"operationId": "testApiKey",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"api_key": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "Valid credentials"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Invalid credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/auth-tests/basic-and-api-key": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Authenticates with basic auth and api key",
|
||||||
|
"tags": [
|
||||||
|
"Auth Tests"
|
||||||
|
],
|
||||||
|
"operationId": "testBasicAndApiKey",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"basic_auth": [
|
||||||
|
|
||||||
|
],
|
||||||
|
"api_key": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "Valid credentials"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Invalid credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/blogs": {
|
"/blogs": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "Creates a blog",
|
"summary": "Creates a blog",
|
||||||
@@ -40,6 +91,9 @@
|
|||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "blog",
|
"name": "blog",
|
||||||
@@ -79,14 +133,8 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"406": {
|
||||||
"description": "success",
|
"description": "unsupported accept header"
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/blog"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +179,8 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"title": "Hello world!",
|
"title": "Hello world!",
|
||||||
"content": "Hello world and hello universe. Thank you all very much!!!"
|
"content": "Hello world and hello universe. Thank you all very much!!!",
|
||||||
|
"thumbnail": "thumbnail.png"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -140,6 +189,40 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"/blogs/{id}/upload": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"put": {
|
||||||
|
"summary": "Uploads a blog thumbnail",
|
||||||
|
"tags": [
|
||||||
|
"Blogs"
|
||||||
|
],
|
||||||
|
"description": "Upload a thumbnail for specific blog by id",
|
||||||
|
"operationId": "uploadThumbnailBlog",
|
||||||
|
"consumes": [
|
||||||
|
"multipart/form-data"
|
||||||
|
],
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "file",
|
||||||
|
"in": "formData",
|
||||||
|
"type": "file",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "blog updated"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
@@ -172,12 +255,16 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"x-nullable": true
|
"x-nullable": true
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"title",
|
"title",
|
||||||
"content"
|
"content",
|
||||||
|
"thumbnail"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -190,12 +277,5 @@
|
|||||||
"name": "api_key",
|
"name": "api_key",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"security": [
|
|
||||||
{
|
|
||||||
"api_key": [
|
|
||||||
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user