25 Commits

Author SHA1 Message Date
richie
77d4cbe0ea Prep for v1.2.0 release 2017-01-08 07:51:24 -08:00
domaindrivendev
32a7ab8234 Merge pull request #40 from PavelBezpalov/master
Add missing in production assets
2017-01-05 08:16:10 -08:00
Pavel Bezpalov
95b009a72f Add missing in production assets 2017-01-04 16:38:12 +02:00
richie
471dff5e34 Prep for v1.1.0 release 2016-11-08 12:43:31 -08:00
richie
99be8135f7 Wire up capybara & add simple feature spec for swagger-ui 2016-11-08 12:38:51 -08:00
richie
8315eda8b2 Update ui to use digest assets in prod 2016-11-02 22:15:09 -07:00
domaindrivendev
e1fe9f3239 Merge pull request #33 from vinh0604/master
Simplify response header validation
2016-10-19 17:15:50 -07:00
vinhbachsy
64b0de494f Simplify response header validation
Change to checks for the presence of required headers instead of using 
JSON::Validator
2016-10-20 01:33:43 +08:00
domaindrivendev
9d4069bcfe A minor readme update 2016-10-19 00:37:06 -07:00
domaindrivendev
17a6cd13c4 Merge pull request #32 from vinh0604/master
Validating response headers and setting response examples
2016-10-19 00:09:48 -07:00
vinhbachsy
0b0acfe4c7 Rename response_examples to examples for consistent DSL
Special handling `examples` invocation with no parameters to avoid
overriding the `examples` method of rspec-core ExampleGroup
2016-10-19 03:04:03 +08:00
vinhbachsy
5ea97a4278 Update README for response headers and examples 2016-10-18 21:46:35 +08:00
vinhbachsy
5cf376891a Validate response headers based on specified header
Add validate_headers step in response validator.
Using JSON::Validator with validate header value with swagger header 
object.
2016-10-18 21:46:35 +08:00
vinhbachsy
10dd37896f Support setting examples for response
Add helper method `response_examples` to inject response examples to swagger
2016-10-18 21:46:35 +08:00
richie
3506fee3d0 Prep for 1.0.3 release 2016-10-17 14:50:57 -07:00
richie
5df130922f Support x-nullable in respone_validator 2016-10-17 14:47:11 -07:00
richie
5a19cd2373 Push latest Gemfile.lock 2016-10-14 17:50:42 -07:00
richie
7025ec0063 Prep for v1.0.2 release 2016-10-14 17:48:16 -07:00
richie
23f4120fe3 Parse defined security requirements correctly 2016-10-14 17:44:27 -07:00
richie
312f68ae72 Rename ExampleHelpers.config - appeared to be conflicting in Rails 4+ 2016-10-14 09:55:55 -07:00
richie
63e0e53104 Merge branch 'path-item-parameters' 2016-10-13 18:17:30 -07:00
richie
4d675056c1 Support parameters provided at the 'path' level 2016-10-13 18:17:19 -07:00
richie
de09df59e1 Move path metadata into 'path_item' hash 2016-10-13 17:23:31 -07:00
richie
b81b2927be More useful error messages in rswag-specs 2016-10-13 16:56:44 -07:00
richie
f1850bc6d0 Ensure Gemfile.lock is up-to-date 2016-10-12 14:16:29 -07:00
36 changed files with 639 additions and 243 deletions

1
.gitignore vendored
View File

@@ -2,3 +2,4 @@
**/*/log **/*/log
**/*/*.gem **/*/*.gem
**/*/*.sqlite3 **/*/*.sqlite3
**/*/public/assets

View File

@@ -7,4 +7,8 @@ env:
- "RAILS_VERSION=5.0.0" - "RAILS_VERSION=5.0.0"
cache: bundler cache: bundler
install: bundle update install: bundle update
before_script:
- "export DISPLAY=:99.0"
- "sh -e /etc/init.d/xvfb start"
- sleep 3 # give xvfb some time to start
script: ./ci/test.sh script: ./ci/test.sh

View File

@@ -25,5 +25,12 @@ group :test do
gem 'test-unit' gem 'test-unit'
gem 'rspec-rails' gem 'rspec-rails'
gem 'generator_spec' gem 'generator_spec'
gem 'capybara'
gem 'capybara-webkit'
gem 'rswag-specs', path: './rswag-specs' gem 'rswag-specs', path: './rswag-specs'
end end
group :assets do
gem 'uglifier'
gem 'therubyracer'
end

View File

