mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-25 23:32:58 +00:00
Compare commits
48 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 | ||
|
|
741f0dc240 | ||
|
|
3404fa72aa | ||
|
|
44840ab836 | ||
|
|
521ab14eaf | ||
|
|
8f5cb1aa12 | ||
|
|
519eb74cdd | ||
|
|
de7ec5f15d | ||
|
|
e40c5fc26e | ||
|
|
1de29136ef | ||
|
|
88449944e1 | ||
|
|
e03a6d333f | ||
|
|
215aaddb7d | ||
|
|
925b754233 | ||
|
|
69c7decce1 | ||
|
|
353be669e4 | ||
|
|
75d676d753 | ||
|
|
1b3a976313 | ||
|
|
2617fdc48d | ||
|
|
724ede0076 | ||
|
|
8aeb9a6c70 | ||
|
|
182ee093f4 | ||
|
|
25d8adaf8b | ||
|
|
f195c82759 | ||
|
|
37f86f6d94 | ||
|
|
51c9f4e5e6 | ||
|
|
233c5cc735 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -3,3 +3,5 @@
|
||||
**/*/*.gem
|
||||
**/*/*.sqlite3
|
||||
**/*/public/assets
|
||||
*.swp
|
||||
Gemfile.lock
|
||||
|
||||
1
.ruby-version
Normal file
1
.ruby-version
Normal file
@@ -0,0 +1 @@
|
||||
2.3.1
|
||||
68
.travis.yml
68
.travis.yml
@@ -1,14 +1,64 @@
|
||||
nguage: ruby
|
||||
language: ruby
|
||||
|
||||
rvm:
|
||||
- 2.2.5
|
||||
|
||||
env:
|
||||
- "RAILS_VERSION=3.2.22"
|
||||
- "RAILS_VERSION=4.2.0"
|
||||
- "RAILS_VERSION=5.0.0"
|
||||
cache: bundler
|
||||
install: bundle update
|
||||
- RAILS_VERSION=5.1.2
|
||||
- RAILS_VERSION=4.2.0
|
||||
- RAILS_VERSION=3.2.22
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- /home/travis/.rvm/gems/ruby-2.2.5
|
||||
|
||||
install: bundle install
|
||||
|
||||
before_script:
|
||||
- "export DISPLAY=:99.0"
|
||||
- "sh -e /etc/init.d/xvfb start"
|
||||
- sleep 3 # give xvfb some time to start
|
||||
- export DISPLAY=:99.0
|
||||
- sh -e /etc/init.d/xvfb start
|
||||
- sleep 3
|
||||
|
||||
script: ./ci/test.sh
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: publish components
|
||||
script: 'cd rswag-api'
|
||||
deploy:
|
||||
gemspec: rswag-api.gemspec
|
||||
provider: rubygems
|
||||
api_key: $RUBYGEMS_API_KEY
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
- stage: publish components
|
||||
script: 'cd rswag-specs'
|
||||
deploy:
|
||||
gemspec: rswag-specs.gemspec
|
||||
provider: rubygems
|
||||
api_key: $RUBYGEMS_API_KEY
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
- stage: publish components
|
||||
script: 'cd rswag-ui'
|
||||
deploy:
|
||||
gemspec: rswag-ui.gemspec
|
||||
provider: rubygems
|
||||
api_key: $RUBYGEMS_API_KEY
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
- stage: publish rswag
|
||||
script: 'cd rswag'
|
||||
deploy:
|
||||
gemspec: rswag.gemspec
|
||||
provider: rubygems
|
||||
api_key: $RUBYGEMS_API_KEY
|
||||
on:
|
||||
branch: master
|
||||
tags: true
|
||||
|
||||
5
Gemfile
5
Gemfile
@@ -2,7 +2,7 @@ source "https://rubygems.org"
|
||||
|
||||
# Allow the rails version to come from an ENV setting so Travis can test multiple versions.
|
||||
# See http://www.schneems.com/post/50991826838/testing-against-multiple-rails-versions/
|
||||
rails_version = ENV['RAILS_VERSION'] || '3.2.22'
|
||||
rails_version = ENV['RAILS_VERSION'] || '5.1.2'
|
||||
|
||||
gem 'rails', "#{rails_version}"
|
||||
|
||||
@@ -15,6 +15,7 @@ end
|
||||
|
||||
gem 'sqlite3'
|
||||
|
||||
gem 'rswag-specs', path: './rswag-specs'
|
||||
gem 'rswag-api', path: './rswag-api'
|
||||
gem 'rswag-ui', path: './rswag-ui'
|
||||
|
||||
@@ -27,7 +28,7 @@ group :test do
|
||||
gem 'generator_spec'
|
||||
gem 'capybara'
|
||||
gem 'capybara-webkit'
|
||||
gem 'rswag-specs', path: './rswag-specs'
|
||||
gem 'puma'
|
||||
end
|
||||
|
||||
group :assets do
|
||||
|
||||
173
Gemfile.lock
173
Gemfile.lock
@@ -1,173 +0,0 @@
|
||||
PATH
|
||||
remote: ./rswag-api
|
||||
specs:
|
||||
rswag-api (1.2.0)
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
PATH
|
||||
remote: ./rswag-specs
|
||||
specs:
|
||||
rswag-specs (1.2.0)
|
||||
json-schema (~> 2.2)
|
||||
rails (>= 3.1, < 5.1)
|
||||
rspec-rails (>= 2.14, < 4)
|
||||
|
||||
PATH
|
||||
remote: ./rswag-ui
|
||||
specs:
|
||||
rswag-ui (1.2.0)
|
||||
rails (>= 3.1, < 5.1)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actionmailer (3.2.22)
|
||||
actionpack (= 3.2.22)
|
||||
mail (~> 2.5.4)
|
||||
actionpack (3.2.22)
|
||||
activemodel (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
builder (~> 3.0.0)
|
||||
erubis (~> 2.7.0)
|
||||
journey (~> 1.0.4)
|
||||
rack (~> 1.4.5)
|
||||
rack-cache (~> 1.2)
|
||||
rack-test (~> 0.6.1)
|
||||
sprockets (~> 2.2.1)
|
||||
activemodel (3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
builder (~> 3.0.0)
|
||||
activerecord (3.2.22)
|
||||
activemodel (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
arel (~> 3.0.2)
|
||||
tzinfo (~> 0.3.29)
|
||||
activeresource (3.2.22)
|
||||
activemodel (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
activesupport (3.2.22)
|
||||
i18n (~> 0.6, >= 0.6.4)
|
||||
multi_json (~> 1.0)
|
||||
addressable (2.4.0)
|
||||
arel (3.0.3)
|
||||
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)
|
||||
erubis (2.7.0)
|
||||
execjs (2.7.0)
|
||||
generator_spec (0.9.3)
|
||||
activesupport (>= 3.0.0)
|
||||
railties (>= 3.0.0)
|
||||
hike (1.2.3)
|
||||
i18n (0.7.0)
|
||||
journey (1.0.4)
|
||||
json (1.8.3)
|
||||
json-schema (2.7.0)
|
||||
addressable (>= 2.4)
|
||||
libv8 (3.16.14.15)
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
mime-types (1.25.1)
|
||||
mini_portile2 (2.1.0)
|
||||
multi_json (1.12.1)
|
||||
nokogiri (1.6.8.1)
|
||||
mini_portile2 (~> 2.1.0)
|
||||
polyglot (0.3.5)
|
||||
power_assert (0.3.1)
|
||||
rack (1.4.7)
|
||||
rack-cache (1.6.1)
|
||||
rack (>= 0.4)
|
||||
rack-ssl (1.3.4)
|
||||
rack
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (3.2.22)
|
||||
actionmailer (= 3.2.22)
|
||||
actionpack (= 3.2.22)
|
||||
activerecord (= 3.2.22)
|
||||
activeresource (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
bundler (~> 1.0)
|
||||
railties (= 3.2.22)
|
||||
railties (3.2.22)
|
||||
actionpack (= 3.2.22)
|
||||
activesupport (= 3.2.22)
|
||||
rack-ssl (~> 1.3.2)
|
||||
rake (>= 0.8.7)
|
||||
rdoc (~> 3.4)
|
||||
thor (>= 0.14.6, < 2.0)
|
||||
rake (11.3.0)
|
||||
rdoc (3.12.2)
|
||||
json (~> 1.4)
|
||||
ref (2.0.0)
|
||||
rspec-core (3.5.4)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-expectations (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-mocks (3.5.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-rails (3.5.2)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.5.0)
|
||||
rspec-expectations (~> 3.5.0)
|
||||
rspec-mocks (~> 3.5.0)
|
||||
rspec-support (~> 3.5.0)
|
||||
rspec-support (3.5.0)
|
||||
sprockets (2.2.3)
|
||||
hike (~> 1.2)
|
||||
multi_json (~> 1.0)
|
||||
rack (~> 1.0)
|
||||
tilt (~> 1.1, != 1.3.0)
|
||||
sqlite3 (1.3.12)
|
||||
strong_parameters (0.2.3)
|
||||
actionpack (~> 3.0)
|
||||
activemodel (~> 3.0)
|
||||
activesupport (~> 3.0)
|
||||
railties (~> 3.0)
|
||||
test-unit (3.2.1)
|
||||
power_assert
|
||||
therubyracer (0.12.2)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thor (0.19.1)
|
||||
tilt (1.4.1)
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
tzinfo (0.3.51)
|
||||
uglifier (3.0.2)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
capybara
|
||||
capybara-webkit
|
||||
generator_spec
|
||||
rails (= 3.2.22)
|
||||
rspec-rails
|
||||
rswag-api!
|
||||
rswag-specs!
|
||||
rswag-ui!
|
||||
sqlite3
|
||||
strong_parameters
|
||||
test-unit
|
||||
therubyracer
|
||||
uglifier
|
||||
95
README.md
95
README.md
@@ -82,6 +82,11 @@ Once you have an API that can describe itself in Swagger, you've opened the trea
|
||||
let(:id) { 'invalid' }
|
||||
run_test!
|
||||
end
|
||||
|
||||
response '406', 'unsupported accept header' do
|
||||
let(:'Accept') { 'application/foo' }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -103,6 +108,17 @@ If you've used [Swagger](http://swagger.io/specification) before, then the synta
|
||||
|
||||
Take special note of the __run_test!__ method that's called within each response block. This tells rswag to create and execute a corresponding example. It builds and submits a request based on parameter descriptions and corresponding values that have been provided using the rspec "let" syntax. For example, the "post" description in the example above specifies a "body" parameter called "blog". It also lists 2 different responses. For the success case (i.e. the 201 response), notice how "let" is used to set the blog parameter to a value that matches the provided schema. For the failure case (i.e. the 422 response), notice how it's set to a value that does not match the provided schema. When the test is executed, rswag also validates the actual response code and, where applicable, the response body against the provided [JSON Schema](http://json-schema.org/documentation.html).
|
||||
|
||||
If you want to do additional validation on the response, pass a block to the __run_test!__ method:
|
||||
|
||||
```ruby
|
||||
response '201', 'blog created' do
|
||||
run_test! do |response|
|
||||
data = JSON.parse(response.body)
|
||||
expect(data['title']).to eq('foo')
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
If you'd like your specs to be a little more explicit about what's going on here, you can replace the call to __run_test!__ with equivalent "before" and "it" blocks:
|
||||
|
||||
```ruby
|
||||
@@ -119,6 +135,31 @@ response '201', 'blog created' do
|
||||
end
|
||||
```
|
||||
|
||||
### Null Values ###
|
||||
|
||||
This library is currently using JSON::Draft4 for validation of response models. It does not support null as a value. So you can add the property 'x-nullable' to a definition to allow null/nil values to pass.
|
||||
```ruby
|
||||
describe 'Blogs API' do
|
||||
path '/blogs' do
|
||||
post 'Creates a blog' do
|
||||
...
|
||||
|
||||
response '200', 'blog found' do
|
||||
schema type: :object,
|
||||
properties: {
|
||||
id: { type: :integer },
|
||||
title: { type: :string },
|
||||
content: { type: :string, 'x-nullable': true }
|
||||
}
|
||||
....
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
*Note:* the OAI v3 may be released soon(ish?) and include a nullable property. This may have an effect on the need/use of custom extension to the draft. Do not use this property if you don't understand the implications.
|
||||
<https://github.com/OAI/OpenAPI-Specification/issues/229#issuecomment-280376087>
|
||||
|
||||
### Global Metadata ###
|
||||
|
||||
In addition to paths, operations and responses, Swagger also supports global API metadata. When you install rswag, a file called _swagger_helper.rb_ is added to your spec folder. This is where you define one or more Swagger documents and provide global metadata. Again, the format is based on Swagger so most of the global fields supported by the top level ["Swagger" object](http://swagger.io/specification/#swaggerObject) can be provided with each document definition. As an example, you could define a Swagger document for each version of your API and in each case specify a title, version string and URL basePath:
|
||||
@@ -164,6 +205,58 @@ describe 'Blogs API', swagger_doc: 'v2/swagger.json' do
|
||||
end
|
||||
```
|
||||
|
||||
### Specifying/Testing API Security ###
|
||||
|
||||
Swagger allows for the specification of different security schemes and their applicability to operations in an API. To leverage this in rswag, you define the schemes globally in _swagger_helper.rb_ and then use the "security" attribute at the operation level to specify which schemes, if any, are applicable to that operation. Swagger supports :basic, :apiKey and :oauth2 scheme types. See [the spec](http://swagger.io/specification/#security-definitions-object-109) for more info.
|
||||
|
||||
```ruby
|
||||
# spec/swagger_helper.rb
|
||||
RSpec.configure do |config|
|
||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||
|
||||
config.swagger_docs = {
|
||||
'v1/swagger.json' => {
|
||||
...
|
||||
securityDefinitions: {
|
||||
basic: {
|
||||
type: :basic
|
||||
},
|
||||
apiKey: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
in: :query
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
# spec/integration/blogs_spec.rb
|
||||
describe 'Blogs API' do
|
||||
|
||||
path '/blogs' do
|
||||
|
||||
post 'Creates a blog' do
|
||||
tags 'Blogs'
|
||||
security [ basic: [] ]
|
||||
...
|
||||
|
||||
response '201', 'blog created' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||
run_test!
|
||||
end
|
||||
|
||||
response '401', 'authentication failed' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('bogus:bogus')}" }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
__NOTE:__ Depending on the scheme types, you'll be required to assign a corresponding parameter value with each example. For example, :basic auth is required above and so the :Authorization (header) parameter must be set accordingly
|
||||
|
||||
## Configuration & Customization ##
|
||||
|
||||
The steps described above will get you up and running with minimal setup. However, rswag offers a lot of flexibility to customize as you see fit. Before exploring the various options, you'll need to be aware of it's different components. The following table lists each of them and the files that get added/updated as part of a standard install.
|
||||
@@ -371,7 +464,7 @@ end
|
||||
|
||||
### Customizing the swagger-ui ###
|
||||
|
||||
The swagger-ui provides several options for customizing it's behavior, all of which are documented here https://github.com/swagger-api/swagger-ui#swaggerui. If you need to tweak these or customize the overall look and feel of your swagger-ui, then you'll need to provide your own version of index.html. You can do this with the following generator.
|
||||
The swagger-ui provides several options for customizing it's behavior, all of which are documented here https://github.com/swagger-api/swagger-ui/tree/2.x#swaggerui. If you need to tweak these or customize the overall look and feel of your swagger-ui, then you'll need to provide your own version of index.html. You can do this with the following generator.
|
||||
|
||||
```ruby
|
||||
rails g rswag:ui:custom
|
||||
|
||||
58
ci/deploy.sh
58
ci/deploy.sh
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
ROOT_PATH=$PWD
|
||||
set -e # abort if anything fails
|
||||
|
||||
bundle check || bundle
|
||||
|
||||
echo '####################'
|
||||
echo 'Build Gems'
|
||||
echo '####################'
|
||||
echo ''
|
||||
|
||||
echo '##### rswag-api #####'
|
||||
cd $ROOT_PATH/rswag-api
|
||||
gem build rswag-api.gemspec
|
||||
|
||||
echo '##### rswag-specs #####'
|
||||
cd $ROOT_PATH/rswag-specs
|
||||
gem build rswag-specs.gemspec
|
||||
|
||||
echo '##### rswag-ui #####'
|
||||
cd $ROOT_PATH/rswag-ui
|
||||
gem build rswag-ui.gemspec
|
||||
|
||||
echo '##### rswag #####'
|
||||
cd $ROOT_PATH/rswag
|
||||
gem build rswag.gemspec
|
||||
|
||||
echo '####################'
|
||||
echo 'Push to RubyGems'
|
||||
echo '####################'
|
||||
echo ''
|
||||
|
||||
echo 'Type the version no, followed by [ENTER]:'
|
||||
read version
|
||||
|
||||
echo '##### rswag-api #####'
|
||||
cd $ROOT_PATH/rswag-api
|
||||
gem push rswag-api-$version.gem
|
||||
|
||||
echo '##### rswag-specs #####'
|
||||
cd $ROOT_PATH/rswag-specs
|
||||
gem push rswag-specs-$version.gem
|
||||
|
||||
echo '##### rswag-ui #####'
|
||||
cd $ROOT_PATH/rswag-ui
|
||||
gem push rswag-ui-$version.gem
|
||||
|
||||
echo '##### rswag #####'
|
||||
cd $ROOT_PATH/rswag
|
||||
gem push rswag-$version.gem
|
||||
|
||||
# Cleanup
|
||||
cd $ROOT_PATH
|
||||
|
||||
# Create git tag
|
||||
git tag v$version
|
||||
git push origin v$version
|
||||
@@ -1,4 +1,3 @@
|
||||
require 'rswag/api/version'
|
||||
require 'rswag/api/configuration'
|
||||
require 'rswag/api/engine'
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module Rswag
|
||||
module Api
|
||||
VERSION = '1.2.0'
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,9 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require 'rswag/api/version'
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag-api"
|
||||
s.version = Rswag::Api::VERSION
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
@@ -16,5 +13,5 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile"]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||
end
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
require 'rspec/core'
|
||||
require 'rswag/specs/version'
|
||||
require 'rswag/specs/example_group_helpers'
|
||||
require 'rswag/specs/example_helpers'
|
||||
require 'rswag/specs/configuration'
|
||||
|
||||
@@ -2,9 +2,9 @@ module Rswag
|
||||
module Specs
|
||||
module ExampleGroupHelpers
|
||||
|
||||
def path(template, &block)
|
||||
api_metadata = { path_item: { template: template } }
|
||||
describe(template, api_metadata, &block)
|
||||
def path(template, metadata={}, &block)
|
||||
metadata[:path_item] = { template: template }
|
||||
describe(template, metadata, &block)
|
||||
end
|
||||
|
||||
[ :get, :post, :patch, :put, :delete, :head ].each do |verb|
|
||||
@@ -36,7 +36,9 @@ module Rswag
|
||||
end
|
||||
|
||||
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)
|
||||
metadata[:operation][:parameters] ||= []
|
||||
@@ -47,9 +49,9 @@ module Rswag
|
||||
end
|
||||
end
|
||||
|
||||
def response(code, description, &block)
|
||||
api_metadata = { response: { code: code, description: description } }
|
||||
context(description, api_metadata, &block)
|
||||
def response(code, description, metadata={}, &block)
|
||||
metadata[:response] = { code: code, description: description }
|
||||
context(description, metadata, &block)
|
||||
end
|
||||
|
||||
def schema(value)
|
||||
@@ -69,7 +71,7 @@ module Rswag
|
||||
metadata[:response][:examples] = example
|
||||
end
|
||||
|
||||
def run_test!
|
||||
def run_test!(&block)
|
||||
# NOTE: rspec 2.x support
|
||||
if RSPEC_VERSION < 3
|
||||
before do
|
||||
@@ -77,7 +79,8 @@ module Rswag
|
||||
end
|
||||
|
||||
it "returns a #{metadata[:response][:code]} response" do
|
||||
assert_response_matches_metadata(example.metadata)
|
||||
assert_response_matches_metadata(metadata)
|
||||
block.call(response) if block_given?
|
||||
end
|
||||
else
|
||||
before do |example|
|
||||
@@ -85,7 +88,8 @@ module Rswag
|
||||
end
|
||||
|
||||
it "returns a #{metadata[:response][:code]} response" do |example|
|
||||
assert_response_matches_metadata(example.metadata)
|
||||
assert_response_matches_metadata(example.metadata, &block)
|
||||
example.instance_exec(response, &block) if block_given?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,39 +5,30 @@ module Rswag
|
||||
module Specs
|
||||
module ExampleHelpers
|
||||
|
||||
def submit_request(api_metadata)
|
||||
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
|
||||
factory = RequestFactory.new(api_metadata, global_metadata)
|
||||
def submit_request(metadata)
|
||||
request = RequestFactory.new.build_request(metadata, self)
|
||||
|
||||
if RAILS_VERSION < 5
|
||||
send(
|
||||
api_metadata[:operation][:verb],
|
||||
factory.build_fullpath(self),
|
||||
factory.build_body(self),
|
||||
factory.build_headers(self)
|
||||
request[:verb],
|
||||
request[:path],
|
||||
request[:payload],
|
||||
request[:headers]
|
||||
)
|
||||
else
|
||||
send(
|
||||
api_metadata[:operation][:verb],
|
||||
factory.build_fullpath(self),
|
||||
request[:verb],
|
||||
request[:path],
|
||||
{
|
||||
params: factory.build_body(self),
|
||||
headers: factory.build_headers(self)
|
||||
params: request[:payload],
|
||||
headers: request[:headers]
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def assert_response_matches_metadata(api_metadata)
|
||||
global_metadata = rswag_config.get_swagger_doc(api_metadata[:swagger_doc])
|
||||
validator = ResponseValidator.new(api_metadata, global_metadata)
|
||||
validator.validate!(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def rswag_config
|
||||
::Rswag::Specs.config
|
||||
def assert_response_matches_metadata(metadata)
|
||||
ResponseValidator.new.validate!(metadata, response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,13 +2,13 @@ require 'json-schema'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class ExtendedSchema < JSON::Schema::Validator
|
||||
class ExtendedSchema < JSON::Schema::Draft4
|
||||
|
||||
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')
|
||||
@names = ['http://tempuri.org/rswag/specs/extended_schema']
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,85 +1,82 @@
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'active_support/core_ext/hash/conversions'
|
||||
require 'json'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class RequestFactory
|
||||
|
||||
def initialize(api_metadata, global_metadata)
|
||||
@api_metadata = api_metadata
|
||||
@global_metadata = global_metadata
|
||||
def initialize(config = ::Rswag::Specs.config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def build_fullpath(example)
|
||||
@api_metadata[:path_item][:template].dup.tap do |t|
|
||||
t.prepend(@global_metadata[:basePath] || '')
|
||||
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
|
||||
t.concat(build_query_string(example))
|
||||
end
|
||||
end
|
||||
def build_request(metadata, example)
|
||||
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||
parameters = expand_parameters(metadata, swagger_doc, example)
|
||||
|
||||
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)
|
||||
headers = Hash[ parameters_in(:header).map { |p| [ p[:name], example.send(p[:name]).to_s ] } ]
|
||||
headers.tap do |h|
|
||||
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
|
||||
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
|
||||
h['ACCEPT'] = produces.join(';') unless produces.nil?
|
||||
h['CONTENT_TYPE'] = consumes.join(';') unless consumes.nil?
|
||||
{}.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
|
||||
|
||||
private
|
||||
|
||||
def parameters_in(location)
|
||||
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
|
||||
def expand_parameters(metadata, swagger_doc, example)
|
||||
operation_params = metadata[:operation][:parameters] || []
|
||||
path_item_params = metadata[:path_item][:parameters] || []
|
||||
security_params = derive_security_params(metadata, swagger_doc)
|
||||
|
||||
applicable_params
|
||||
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
|
||||
.concat(resolve_api_key_parameters)
|
||||
.select { |p| p[:in] == location }
|
||||
# NOTE: Use of + instead of concat to avoid mutation of the metadata object
|
||||
(operation_params + path_item_params + security_params)
|
||||
.map { |p| p['$ref'] ? resolve_parameter(p['$ref'], swagger_doc) : p }
|
||||
.uniq { |p| p[:name] }
|
||||
.reject { |p| p[:required] == false && !example.respond_to?(p[:name]) }
|
||||
end
|
||||
|
||||
def resolve_parameter(ref)
|
||||
defined_params = @global_metadata[:parameters]
|
||||
key = ref.sub('#/parameters/', '')
|
||||
raise "Referenced parameter '#{ref}' must be defined" unless defined_params && defined_params[key]
|
||||
defined_params[key]
|
||||
def derive_security_params(metadata, swagger_doc)
|
||||
requirements = metadata[:operation][:security] || swagger_doc[:security] || []
|
||||
scheme_names = requirements.flat_map { |r| r.keys }
|
||||
schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
|
||||
|
||||
schemes.map do |scheme|
|
||||
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
||||
param.merge(type: :string, required: requirements.one?)
|
||||
end
|
||||
end
|
||||
|
||||
def resolve_api_key_parameters
|
||||
@api_key_params ||= begin
|
||||
# First figure out the security requirement applicable to the operation
|
||||
global_requirements = (@global_metadata[:security] || [] ).map { |r| r.keys.first }
|
||||
operation_requirements = (@api_metadata[:operation][:security] || [] ).map { |r| r.keys.first }
|
||||
requirements = global_requirements | operation_requirements
|
||||
def resolve_parameter(ref, swagger_doc)
|
||||
key = ref.sub('#/parameters/', '').to_sym
|
||||
definitions = swagger_doc[:parameters]
|
||||
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
|
||||
definitions[key]
|
||||
end
|
||||
|
||||
# Then obtain the scheme definitions for those requirements
|
||||
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements)
|
||||
definitions.values.select { |d| d[:type] == :apiKey }
|
||||
def add_verb(request, metadata)
|
||||
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
|
||||
|
||||
def build_query_string_part(param, value)
|
||||
return "#{param[:name]}=#{value.to_s}" unless param[:type].to_sym == :array
|
||||
|
||||
name = param[:name]
|
||||
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
|
||||
|
||||
case param[:collectionFormat]
|
||||
when :ssv
|
||||
"#{name}=#{value.join(' ')}"
|
||||
@@ -93,6 +90,68 @@ module Rswag
|
||||
"#{name}=#{value.join(',')}" # csv is default
|
||||
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
|
||||
|
||||
@@ -1,51 +1,49 @@
|
||||
require 'active_support/core_ext/hash/slice'
|
||||
require 'json-schema'
|
||||
require 'json'
|
||||
require 'rswag/specs/extended_schema'
|
||||
|
||||
module Rswag
|
||||
module Specs
|
||||
class ResponseValidator
|
||||
|
||||
def initialize(api_metadata, global_metadata)
|
||||
@api_metadata = api_metadata
|
||||
@global_metadata = global_metadata
|
||||
def initialize(config = ::Rswag::Specs.config)
|
||||
@config = config
|
||||
end
|
||||
|
||||
def validate!(response)
|
||||
validate_code!(response.code)
|
||||
validate_headers!(response.headers)
|
||||
validate_body!(response.body)
|
||||
def validate!(metadata, response)
|
||||
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||
|
||||
validate_code!(metadata, response.code)
|
||||
validate_headers!(metadata, response.headers)
|
||||
validate_body!(metadata, swagger_doc, response.body)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_code!(code)
|
||||
if code.to_s != @api_metadata[:response][:code].to_s
|
||||
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{@api_metadata[:response][:code]}'"
|
||||
def validate_code!(metadata, code)
|
||||
expected = metadata[:response][:code].to_s
|
||||
if code != expected
|
||||
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{expected}'"
|
||||
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?
|
||||
def validate_headers!(metadata, headers)
|
||||
expected = (metadata[:response][:headers] || {}).keys
|
||||
expected.each do |name|
|
||||
raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil?
|
||||
end
|
||||
end
|
||||
|
||||
def validate_body!(body)
|
||||
response_schema = @api_metadata[:response][:schema]
|
||||
def validate_body!(metadata, swagger_doc, body)
|
||||
response_schema = metadata[:response][:schema]
|
||||
return if response_schema.nil?
|
||||
|
||||
begin
|
||||
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
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
|
||||
end
|
||||
.merge(swagger_doc.slice(:definitions))
|
||||
errors = JSON::Validator.fully_validate(validation_schema, body)
|
||||
raise UnexpectedResponse, "Expected response body to match schema: #{errors[0]}" if errors.any?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module Rswag
|
||||
module Specs
|
||||
VERSION = '1.2.0'
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,9 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require 'rswag/specs/version'
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag-specs"
|
||||
s.version = Rswag::Specs::VERSION
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
@@ -16,7 +13,7 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
s.add_dependency 'activesupport', '>= 3.1', '< 6.0'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||
s.add_dependency 'json-schema', '~> 2.2'
|
||||
s.add_dependency 'rspec-rails', '>= 2.14', '< 4'
|
||||
end
|
||||
|
||||
@@ -120,6 +120,15 @@ module Rswag
|
||||
)
|
||||
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
|
||||
|
||||
describe '#response(code, description)' do
|
||||
|
||||
@@ -7,19 +7,30 @@ module Rswag
|
||||
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)
|
||||
subject.extend(ExampleHelpers)
|
||||
allow(Rswag::Specs).to receive(:config).and_return(config)
|
||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||
stub_const('Rswag::Specs::RAILS_VERSION', 3)
|
||||
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}' },
|
||||
operation: {
|
||||
verb: :put,
|
||||
summary: 'Updates a blog',
|
||||
consumes: [ 'application/json' ],
|
||||
parameters: [
|
||||
{ name: :blog_id, in: :path, type: 'integer' },
|
||||
{ name: 'id', in: :path, type: 'integer' },
|
||||
@@ -32,19 +43,8 @@ module Rswag
|
||||
}
|
||||
}
|
||||
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
|
||||
allow(subject).to receive(:blog_id).and_return(1)
|
||||
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(:blog).and_return(text: 'Some comment')
|
||||
allow(subject).to receive(:put)
|
||||
subject.submit_request(api_metadata)
|
||||
subject.submit_request(metadata)
|
||||
end
|
||||
|
||||
it "submits a request built from metadata and 'let' values" do
|
||||
expect(subject).to have_received(:put).with(
|
||||
'/blogs/1/comments/2?q1=foo&api_key=fookey',
|
||||
"{\"text\":\"Some comment\"}",
|
||||
{}
|
||||
{ 'CONTENT_TYPE' => 'application/json' }
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,222 +4,335 @@ module Rswag
|
||||
module Specs
|
||||
|
||||
describe RequestFactory do
|
||||
subject { RequestFactory.new(api_metadata, global_metadata) }
|
||||
subject { RequestFactory.new(config) }
|
||||
|
||||
before do
|
||||
allow(example).to receive(:blog_id).and_return(1)
|
||||
allow(example).to receive(:id).and_return('2')
|
||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||
end
|
||||
let(:api_metadata) do
|
||||
{
|
||||
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(:config) { double('config') }
|
||||
let(:swagger_doc) { {} }
|
||||
let(:example) { double('example') }
|
||||
let(:metadata) do
|
||||
{
|
||||
path_item: { template: '/blogs' },
|
||||
operation: { verb: :get }
|
||||
}
|
||||
end
|
||||
|
||||
describe '#build_fullpath(example)' do
|
||||
let(:path) { subject.build_fullpath(example) }
|
||||
describe '#build_request(metadata, example)' do
|
||||
let(:request) { subject.build_request(metadata, example) }
|
||||
|
||||
context 'always' do
|
||||
it "builds a path using metadata and example values" do
|
||||
expect(path).to eq('/blogs/1/comments/2')
|
||||
it 'builds request hash for given example' do
|
||||
expect(request[:verb]).to eq(:get)
|
||||
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
|
||||
|
||||
context "'query' parameters" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << { name: 'q1', in: :query, type: 'string' }
|
||||
api_metadata[:operation][:parameters] << { name: 'q2', in: :query, type: 'string' }
|
||||
metadata[:operation][:parameters] = [
|
||||
{ name: 'q1', in: :query, type: :string },
|
||||
{ name: 'q2', in: :query, type: :string }
|
||||
]
|
||||
allow(example).to receive(:q1).and_return('foo')
|
||||
allow(example).to receive(:q2).and_return('bar')
|
||||
end
|
||||
|
||||
it "appends a query string using metadata and example values" do
|
||||
expect(path).to eq('/blogs/1/comments/2?q1=foo&q2=bar')
|
||||
it "builds the query string from example values" do
|
||||
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||
end
|
||||
end
|
||||
|
||||
context "'query' parameter of type 'array'" do
|
||||
context "'query' parameters of type 'array'" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << {
|
||||
name: 'things',
|
||||
in: :query,
|
||||
type: :array,
|
||||
collectionFormat: collectionFormat
|
||||
}
|
||||
metadata[:operation][:parameters] = [
|
||||
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format }
|
||||
]
|
||||
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
|
||||
end
|
||||
|
||||
context 'collectionFormat = csv' do
|
||||
let(:collectionFormat) { :csv }
|
||||
let(:collection_format) { :csv }
|
||||
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
|
||||
|
||||
context 'collectionFormat = ssv' do
|
||||
let(:collectionFormat) { :ssv }
|
||||
let(:collection_format) { :ssv }
|
||||
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
|
||||
|
||||
context 'collectionFormat = tsv' do
|
||||
let(:collectionFormat) { :tsv }
|
||||
let(:collection_format) { :tsv }
|
||||
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
|
||||
|
||||
context 'collectionFormat = pipes' do
|
||||
let(:collectionFormat) { :pipes }
|
||||
let(:collection_format) { :pipes }
|
||||
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
|
||||
|
||||
context 'collectionFormat = multi' do
|
||||
let(:collectionFormat) { :multi }
|
||||
let(:collection_format) { :multi }
|
||||
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
|
||||
|
||||
context "global definition for 'api_key in query'" do
|
||||
context "'header' parameters" do
|
||||
before do
|
||||
global_metadata[:securityDefinitions] = { api_key: { type: :apiKey, name: 'api_key', in: :query } }
|
||||
allow(example).to receive(:api_key).and_return('fookey')
|
||||
metadata[:operation][:parameters] = [ { name: 'Api-Key', in: :header, type: :string } ]
|
||||
allow(example).to receive(:'Api-Key').and_return('foobar')
|
||||
end
|
||||
|
||||
context 'global requirement' do
|
||||
before { global_metadata[:security] = [ { api_key: [] } ] }
|
||||
|
||||
it "appends the api_key using metadata and example value" do
|
||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
||||
it 'adds names and example values to headers' do
|
||||
expect(request[:headers]).to eq({ 'Api-Key' => 'foobar' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'operation-specific requirement' do
|
||||
before { api_metadata[:operation][:security] = [ { api_key: [] } ] }
|
||||
|
||||
it "appends the api_key using metadata and example value" do
|
||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
||||
context 'optional parameters not provided' do
|
||||
before do
|
||||
metadata[:operation][:parameters] = [
|
||||
{ name: 'q1', in: :query, type: :string, required: false },
|
||||
{ name: 'Api-Key', in: :header, type: :string, required: false }
|
||||
]
|
||||
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
|
||||
|
||||
context 'global basePath' do
|
||||
before { global_metadata[:basePath] = '/foobar' }
|
||||
before { swagger_doc[:basePath] = '/api' }
|
||||
|
||||
it 'prepends the basePath' do
|
||||
expect(path).to eq('/foobar/blogs/1/comments/2')
|
||||
it 'prepends to the path' do
|
||||
expect(request[:path]).to eq('/api/blogs')
|
||||
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
|
||||
api_metadata[:path_item][:parameters] = [ { name: :blog_id, in: :path } ]
|
||||
api_metadata[:operation][:parameters] = [ { name: :id, in: :path } ]
|
||||
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: :query } }
|
||||
swagger_doc[:security] = [ apiKey: [] ]
|
||||
allow(example).to receive(:api_key).and_return('foobar')
|
||||
end
|
||||
|
||||
it "builds path from parameters defined at path and operation levels" do
|
||||
expect(path).to eq('/blogs/1/comments/2')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_body(example)' do
|
||||
let(:body) { subject.build_body(example) }
|
||||
|
||||
context "no 'body' parameter" do
|
||||
it "returns ''" do
|
||||
expect(body).to eq('')
|
||||
end
|
||||
end
|
||||
|
||||
context "'body' parameter" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << { name: 'comment', in: :body, schema: { type: 'object' } }
|
||||
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
||||
end
|
||||
|
||||
it 'returns the example value as a json string' do
|
||||
expect(body).to eq("{\"text\":\"Some comment\"}")
|
||||
end
|
||||
end
|
||||
|
||||
context "referenced 'body' parameter" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << { '$ref' => '#/parameters/comment' }
|
||||
global_metadata[:parameters] = {
|
||||
'comment' => { name: 'comment', in: :body, schema: { type: 'object' } }
|
||||
}
|
||||
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
||||
end
|
||||
|
||||
it 'returns the example value as a json string' do
|
||||
expect(body).to eq("{\"text\":\"Some comment\"}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#build_headers' do
|
||||
let(:headers) { subject.build_headers(example) }
|
||||
|
||||
context "no 'header' params" do
|
||||
it 'returns an empty hash' do
|
||||
expect(headers).to eq({})
|
||||
end
|
||||
end
|
||||
|
||||
context "'header' params" do
|
||||
before do
|
||||
api_metadata[:operation][:parameters] << { name: 'Api-Key', in: :header, type: 'string' }
|
||||
allow(example).to receive(:'Api-Key').and_return('foobar')
|
||||
end
|
||||
|
||||
it 'returns a hash of names with example values' do
|
||||
expect(headers).to eq({ 'Api-Key' => 'foobar' })
|
||||
end
|
||||
end
|
||||
|
||||
context 'consumes & produces' do
|
||||
before do
|
||||
api_metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
|
||||
api_metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
|
||||
end
|
||||
|
||||
it "includes corresponding 'Accept' & 'Content-Type' headers" do
|
||||
expect(headers).to match(
|
||||
'ACCEPT' => 'application/json;application/xml',
|
||||
'CONTENT_TYPE' => 'application/json;application/xml'
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
context 'global consumes & produces' do
|
||||
let(:global_metadata) do
|
||||
{
|
||||
consumes: [ 'application/json', 'application/xml' ],
|
||||
produces: [ 'application/json', 'application/xml' ]
|
||||
}
|
||||
end
|
||||
|
||||
it "includes corresponding 'Accept' & 'Content-Type' headers" do
|
||||
expect(headers).to match(
|
||||
'ACCEPT' => 'application/json;application/xml',
|
||||
'CONTENT_TYPE' => 'application/json;application/xml'
|
||||
)
|
||||
it 'applieds the scheme by default' do
|
||||
expect(request[:path]).to eq('/blogs?api_key=foobar')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,102 +4,71 @@ module Rswag
|
||||
module Specs
|
||||
|
||||
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
|
||||
api_metadata[:response][:schema] = {
|
||||
type: 'object',
|
||||
properties: { text: { type: 'string' } },
|
||||
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||
end
|
||||
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' ]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
context 'response code & body matches' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some comment\"}") }
|
||||
describe '#validate!(metadata, response)' do
|
||||
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 }
|
||||
end
|
||||
|
||||
context 'response code matches & body does not' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") }
|
||||
it { expect { call }.to raise_error UnexpectedResponse }
|
||||
end
|
||||
context "response code differs from metadata" do
|
||||
before { response.code = '400' }
|
||||
it { expect { call }.to raise_error /Expected response code/ }
|
||||
end
|
||||
|
||||
context "referenced 'schema' provided" do
|
||||
context "response headers differ from metadata" do
|
||||
before { response.headers = {} }
|
||||
it { expect { call }.to raise_error /Expected response header/ }
|
||||
end
|
||||
|
||||
context "response body differs from metadata" do
|
||||
before { response.body = "{\"foo\":\"Some comment\"}" }
|
||||
it { expect { call }.to raise_error /Expected response body/ }
|
||||
end
|
||||
|
||||
context 'referenced schemas' do
|
||||
before do
|
||||
api_metadata[:response][:schema] = { '$ref' => '#/definitions/author' }
|
||||
global_metadata[:definitions] = {
|
||||
author: {
|
||||
type: 'object',
|
||||
properties: { name: { type: 'string' } },
|
||||
required: [ 'name' ]
|
||||
swagger_doc[:definitions] = {
|
||||
'blog' => {
|
||||
type: :object,
|
||||
properties: { foo: { type: :string } },
|
||||
required: [ 'foo' ]
|
||||
}
|
||||
}
|
||||
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
|
||||
end
|
||||
|
||||
context 'response code & body matches' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"name\":\"Some name\"}") }
|
||||
it { expect { call }.to_not raise_error }
|
||||
end
|
||||
|
||||
context 'response code matches & body does not' do
|
||||
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some name\"}") }
|
||||
it { expect { call }.to raise_error UnexpectedResponse }
|
||||
end
|
||||
end
|
||||
|
||||
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 }
|
||||
it 'uses the referenced schema to validate the response body' do
|
||||
expect { call }.to raise_error /Expected response body/
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
require 'rswag/ui/version'
|
||||
require 'rswag/ui/configuration'
|
||||
require 'rswag/ui/engine'
|
||||
|
||||
|
||||
@@ -5,7 +5,14 @@ module Rswag
|
||||
|
||||
initializer 'rswag-ui.initialize' do |app|
|
||||
if app.config.respond_to?(:assets)
|
||||
app.config.assets.precompile += [ 'swagger-ui/*' ]
|
||||
app.config.assets.precompile += [
|
||||
'swagger-ui/css/*',
|
||||
'swagger-ui/fonts/*',
|
||||
'swagger-ui/images/*',
|
||||
'swagger-ui/lang/*',
|
||||
'swagger-ui/lib/*',
|
||||
'swagger-ui/swagger-ui.min.js'
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
module Rswag
|
||||
module Ui
|
||||
VERSION = '1.2.0'
|
||||
end
|
||||
end
|
||||
@@ -1,12 +1,9 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require 'rswag/ui/version'
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag-ui"
|
||||
s.version = Rswag::Ui::VERSION
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
@@ -16,5 +13,6 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.files = Dir["{app,config,lib,vendor}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
||||
|
||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
||||
s.add_dependency 'actionpack', '>=3.1', '< 6.0'
|
||||
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||
end
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
require 'rswag/version'
|
||||
require 'rswag/specs'
|
||||
require 'rswag/api'
|
||||
require 'rswag/ui'
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
module Rswag
|
||||
VERSION = '1.2.0'
|
||||
end
|
||||
@@ -1,12 +1,9 @@
|
||||
$:.push File.expand_path("../lib", __FILE__)
|
||||
|
||||
# Maintain your gem's version:
|
||||
require 'rswag/version'
|
||||
|
||||
# Describe your gem and declare its dependencies:
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "rswag"
|
||||
s.version = Rswag::VERSION
|
||||
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.authors = ["Richie Morris"]
|
||||
s.email = ["domaindrivendev@gmail.com"]
|
||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||
@@ -16,7 +13,7 @@ Gem::Specification.new do |s|
|
||||
|
||||
s.files = Dir["{lib}/**/*"] + [ "MIT-LICENSE" ]
|
||||
|
||||
s.add_dependency 'rswag-specs', Rswag::VERSION
|
||||
s.add_dependency 'rswag-api', Rswag::VERSION
|
||||
s.add_dependency 'rswag-ui', Rswag::VERSION
|
||||
s.add_dependency 'rswag-specs', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.add_dependency 'rswag-api', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
s.add_dependency 'rswag-ui', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||
end
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
class ApplicationController < ActionController::Base
|
||||
# Prevent CSRF attacks by raising an exception.
|
||||
# For APIs, you may want to use :null_session instead.
|
||||
protect_from_forgery with: :exception
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
wrap_parameters format: [ :json ]
|
||||
|
||||
respond_to :json
|
||||
rescue_from 'ActionController::UnknownFormat' do |ex|
|
||||
head :not_acceptable
|
||||
end
|
||||
end
|
||||
|
||||
30
test-app/app/controllers/auth_tests_controller.rb
Normal file
30
test-app/app/controllers/auth_tests_controller.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
class AuthTestsController < ApplicationController
|
||||
|
||||
# POST /auth-tests/basic
|
||||
def basic
|
||||
return head :unauthorized unless authenticate_basic
|
||||
head :no_content
|
||||
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
|
||||
@@ -1,6 +1,6 @@
|
||||
require 'fileutils'
|
||||
|
||||
class BlogsController < ApplicationController
|
||||
wrap_parameters Blog
|
||||
respond_to :json
|
||||
|
||||
# POST /blogs
|
||||
def create
|
||||
@@ -8,6 +8,14 @@ class BlogsController < ApplicationController
|
||||
respond_with @blog
|
||||
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
|
||||
def index
|
||||
@blogs = Blog.all
|
||||
@@ -24,4 +32,13 @@ class BlogsController < ApplicationController
|
||||
respond_with @blog, status: :not_found and return unless @blog
|
||||
respond_with @blog
|
||||
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
|
||||
|
||||
@@ -5,7 +5,8 @@ class Blog < ActiveRecord::Base
|
||||
{
|
||||
id: id,
|
||||
title: title,
|
||||
content: content
|
||||
content: nil,
|
||||
thumbnail: thumbnail
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
require 'rubygems'
|
||||
gemfile = File.expand_path('../../../../Gemfile', __FILE__)
|
||||
gemfile = File.expand_path('../../../Gemfile', __FILE__)
|
||||
|
||||
if File.exist?(gemfile)
|
||||
ENV['BUNDLE_GEMFILE'] = gemfile
|
||||
|
||||
@@ -28,4 +28,6 @@ TestApp::Application.configure do
|
||||
|
||||
# Expands the lines which load the assets
|
||||
config.assets.debug = true
|
||||
|
||||
config.eager_load = false
|
||||
end
|
||||
|
||||
@@ -1,67 +0,0 @@
|
||||
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
|
||||
@@ -32,4 +32,6 @@ TestApp::Application.configure do
|
||||
|
||||
# Print deprecation notices to the stderr
|
||||
config.active_support.deprecation = :stderr
|
||||
|
||||
config.eager_load = false
|
||||
end
|
||||
|
||||
@@ -5,3 +5,6 @@
|
||||
# Make sure the secret is at least 30 characters and all random,
|
||||
# no regular words or you'll be exposed to dictionary attacks.
|
||||
TestApp::Application.config.secret_token = '60f36cd33756d73f362053f1d45256ae50d75440b634ae73b070a6e35a2df38692f59e28e5ecbd1f9f2e850255f6d29a468bc59ac4484c2b7f0548ddbfc1b870'
|
||||
|
||||
# See http://guides.rubyonrails.org/upgrading_ruby_on_rails.html#config-secrets-yml
|
||||
TestApp::Application.config.secret_key_base = 'f6a820cc8aa76094583cd68ef46a735e25e3278648086355f8bd24721f036959c728c06a28dcecfe695f17ae2db44dfa1424f22b81377f2a1496d4e19f6f7faa'
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
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/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::Ui::Engine => 'api-docs'
|
||||
|
||||
@@ -1,8 +1,15 @@
|
||||
class CreateBlogs < ActiveRecord::Migration
|
||||
migration_class = if Gem::Version.new(Rails.version) >= Gem::Version.new("5.0")
|
||||
ActiveRecord::Migration[4.2]
|
||||
else
|
||||
ActiveRecord::Migration
|
||||
end
|
||||
|
||||
class CreateBlogs < migration_class
|
||||
def change
|
||||
create_table :blogs do |t|
|
||||
t.string :title
|
||||
t.text :content
|
||||
t.string :thumbnail
|
||||
|
||||
t.timestamps
|
||||
end
|
||||
|
||||
@@ -16,6 +16,7 @@ ActiveRecord::Schema.define(:version => 20160218212104) do
|
||||
create_table "blogs", :force => true do |t|
|
||||
t.string "title"
|
||||
t.text "content"
|
||||
t.string "thumbnail"
|
||||
t.datetime "created_at", :null => false
|
||||
t.datetime "updated_at", :null => false
|
||||
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 |
60
test-app/spec/integration/auth_tests_spec.rb
Normal file
60
test-app/spec/integration/auth_tests_spec.rb
Normal file
@@ -0,0 +1,60 @@
|
||||
require 'swagger_helper'
|
||||
|
||||
describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
|
||||
path '/auth-tests/basic' do
|
||||
post 'Authenticates with basic auth' do
|
||||
tags 'Auth Tests'
|
||||
operationId 'testBasicAuth'
|
||||
security [ basic_auth: [] ]
|
||||
|
||||
response '204', 'Valid credentials' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" }
|
||||
run_test!
|
||||
end
|
||||
|
||||
response '401', 'Invalid credentials' do
|
||||
let(:Authorization) { "Basic #{::Base64.strict_encode64('foo:bar')}" }
|
||||
run_test!
|
||||
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
|
||||
@@ -9,10 +9,12 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
description 'Creates a new blog from provided data'
|
||||
operationId 'createBlog'
|
||||
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
|
||||
let(:blog) { { title: 'foo', content: 'bar' } }
|
||||
run_test!
|
||||
end
|
||||
|
||||
@@ -20,7 +22,9 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
schema '$ref' => '#/definitions/errors_object'
|
||||
|
||||
let(:blog) { { title: 'foo' } }
|
||||
run_test!
|
||||
run_test! do |response|
|
||||
expect(response.body).to include("can't be blank")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -31,17 +35,24 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
produces 'application/json'
|
||||
parameter name: :keywords, in: :query, type: 'string'
|
||||
|
||||
let(:keywords) { 'foo bar' }
|
||||
|
||||
response '200', 'success' do
|
||||
schema type: 'array', items: { '$ref' => '#/definitions/blog' }
|
||||
end
|
||||
|
||||
let(:keywords) { 'foo bar' }
|
||||
response '406', 'unsupported accept header' do
|
||||
let(:'Accept') { 'application/foo' }
|
||||
run_test!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
tags 'Blogs'
|
||||
@@ -59,10 +70,10 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
examples 'application/json' => {
|
||||
id: 1,
|
||||
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 }
|
||||
run_test!
|
||||
end
|
||||
@@ -73,4 +84,24 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
||||
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
|
||||
|
||||
@@ -47,6 +47,10 @@ RSpec.configure do |config|
|
||||
# triggering implicit auto-inclusion in groups with matching metadata.
|
||||
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
|
||||
# with RSpec, but feel free to customize to your heart's content.
|
||||
=begin
|
||||
|
||||
@@ -39,21 +39,22 @@ RSpec.configure do |config|
|
||||
properties: {
|
||||
id: { type: 'integer' },
|
||||
title: { type: 'string' },
|
||||
content: { type: 'string' }
|
||||
content: { type: 'string', 'x-nullable': true },
|
||||
thumbnail: { type: 'string'}
|
||||
},
|
||||
required: [ 'id', 'title', 'content' ]
|
||||
required: [ 'id', 'title', 'content', 'thumbnail' ]
|
||||
}
|
||||
},
|
||||
securityDefinitions: {
|
||||
basic_auth: {
|
||||
type: :basic
|
||||
},
|
||||
api_key: {
|
||||
type: :apiKey,
|
||||
name: 'api_key',
|
||||
in: :query
|
||||
}
|
||||
},
|
||||
security: [
|
||||
{ api_key: [] }
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
@@ -5,6 +5,81 @@
|
||||
"version": "v1"
|
||||
},
|
||||
"paths": {
|
||||
"/auth-tests/basic": {
|
||||
"post": {
|
||||
"summary": "Authenticates with basic auth",
|
||||
"tags": [
|
||||
"Auth Tests"
|
||||
],
|
||||
"operationId": "testBasicAuth",
|
||||
"security": [
|
||||
{
|
||||
"basic_auth": [
|
||||
|
||||
]
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": "Valid credentials"
|
||||
},
|
||||
"401": {
|
||||
"description": "Invalid credentials"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/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": {
|
||||
"post": {
|
||||
"summary": "Creates a blog",
|
||||
@@ -16,6 +91,9 @@
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"name": "blog",
|
||||
@@ -55,14 +133,8 @@
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "success",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/blog"
|
||||
}
|
||||
}
|
||||
"406": {
|
||||
"description": "unsupported accept header"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -107,7 +179,8 @@
|
||||
"application/json": {
|
||||
"id": 1,
|
||||
"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"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -116,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": {
|
||||
@@ -146,28 +253,29 @@
|
||||
"type": "string"
|
||||
},
|
||||
"content": {
|
||||
"type": "string",
|
||||
"x-nullable": true
|
||||
},
|
||||
"thumbnail": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"title",
|
||||
"content"
|
||||
"content",
|
||||
"thumbnail"
|
||||
]
|
||||
}
|
||||
},
|
||||
"securityDefinitions": {
|
||||
"basic_auth": {
|
||||
"type": "basic"
|
||||
},
|
||||
"api_key": {
|
||||
"type": "apiKey",
|
||||
"name": "api_key",
|
||||
"in": "query"
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"api_key": [
|
||||
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user