@@ -1,13 +1,13 @@
PATH PATH
remote: ./rswag-api remote: ./rswag-api
specs: specs:
rswag-api (1.0.0) rswag-api (1.2.0)
rails (>= 3.1, < 5.1) rails (>= 3.1, < 5.1)
PATH PATH
remote: ./rswag-specs remote: ./rswag-specs
specs: specs:
rswag-specs (1.0.0) rswag-specs (1.2.0)
json-schema (~> 2.2) json-schema (~> 2.2)
rails (>= 3.1, < 5.1) rails (>= 3.1, < 5.1)
rspec-rails (>= 2.14, < 4) rspec-rails (>= 2.14, < 4)
@@ -15,7 +15,7 @@ PATH
PATH PATH
remote: ./rswag-ui remote: ./rswag-ui
specs: specs:
rswag-ui (1.0.0) rswag-ui (1.2.0)
rails (>= 3.1, < 5.1) rails (>= 3.1, < 5.1)
GEM GEM
@@ -51,8 +51,19 @@ GEM
addressable (2.4.0) addressable (2.4.0)
arel (3.0.3) arel (3.0.3)
builder (3.0.4) builder (3.0.4)
capybara (2.10.1)
addressable
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
xpath (~> 2.0)
capybara-webkit (1.1.0)
capybara (~> 2.0, >= 2.0.2)
json
diff-lcs (1.2.5) diff-lcs (1.2.5)
erubis (2.7.0) erubis (2.7.0)
execjs (2.7.0)
generator_spec (0.9.3) generator_spec (0.9.3)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
railties (>= 3.0.0) railties (>= 3.0.0)
@@ -62,11 +73,15 @@ GEM
json (1.8.3) json (1.8.3)
json-schema (2.7.0) json-schema (2.7.0)
addressable (>= 2.4) addressable (>= 2.4)
libv8 (3.16.14.15)
mail (2.5.4) mail (2.5.4)
mime-types (~> 1.16) mime-types (~> 1.16)
treetop (~> 1.4.8) treetop (~> 1.4.8)
mime-types (1.25.1) mime-types (1.25.1)
mini_portile2 (2.1.0)
multi_json (1.12.1) multi_json (1.12.1)
nokogiri (1.6.8.1)
mini_portile2 (~> 2.1.0)
polyglot (0.3.5) polyglot (0.3.5)
power_assert (0.3.1) power_assert (0.3.1)
rack (1.4.7) rack (1.4.7)
@@ -94,6 +109,7 @@ GEM
rake (11.3.0) rake (11.3.0)
rdoc (3.12.2) rdoc (3.12.2)
json (~> 1.4) json (~> 1.4)
ref (2.0.0)
rspec-core (3.5.4) rspec-core (3.5.4)
rspec-support (~> 3.5.0) rspec-support (~> 3.5.0)
rspec-expectations (3.5.0) rspec-expectations (3.5.0)
@@ -124,17 +140,26 @@ GEM
railties (~> 3.0) railties (~> 3.0)
test-unit (3.2.1) test-unit (3.2.1)
power_assert power_assert
therubyracer (0.12.2)
libv8 (~> 3.16.14.0)
ref
thor (0.19.1) thor (0.19.1)
tilt (1.4.1) tilt (1.4.1)
treetop (1.4.15) treetop (1.4.15)
polyglot polyglot
polyglot (>= 0.3.1) polyglot (>= 0.3.1)
tzinfo (0.3.51) tzinfo (0.3.51)
uglifier (3.0.2)
execjs (>= 0.3.0, < 3)
xpath (2.0.0)
nokogiri (~> 1.3)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
capybara
capybara-webkit
generator_spec generator_spec
rails (= 3.2.22) rails (= 3.2.22)
rspec-rails rspec-rails
@@ -144,3 +169,5 @@ DEPENDENCIES
sqlite3 sqlite3
strong_parameters strong_parameters
test-unit test-unit
therubyracer
uglifier

View File

@@ -171,8 +171,8 @@ The steps described above will get you up and running with minimal setup. Howeve
|Gem|Description|Added/Updated| |Gem|Description|Added/Updated|
|---------|-----------|-------------| |---------|-----------|-------------|
|__rswag-specs__|Swagger-based DSL for rspec & accompanying rake task for generating Swagger files|_spec/swagger_helper.rb_| |__rswag-specs__|Swagger-based DSL for rspec & accompanying rake task for generating Swagger files|_spec/swagger_helper.rb_|
|__rswag-api__ |Rails Engine that exposes the Swagger files as JSON endpoints|_config/initializers/rswag-api.rb, config/routes.rb_| |__rswag-api__ |Rails Engine that exposes your Swagger files as JSON endpoints|_config/initializers/rswag-api.rb, config/routes.rb_|
|__rswag-ui__ |Rails Engine that includes [swagger-ui](https://github.com/swagger-api/swagger-ui) and powers it from the Swagger endpoints|_config/initializers/rswag-ui.rb, config/routes.rb_| |__rswag-ui__ |Rails Engine that includes [swagger-ui](https://github.com/swagger-api/swagger-ui) and powers it from your Swagger endpoints|_config/initializers/rswag-ui.rb, config/routes.rb_|
### Output Location for Generated Swagger Files ### ### Output Location for Generated Swagger Files ###
@@ -243,6 +243,47 @@ describe 'Blogs API' do
end end
``` ```
### Response headers ###
In Rswag, you could use `header` method inside the response block to specify header objects for this response. Rswag will validate your response headers with those header objects and inject them into the generated swagger file:
```ruby
# spec/integration/comments_spec.rb
describe 'Blogs API' do
path '/blogs/{blog_id}/comments' do
post 'Creates a comment' do
response 422, 'invalid request' do
header 'X-Rate-Limit-Limit', type: :integer, description: 'The number of allowed requests in the current period'
header 'X-Rate-Limit-Remaining', type: :integer, description: 'The number of remaining requests in the current period'
...
end
```
### Response examples ###
You can provide custom response examples to the generated swagger file by calling the method `examples` inside the response block:
```ruby
# spec/integration/blogs_spec.rb
describe 'Blogs API' do
path '/blogs/{blog_id}' do
get 'Retrieves a blog' do
response 200, 'blog found' do
examples 'application/json' => {
id: 1,
title: 'Hello world!',
content: '...'
}
...
end
```
### Route Prefix for Swagger JSON Endpoints ### ### Route Prefix for Swagger JSON Endpoints ###
The functionality to expose Swagger files, such as those generated by rswag-specs, as JSON endpoints is implemented as a Rails Engine. As with any Engine, you can change it's mount prefix in _routes.rb_: The functionality to expose Swagger files, such as those generated by rswag-specs, as JSON endpoints is implemented as a Rails Engine. As with any Engine, you can change it's mount prefix in _routes.rb_:

View File

@@ -1,5 +1,5 @@
module Rswag module Rswag
module Api module Api
VERSION = '1.0.1' VERSION = '1.2.0'
end end
end end

View File

@@ -2,6 +2,7 @@ require 'rspec/core'
require 'rswag/specs/version' require 'rswag/specs/version'
require 'rswag/specs/example_group_helpers' require 'rswag/specs/example_group_helpers'
require 'rswag/specs/example_helpers' require 'rswag/specs/example_helpers'
require 'rswag/specs/configuration'
require 'rswag/specs/railtie' if defined?(Rails::Railtie) require 'rswag/specs/railtie' if defined?(Rails::Railtie)
module Rswag module Rswag
@@ -15,6 +16,10 @@ module Rswag
c.include ExampleHelpers, type: :request c.include ExampleHelpers, type: :request
end end
def self.config
@config ||= Configuration.new(RSpec.configuration)
end
# Support Rails 3+ and RSpec 2+ (sigh!) # Support Rails 3+ and RSpec 2+ (sigh!)
RAILS_VERSION = Rails::VERSION::MAJOR RAILS_VERSION = Rails::VERSION::MAJOR
RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i

View File

@@ -0,0 +1,37 @@
module Rswag
module Specs
class Configuration
def initialize(rspec_config)
@rspec_config = rspec_config
end
def swagger_root
@swagger_root ||= begin
if @rspec_config.swagger_root.nil?
raise ConfigurationError, 'No swagger_root provided. See swagger_helper.rb'
end
@rspec_config.swagger_root
end
end
def swagger_docs
@swagger_docs ||= begin
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
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
class ConfigurationError < StandardError; end
end
end

View File

@@ -2,9 +2,9 @@ module Rswag
module Specs module Specs
module ExampleGroupHelpers module ExampleGroupHelpers
def path(path, &block) def path(template, &block)
api_metadata = { path: path} api_metadata = { path_item: { template: template } }
describe(path, api_metadata, &block) describe(template, api_metadata, &block)
end end
[ :get, :post, :patch, :put, :delete, :head ].each do |verb| [ :get, :post, :patch, :put, :delete, :head ].each do |verb|
@@ -37,8 +37,14 @@ module Rswag
def parameter(attributes) def parameter(attributes)
attributes[:required] = true if attributes[:in].to_sym == :path attributes[:required] = true if attributes[:in].to_sym == :path
if metadata.has_key?(:operation)
metadata[:operation][:parameters] ||= [] metadata[:operation][:parameters] ||= []
metadata[:operation][:parameters] << attributes metadata[:operation][:parameters] << attributes
else
metadata[:path_item][:parameters] ||= []
metadata[:path_item][:parameters] << attributes
end
end end
def response(code, description, &block) def response(code, description, &block)
@@ -55,6 +61,14 @@ module Rswag
metadata[:response][:headers][name] = attributes metadata[:response][:headers][name] = attributes
end end
# NOTE: Similar to 'description', 'examples' need to handle the case when
# being invoked with no params to avoid overriding 'examples' method of
# rspec-core ExampleGroup
def examples(example = nil)
return super() if example.nil?
metadata[:response][:examples] = example
end
def run_test! def run_test!
# NOTE: rspec 2.x support # NOTE: rspec 2.x support
if RSPEC_VERSION < 3 if RSPEC_VERSION < 3

View File

@@ -6,7 +6,8 @@ module Rswag
module ExampleHelpers module ExampleHelpers
def submit_request(api_metadata) def submit_request(api_metadata)
factory = RequestFactory.new(api_metadata, global_metadata(api_metadata[:swagger_doc])) global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
factory = RequestFactory.new(api_metadata, global_metadata)
if RAILS_VERSION < 5 if RAILS_VERSION < 5
send( send(
@@ -28,15 +29,15 @@ module Rswag
end end
def assert_response_matches_metadata(api_metadata) def assert_response_matches_metadata(api_metadata)
validator = ResponseValidator.new(api_metadata, global_metadata(api_metadata[:swagger_doc])) global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
validator = ResponseValidator.new(api_metadata, global_metadata)
validator.validate!(response) validator.validate!(response)
end end
private private
def global_metadata(swagger_doc) def rswag_config
swagger_docs = ::RSpec.configuration.swagger_docs ::Rswag::Specs.config
swagger_doc.nil? ? swagger_docs.values.first : swagger_docs[swagger_doc]
end end
end end
end end

View File

@@ -0,0 +1,25 @@
require 'json-schema'
module Rswag
module Specs
class ExtendedSchema < JSON::Schema::Validator
def initialize
super
extend_schema_definition("http://json-schema.org/draft-04/schema#")
@attributes['type'] = ExtendedTypeAttribute
@uri = URI.parse('http://tempuri.org/rswag/specs/extended_schema')
end
end
class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute
def self.validate(current_schema, data, fragments, processor, validator, options={})
return if data.nil? && current_schema.schema['x-nullable'] == true
super
end
end
JSON::Validator.register_validator(ExtendedSchema.new)
end
end

View File

@@ -11,7 +11,7 @@ module Rswag
end end
def build_fullpath(example) def build_fullpath(example)
@api_metadata[:path].dup.tap do |t| @api_metadata[:path_item][:template].dup.tap do |t|
t.prepend(@global_metadata[:basePath] || '') t.prepend(@global_metadata[:basePath] || '')
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) } parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
t.concat(build_query_string(example)) t.concat(build_query_string(example))
@@ -44,7 +44,13 @@ module Rswag
private private
def parameters_in(location) def parameters_in(location)
(@api_metadata[:operation][:parameters] || []) path_item_params = @api_metadata[:path_item][:parameters] || []
operation_params = @api_metadata[:operation][:parameters] || []
applicable_params = operation_params
.concat(path_item_params)
.uniq { |p| p[:name] } # operation params should override path_item params
applicable_params
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references .map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
.concat(resolve_api_key_parameters) .concat(resolve_api_key_parameters)
.select { |p| p[:in] == location } .select { |p| p[:in] == location }
@@ -59,9 +65,13 @@ module Rswag
def resolve_api_key_parameters def resolve_api_key_parameters
@api_key_params ||= begin @api_key_params ||= begin
global_requirements = (@global_metadata[:security] || {}) # First figure out the security requirement applicable to the operation
requirements = global_requirements.merge(@api_metadata[:operation][:security] || {}) global_requirements = (@global_metadata[:security] || [] ).map { |r| r.keys.first }
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements.keys) operation_requirements = (@api_metadata[:operation][:security] || [] ).map { |r| r.keys.first }
requirements = global_requirements | operation_requirements
# Then obtain the scheme definitions for those requirements
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements)
definitions.values.select { |d| d[:type] == :apiKey } definitions.values.select { |d| d[:type] == :apiKey }
end end
end end

View File

@@ -1,4 +1,6 @@
require 'active_support/core_ext/hash/slice'
require 'json-schema' require 'json-schema'
require 'rswag/specs/extended_schema'
module Rswag module Rswag
module Specs module Specs
@@ -11,6 +13,7 @@ module Rswag
def validate!(response) def validate!(response)
validate_code!(response.code) validate_code!(response.code)
validate_headers!(response.headers)
validate_body!(response.body) validate_body!(response.body)
end end
@@ -22,11 +25,24 @@ module Rswag
end end
end end
def validate_headers!(headers)
header_schema = @api_metadata[:response][:headers]
return if header_schema.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
def validate_body!(body) def validate_body!(body)
schema = @api_metadata[:response][:schema] response_schema = @api_metadata[:response][:schema]
return if schema.nil? return if response_schema.nil?
begin begin
JSON::Validator.validate!(schema.merge(@global_metadata), body) validation_schema = response_schema
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
.merge(@global_metadata.slice(:definitions))
JSON::Validator.validate!(validation_schema, body)
rescue JSON::Schema::ValidationError => ex rescue JSON::Schema::ValidationError => ex
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}" raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
end end

View File

@@ -1,5 +1,4 @@
require 'active_support/core_ext/hash/deep_merge' require 'active_support/core_ext/hash/deep_merge'
require 'rspec/core/formatters/base_text_formatter'
require 'swagger_helper' require 'swagger_helper'
module Rswag module Rswag
@@ -11,12 +10,9 @@ module Rswag
::RSpec::Core::Formatters.register self, :example_group_finished, :stop ::RSpec::Core::Formatters.register self, :example_group_finished, :stop
end end
def initialize(output) def initialize(output, config = Rswag::Specs.config)
@output = output @output = output
@swagger_root = ::RSpec.configuration.swagger_root @config = config
raise ConfigurationError, 'Missing swagger_root. See swagger_helper.rb' if @swagger_root.nil?
@swagger_docs = ::RSpec.configuration.swagger_docs || []
raise ConfigurationError, 'Missing swagger_docs. See swagger_helper.rb' if @swagger_docs.empty?
@output.puts 'Generating Swagger docs ...' @output.puts 'Generating Swagger docs ...'
end end
@@ -30,13 +26,13 @@ module Rswag
end end
return unless metadata.has_key?(:response) return unless metadata.has_key?(:response)
swagger_doc = get_swagger_doc(metadata[:swagger_doc]) swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
swagger_doc.deep_merge!(metadata_to_swagger(metadata)) swagger_doc.deep_merge!(metadata_to_swagger(metadata))
end end
def stop(notification=nil) def stop(notification=nil)
@swagger_docs.each do |url_path, doc| @config.swagger_docs.each do |url_path, doc|
file_path = File.join(@swagger_root, url_path) file_path = File.join(@config.swagger_root, url_path)
dirname = File.dirname(file_path) dirname = File.dirname(file_path)
FileUtils.mkdir_p dirname unless File.exists?(dirname) FileUtils.mkdir_p dirname unless File.exists?(dirname)
@@ -50,30 +46,22 @@ module Rswag
private private
def get_swagger_doc(tag)
return @swagger_docs.values.first if tag.nil?
raise ConfigurationError, "Unknown swagger_doc '#{tag}'" unless @swagger_docs.has_key?(tag)
@swagger_docs[tag]
end
def metadata_to_swagger(metadata) def metadata_to_swagger(metadata)
response_code = metadata[:response][:code] response_code = metadata[:response][:code]
response = metadata[:response].reject { |k,v| k == :code } response = metadata[:response].reject { |k,v| k == :code }
verb = metadata[:operation][:verb] verb = metadata[:operation][:verb]
operation = metadata[:operation] operation = metadata[:operation]
.reject { |k,v| k == :verb } .reject { |k,v| k == :verb }
.merge(responses: { response_code => response }) .merge(responses: { response_code => response })
{ path_template = metadata[:path_item][:template]
paths: { path_item = metadata[:path_item]
metadata[:path] => { .reject { |k,v| k == :template }
verb => operation .merge(verb => operation)
}
}
}
end
end
class ConfigurationError < StandardError; end { paths: { path_template => path_item } }
end
end
end end
end end

View File

@@ -1,5 +1,5 @@
module Rswag module Rswag
module Specs module Specs
VERSION = '1.0.1' VERSION = '1.2.0'
end end
end end

View File

@@ -0,0 +1,77 @@
require 'rswag/specs/configuration'
module Rswag
module Specs
describe Configuration do
subject { described_class.new(rspec_config) }
let(:rspec_config) { OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs) }
let(:swagger_root) { 'foobar' }
let(:swagger_docs) do
{
'v1/swagger.json' => { info: { title: 'v1' } },
'v2/swagger.json' => { info: { title: 'v2' } }
}
end
describe '#swagger_root' do
let(:response) { subject.swagger_root }
context 'provided in rspec config' do
it { expect(response).to eq('foobar') }
end
context 'not provided' do
let(:swagger_root) { nil }
it { expect { response }.to raise_error ConfigurationError }
end
end
describe '#swagger_docs' do
let(:response) { subject.swagger_docs }
context 'provided in rspec config' do
it { expect(response).to be_an_instance_of(Hash) }
end
context 'not provided' do
let(:swagger_docs) { nil }
it { expect { response }.to raise_error ConfigurationError }
end
context 'provided but empty' do
let(:swagger_docs) { {} }
it { expect { response }.to raise_error ConfigurationError }
end
end
describe '#get_swagger_doc(tag=nil)' do
let(:swagger_doc) { subject.get_swagger_doc(tag) }
context 'no tag provided' do
let(:tag) { nil }
it 'returns the first doc in rspec config' do
expect(swagger_doc).to eq(info: { title: 'v1' })
end
end
context 'tag provided' do
context 'matching doc' do
let(:tag) { 'v2/swagger.json' }
it 'returns the matching doc in rspec config' do
expect(swagger_doc).to eq(info: { title: 'v2' })
end
end
context 'no matching doc' do
let(:tag) { 'foobar' }
it { expect { swagger_doc }.to raise_error ConfigurationError }
end
end
end
end
end
end

View File

@@ -5,20 +5,21 @@ module Rswag
describe ExampleGroupHelpers do describe ExampleGroupHelpers do
subject { double('example_group') } subject { double('example_group') }
let(:api_metadata) { {} }
before do before do
subject.extend ExampleGroupHelpers subject.extend ExampleGroupHelpers
allow(subject).to receive(:describe) allow(subject).to receive(:describe)
allow(subject).to receive(:context) allow(subject).to receive(:context)
allow(subject).to receive(:metadata).and_return(api_metadata) allow(subject).to receive(:metadata).and_return(api_metadata)
end end
let(:api_metadata) { {} }
describe '#path(path)' do describe '#path(path)' do
before { subject.path('/blogs') } before { subject.path('/blogs') }
it "delegates to 'describe' with 'path' metadata" do it "delegates to 'describe' with 'path' metadata" do
expect(subject).to have_received(:describe).with( expect(subject).to have_received(:describe).with(
'/blogs', path: '/blogs' '/blogs', path_item: { template: '/blogs' }
) )
end end
end end
@@ -34,7 +35,6 @@ module Rswag
end end
describe '#tags|description|operationId|consumes|produces|schemes|deprecated(value)' do describe '#tags|description|operationId|consumes|produces|schemes|deprecated(value)' do
let(:api_metadata) { { operation: {} } }
before do before do
subject.tags('Blogs', 'Admin') subject.tags('Blogs', 'Admin')
subject.description('Some description') subject.description('Some description')
@@ -44,6 +44,7 @@ module Rswag
subject.schemes('http', 'https') subject.schemes('http', 'https')
subject.deprecated(true) subject.deprecated(true)
end end
let(:api_metadata) { { operation: {} } }
it "adds to the 'operation' metadata" do it "adds to the 'operation' metadata" do
expect(api_metadata[:operation]).to match( expect(api_metadata[:operation]).to match(
@@ -59,7 +60,6 @@ module Rswag
end end
describe '#tags|description|operationId|consumes|produces|schemes|deprecated|security(value)' do describe '#tags|description|operationId|consumes|produces|schemes|deprecated|security(value)' do
let(:api_metadata) { { operation: {} } }
before do before do
subject.tags('Blogs', 'Admin') subject.tags('Blogs', 'Admin')
subject.description('Some description') subject.description('Some description')
@@ -70,6 +70,7 @@ module Rswag
subject.deprecated(true) subject.deprecated(true)
subject.security(api_key: []) subject.security(api_key: [])
end end
let(:api_metadata) { { operation: {} } }
it "adds to the 'operation' metadata" do it "adds to the 'operation' metadata" do
expect(api_metadata[:operation]).to match( expect(api_metadata[:operation]).to match(
@@ -86,10 +87,21 @@ module Rswag
end end
describe '#parameter(attributes)' do describe '#parameter(attributes)' do
let(:api_metadata) { { operation: {} } }
context 'always' do context "when called at the 'path' level" do
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) } 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' } ]
)
end
end
context "when called at the 'operation' level" do
before { subject.parameter(name: :blog, in: :body, schema: { type: 'object' }) }
let(:api_metadata) { { path_item: {}, operation: {} } } # i.e. operation defined
it "adds to the 'operation parameters' metadata" do it "adds to the 'operation parameters' metadata" do
expect(api_metadata[:operation][:parameters]).to match( expect(api_metadata[:operation][:parameters]).to match(
@@ -100,6 +112,7 @@ module Rswag
context "'path' parameter" do context "'path' parameter" do
before { subject.parameter(name: :id, in: :path) } before { subject.parameter(name: :id, in: :path) }
let(:api_metadata) { { operation: {} } }
it "automatically sets the 'required' flag" do it "automatically sets the 'required' flag" do
expect(api_metadata[:operation][:parameters]).to match( expect(api_metadata[:operation][:parameters]).to match(
@@ -120,8 +133,8 @@ module Rswag
end end
describe '#schema(value)' do describe '#schema(value)' do
let(:api_metadata) { { response: {} } }
before { subject.schema(type: 'object') } before { subject.schema(type: 'object') }
let(:api_metadata) { { response: {} } }
it "adds to the 'response' metadata" do it "adds to the 'response' metadata" do
expect(api_metadata[:response][:schema]).to match(type: 'object') expect(api_metadata[:response][:schema]).to match(type: 'object')
@@ -129,8 +142,8 @@ module Rswag
end end
describe '#header(name, attributes)' do describe '#header(name, attributes)' do
let(:api_metadata) { { response: {} } }
before { subject.header('Date', type: 'string') } before { subject.header('Date', type: 'string') }
let(:api_metadata) { { response: {} } }
it "adds to the 'response headers' metadata" do it "adds to the 'response headers' metadata" do
expect(api_metadata[:response][:headers]).to match( expect(api_metadata[:response][:headers]).to match(
@@ -138,6 +151,25 @@ module Rswag
) )
end end
end end
describe '#examples(example)' do
let(:json_example) do
{
'application/json' => {
foo: 'bar'
}
}
end
let(:api_metadata) { { response: {} } }
before do
subject.examples(json_example)
end
it "adds to the 'response examples' metadata" do
expect(api_metadata[:response][:examples]).to eq(json_example)
end
end
end end
end end
end end

View File

@@ -4,9 +4,19 @@ module Rswag
module Specs module Specs
describe ExampleHelpers do describe ExampleHelpers do
subject { double('example') }
before do
subject.extend ExampleHelpers
# Mock out some infrastructure
stub_const('Rails::VERSION::MAJOR', 3)
rswag_config = double('rswag_config')
allow(rswag_config).to receive(:get_swagger_doc).and_return(global_metadata)
allow(subject).to receive(:rswag_config).and_return(rswag_config)
end
let(:api_metadata) do let(:api_metadata) do
{ {
path: '/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',
@@ -16,9 +26,9 @@ module Rswag
{ name: 'q1', in: :query, type: 'string' }, { name: 'q1', in: :query, type: 'string' },
{ name: :blog, in: :body, schema: { type: 'object' } } { name: :blog, in: :body, schema: { type: 'object' } }
], ],
security: { security: [
api_key: [] { api_key: [] }
} ]
} }
} }
end end
@@ -34,22 +44,14 @@ module Rswag
} }
end end
subject { double('example') } describe '#submit_request(api_metadata)' do
before do before do
subject.extend ExampleHelpers
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)
allow(subject).to receive(:q1).and_return('foo') allow(subject).to receive(:q1).and_return('foo')
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(:global_metadata).and_return(global_metadata)
allow(subject).to receive(:put) allow(subject).to receive(:put)
end
describe '#submit_request(api_metadata)' do
before do
stub_const('Rails::VERSION::MAJOR', 3)
subject.submit_request(api_metadata) subject.submit_request(api_metadata)
end end

View File

@@ -4,9 +4,15 @@ module Rswag
module Specs module Specs
describe RequestFactory do describe RequestFactory do
subject { RequestFactory.new(api_metadata, global_metadata) }
before do
allow(example).to receive(:blog_id).and_return(1)
allow(example).to receive(:id).and_return('2')
end
let(:api_metadata) do let(:api_metadata) do
{ {
path: '/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',
@@ -18,14 +24,7 @@ module Rswag
} }
end end
let(:global_metadata) { {} } let(:global_metadata) { {} }
subject { RequestFactory.new(api_metadata, global_metadata) }
let(:example) { double('example') } let(:example) { double('example') }
before do
allow(example).to receive(:blog_id).and_return(1)
allow(example).to receive(:id).and_return('2')
end
describe '#build_fullpath(example)' do describe '#build_fullpath(example)' do
let(:path) { subject.build_fullpath(example) } let(:path) { subject.build_fullpath(example) }
@@ -103,7 +102,7 @@ module Rswag
end end
context 'global requirement' do context 'global requirement' do
before { global_metadata[:security] = { api_key: [] } } before { global_metadata[:security] = [ { api_key: [] } ] }
it "appends the api_key using metadata and example value" do it "appends the api_key using metadata and example value" do
expect(path).to eq('/blogs/1/comments/2?api_key=fookey') expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
@@ -111,7 +110,7 @@ module Rswag
end end
context 'operation-specific requirement' do context 'operation-specific requirement' do
before { api_metadata[:operation][:security] = { api_key: [] } } before { api_metadata[:operation][:security] = [ { api_key: [] } ] }
it "appends the api_key using metadata and example value" do it "appends the api_key using metadata and example value" do
expect(path).to eq('/blogs/1/comments/2?api_key=fookey') expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
@@ -126,6 +125,17 @@ module Rswag
expect(path).to eq('/foobar/blogs/1/comments/2') expect(path).to eq('/foobar/blogs/1/comments/2')
end end
end end
context "defined at the 'path' level" do
before do
api_metadata[:path_item][:parameters] = [ { name: :blog_id, in: :path } ]
api_metadata[:operation][:parameters] = [ { name: :id, in: :path } ]
end
it "builds path from parameters defined at path and operation levels" do
expect(path).to eq('/blogs/1/comments/2')
end
end
end end
describe '#build_body(example)' do describe '#build_body(example)' do

View File

@@ -4,11 +4,11 @@ module Rswag
module Specs module Specs
describe ResponseValidator do describe ResponseValidator do
subject { ResponseValidator.new(api_metadata, global_metadata) }
let(:api_metadata) { { response: { code: 200 } } } let(:api_metadata) { { response: { code: 200 } } }
let(:global_metadata) { {} } let(:global_metadata) { {} }
subject { ResponseValidator.new(api_metadata, global_metadata) }
describe '#validate!(response)' do describe '#validate!(response)' do
let(:call) { subject.validate!(response) } let(:call) { subject.validate!(response) }
@@ -66,6 +66,42 @@ module Rswag
it { expect { call }.to raise_error UnexpectedResponse } it { expect { call }.to raise_error UnexpectedResponse }
end end
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
end end

View File

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

View File

@@ -3,33 +3,33 @@
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>Swagger UI</title> <title>Swagger UI</title>
<link rel="icon" type="image/png" href="/assets/swagger-ui/images/favicon-32x32.png" sizes="32x32" /> <link rel="icon" type="image/png" href="<%= asset_path 'swagger-ui/images/favicon-32x32.png' %>" sizes="32x32" />
<link rel="icon" type="image/png" href="/assets/swagger-ui/images/favicon-16x16.png" sizes="16x16" /> <link rel="icon" type="image/png" href="<%= asset_path 'swagger-ui/images/favicon-16x16.png' %>" sizes="16x16" />
<link href='/assets/swagger-ui/css/typography.css' media='screen' rel='stylesheet' type='text/css'/> <link href="<%= asset_path 'swagger-ui/css/typography.css' %>" media='screen' rel='stylesheet' type='text/css'/>
<link href='/assets/swagger-ui/css/reset.css' media='screen' rel='stylesheet' type='text/css'/> <link href="<%= asset_path 'swagger-ui/css/reset.css' %>" media='screen' rel='stylesheet' type='text/css'/>
<link href='/assets/swagger-ui/css/screen.css' media='screen' rel='stylesheet' type='text/css'/> <link href="<%= asset_path 'swagger-ui/css/screen.css' %>" media='screen' rel='stylesheet' type='text/css'/>
<link href='/assets/swagger-ui/css/reset.css' media='print' rel='stylesheet' type='text/css'/> <link href="<%= asset_path 'swagger-ui/css/reset.css' %>" media='print' rel='stylesheet' type='text/css'/>
<link href='/assets/swagger-ui/css/print.css' media='print' rel='stylesheet' type='text/css'/> <link href="<%= asset_path 'swagger-ui/css/print.css' %>" media='print' rel='stylesheet' type='text/css'/>
<script src='/assets/swagger-ui/lib/object-assign-pollyfill.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/object-assign-pollyfill.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jquery-1.8.0.min.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/jquery-1.8.0.min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jquery.slideto.min.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/jquery.slideto.min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jquery.wiggle.min.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/jquery.wiggle.min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jquery.ba-bbq.min.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/jquery.ba-bbq.min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/handlebars-4.0.5.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/handlebars-4.0.5.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/lodash.min.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/lodash.min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/backbone-min.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/backbone-min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/swagger-ui.min.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/swagger-ui.min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/highlight.9.1.0.pack.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/highlight.9.1.0.pack.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/highlight.9.1.0.pack_extended.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/highlight.9.1.0.pack_extended.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/jsoneditor.min.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/jsoneditor.min.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/marked.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/marked.js' %>" type='text/javascript'></script>
<script src='/assets/swagger-ui/lib/swagger-oauth.js' type='text/javascript'></script> <script src="<%= asset_path 'swagger-ui/lib/swagger-oauth.js' %>" type='text/javascript'></script>
<!-- Some basic translations --> <!-- Some basic translations -->
<!-- <script src='/assets/swagger-ui/lang/translator.js' type='text/javascript'></script> --> <!-- <script src="<%= asset_path 'swagger-ui/lang/translator.js' %>" type='text/javascript'></script> -->
<!-- <script src='/assets/swagger-ui/lang/ru.js' type='text/javascript'></script> --> <!-- <script src="<%= asset_path 'swagger-ui/lang/ru.js' %>" type='text/javascript'></script> -->
<!-- <script src='/assets/swagger-ui/lang/en.js' type='text/javascript'></script> --> <!-- <script src="<%= asset_path 'swagger-ui/lang/en.js' %>" type='text/javascript'></script> -->
<script type="text/javascript"> <script type="text/javascript">
$(function () { $(function () {
@@ -84,7 +84,7 @@
<body class="swagger-section"> <body class="swagger-section">
<div id='header'> <div id='header'>
<div class="swagger-ui-wrap"> <div class="swagger-ui-wrap">
<a id="logo" href="http://swagger.io"><img class="logo__img" alt="swagger" height="30" width="30" src="/assets/swagger-ui/images/logo_small.png" /><span class="logo__title">swagger</span></a> <a id="logo" href="http://swagger.io"><img class="logo__img" alt="swagger" height="30" width="30" src="<%= asset_path 'swagger-ui/images/logo_small.png' %>" /><span class="logo__title">swagger</span></a>
<form id='api_selector'> <form id='api_selector'>
<div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div> <div class='input'><input placeholder="http://example.com/api" id="input_baseUrl" name="baseUrl" type="text"/></div>
<div id='auth_container'></div> <div id='auth_container'></div>

View File

@@ -1,5 +1,5 @@
module Rswag module Rswag
module Ui module Ui
VERSION = '1.0.1' VERSION = '1.2.0'
end end
end end

View File

@@ -880,7 +880,7 @@
padding: 6px 8px; padding: 6px 8px;
} }
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { .swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber {
background-image: url('../images/throbber.gif'); background-image: url(<%= asset_path('swagger-ui/images/throbber.gif') %>);
width: 128px; width: 128px;
height: 16px; height: 16px;
display: block; display: block;
@@ -1222,7 +1222,7 @@
height: 18px; height: 18px;
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
background: url(../images/explorer_icons.png) no-repeat; background: url(<%= asset_path('swagger-ui/images/explorer_icons.png') %>) no-repeat;
} }
.swagger-section .authorize__btn_operation_login { .swagger-section .authorize__btn_operation_login {
background-position: 0 0; background-position: 0 0;

View File

@@ -880,7 +880,7 @@
padding: 6px 8px; padding: 6px 8px;
} }
.swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber { .swagger-section .swagger-ui-wrap ul#resources li.resource ul.endpoints li.endpoint ul.operations li.operation div.content div.sandbox_header span.response_throbber {
background-image: url('../images/throbber.gif'); background-image: url(<%= asset_path('swagger-ui/images/throbber.gif') %>);
width: 128px; width: 128px;
height: 16px; height: 16px;
display: block; display: block;
@@ -1222,7 +1222,7 @@
height: 18px; height: 18px;
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
background: url(../images/explorer_icons.png) no-repeat; background: url(<%= asset_path('swagger-ui/images/explorer_icons.png') %>) no-repeat;
} }
.swagger-section .authorize__btn_operation_login { .swagger-section .authorize__btn_operation_login {
background-position: 0 0; background-position: 0 0;
@@ -1354,7 +1354,7 @@
height: 18px; height: 18px;
vertical-align: middle; vertical-align: middle;
display: inline-block; display: inline-block;
background: url(../images/explorer_icons.png) no-repeat; background: url(<%= asset_path('swagger-ui/images/explorer_icons.png') %>) no-repeat;
} }
.swagger-section .api-ic .api_information_panel { .swagger-section .api-ic .api_information_panel {
position: relative; position: relative;

View File

@@ -2,7 +2,7 @@
font-size: 1.5em; font-size: 1.5em;
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
background: transparent url(../images/logo.png) no-repeat left center; background: transparent url(<%= asset_path('swagger-ui/images/logo.png') %>) no-repeat left center;
padding: 20px 0 20px 40px; padding: 20px 0 20px 40px;
} }
#text-head { #text-head {
@@ -64,7 +64,7 @@ h1 {
width: 1500px; width: 1500px;
margin: auto; margin: auto;
margin-top: 0; margin-top: 0;
background-image: url('../images/shield.png'); background-image: url(<%= asset_path('swagger-ui/images/shield.png') %>);
background-repeat: no-repeat; background-repeat: no-repeat;
background-position: -40px -20px; background-position: -40px -20px;
margin-bottom: 210px; margin-bottom: 210px;

View File

@@ -1,14 +0,0 @@
/* Google Font's Droid Sans */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 400;
src: local('Droid Sans'), local('DroidSans'), url('../fonts/DroidSans.ttf'), format('truetype');
}
/* Google Font's Droid Sans Bold */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 700;
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url('../fonts/DroidSans-Bold.ttf'), format('truetype');
}

View File

@@ -0,0 +1,14 @@
/* Google Font's Droid Sans */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 400;
src: local('Droid Sans'), local('DroidSans'), url(<%= asset_path('swagger-ui/fonts/DroidSans.ttf') %>), format('truetype');
}
/* Google Font's Droid Sans Bold */
@font-face {
font-family: 'Droid Sans';
font-style: normal;
font-weight: 700;
src: local('Droid Sans Bold'), local('DroidSans-Bold'), url(<%= asset_path('swagger-ui/fonts/DroidSans-Bold.ttf') %>), format('truetype');
}

View File

@@ -1,3 +1,3 @@
module Rswag module Rswag
VERSION = '1.0.1' VERSION = '1.2.0'
end end

View File

@@ -17,6 +17,10 @@ class BlogsController < ApplicationController
# GET /blogs/1 # GET /blogs/1
def show def show
@blog = Blog.find_by_id(params[:id]) @blog = Blog.find_by_id(params[:id])
fresh_when(@blog)
return unless stale?(@blog)
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

View File

@@ -0,0 +1,67 @@
TestApp::Application.configure do
# Settings specified here will take precedence over those in config/application.rb
# Code is not reloaded between requests
config.cache_classes = true
# Full error reports are disabled and caching is turned on
config.consider_all_requests_local = false
config.action_controller.perform_caching = true
# Disable Rails's static asset server (Apache or nginx will already do this)
config.serve_static_assets = true
# Compress JavaScripts and CSS
config.assets.compress = true
# Don't fallback to assets pipeline if a precompiled asset is missed
config.assets.compile = false
# Generate digests for assets URLs
config.assets.digest = true
# Defaults to nil and saved in location specified by config.assets.prefix
# config.assets.manifest = YOUR_PATH
# Specifies the header that your server uses for sending files
# config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache
# config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx
# Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
# config.force_ssl = true
# See everything in the log (default is :info)
# config.log_level = :debug
# Prepend all log lines with the following tags
# config.log_tags = [ :subdomain, :uuid ]
# Use a different logger for distributed setups
# config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new)
# Use a different cache store in production
# config.cache_store = :mem_cache_store
# Enable serving of images, stylesheets, and JavaScripts from an asset server
# config.action_controller.asset_host = "http://assets.example.com"
# Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added)
# config.assets.precompile += %w( search.js )
# Disable delivery errors, bad email addresses will be ignored
# config.action_mailer.raise_delivery_errors = false
# Enable threaded mode
# config.threadsafe!
# Enable locale fallbacks for I18n (makes lookups for any locale fall back to
# the I18n.default_locale when a translation can not be found)
config.i18n.fallbacks = true
# Send deprecation notices to registered listeners
config.active_support.deprecation = :notify
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL)
# config.active_record.auto_explain_threshold_in_seconds = 0.5
end

View File

@@ -0,0 +1,12 @@
require 'rails_helper'
feature 'swagger-ui', js: true do
scenario 'browsing api-docs' do
visit '/api-docs'
expect(page).to have_content('GET /blogs Searches blogs')
expect(page).to have_content('POST /blogs Creates a blog')
expect(page).to have_content('GET /blogs/{id} Retrieves a blog')
end
end

View File

@@ -41,16 +41,27 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
end end
path '/blogs/{id}' do path '/blogs/{id}' do
parameter name: :id, :in => :path, :type => :string
get 'Retrieves a blog' do get 'Retrieves a blog' do
tags 'Blogs' tags 'Blogs'
description 'Retrieves a specific blog by id' description 'Retrieves a specific blog by id'
operationId 'getBlog' operationId 'getBlog'
produces 'application/json' produces 'application/json'
parameter name: :id, :in => :path, :type => :string
response '200', 'blog found' do response '200', 'blog found' do
header 'ETag', type: :string
header 'Last-Modified', type: :string
header 'Cache-Control', type: :string
schema '$ref' => '#/definitions/blog' schema '$ref' => '#/definitions/blog'
examples 'application/json' => {
id: 1,
title: 'Hello world!',
content: 'Hello world and hello universe. Thank you all very much!!!'
}
let(:blog) { Blog.create(title: 'foo', content: 'bar') } let(:blog) { Blog.create(title: 'foo', content: 'bar') }
let(:id) { blog.id } let(:id) { blog.id }
run_test! run_test!

View File

@@ -50,4 +50,6 @@ RSpec.configure do |config|
config.filter_rails_from_backtrace! config.filter_rails_from_backtrace!
# arbitrary gems may also be filtered via: # arbitrary gems may also be filtered via:
# config.filter_gems_from_backtrace("gem name") # config.filter_gems_from_backtrace("gem name")
Capybara.javascript_driver = :webkit
end end

View File

@@ -51,9 +51,9 @@ RSpec.configure do |config|
in: :query in: :query
} }
}, },
security: { security: [
api_key: [] { api_key: [] }
} ]
} }
} }
end end

View File

@@ -68,6 +68,14 @@
} }
}, },
"/blogs/{id}": { "/blogs/{id}": {
"parameters": [
{
"name": "id",
"in": "path",
"type": "string",
"required": true
}
],
"get": { "get": {
"summary": "Retrieves a blog", "summary": "Retrieves a blog",
"tags": [ "tags": [
@@ -78,19 +86,29 @@
"produces": [ "produces": [
"application/json" "application/json"
], ],
"parameters": [
{
"name": "id",
"in": "path",
"type": "string",
"required": true
}
],
"responses": { "responses": {
"200": { "200": {
"description": "blog found", "description": "blog found",
"headers": {
"ETag": {
"type": "string"
},
"Last-Modified": {
"type": "string"
},
"Cache-Control": {
"type": "string"
}
},
"schema": { "schema": {
"$ref": "#/definitions/blog" "$ref": "#/definitions/blog"
},
"examples": {
"application/json": {
"id": 1,
"title": "Hello world!",
"content": "Hello world and hello universe. Thank you all very much!!!"
}
} }
}, },
"404": { "404": {
@@ -145,9 +163,11 @@
"in": "query" "in": "query"
} }
}, },
"security": { "security": [
{
"api_key": [ "api_key": [
] ]
} }
]
} }