mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-25 15:22:56 +00:00
Compare commits
64 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
732cab994c | ||
|
|
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 | ||
|
|
cf9170101b | ||
|
|
98d5b982c4 | ||
|
|
77d4cbe0ea | ||
|
|
32a7ab8234 | ||
|
|
95b009a72f | ||
|
|
471dff5e34 | ||
|
|
99be8135f7 | ||
|
|
8315eda8b2 | ||
|
|
e1fe9f3239 | ||
|
|
64b0de494f | ||
|
|
9d4069bcfe | ||
|
|
17a6cd13c4 | ||
|
|
0b0acfe4c7 | ||
|
|
5ea97a4278 | ||
|
|
5cf376891a | ||
|
|
10dd37896f | ||
|
|
3506fee3d0 | ||
|
|
5df130922f | ||
|
|
5a19cd2373 | ||
|
|
7025ec0063 | ||
|
|
23f4120fe3 | ||
|
|
312f68ae72 | ||
|
|
63e0e53104 | ||
|
|
4d675056c1 | ||
|
|
de09df59e1 | ||
|
|
b81b2927be | ||
|
|
f1850bc6d0 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@
|
|||||||
**/*/log
|
**/*/log
|
||||||
**/*/*.gem
|
**/*/*.gem
|
||||||
**/*/*.sqlite3
|
**/*/*.sqlite3
|
||||||
|
**/*/public/assets
|
||||||
|
*.swp
|
||||||
|
Gemfile.lock
|
||||||
|
|||||||
1
.ruby-version
Normal file
1
.ruby-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
2.3.0
|
||||||
66
.travis.yml
66
.travis.yml
@@ -1,10 +1,64 @@
|
|||||||
nguage: ruby
|
language: ruby
|
||||||
|
|
||||||
rvm:
|
rvm:
|
||||||
- 2.2.5
|
- 2.2.5
|
||||||
|
|
||||||
env:
|
env:
|
||||||
- "RAILS_VERSION=3.2.22"
|
- RAILS_VERSION=5.1.2
|
||||||
- "RAILS_VERSION=4.2.0"
|
- RAILS_VERSION=4.2.0
|
||||||
- "RAILS_VERSION=5.0.0"
|
- RAILS_VERSION=3.2.22
|
||||||
cache: bundler
|
|
||||||
install: bundle update
|
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
|
||||||
|
|
||||||
script: ./ci/test.sh
|
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
|
||||||
|
|||||||
11
Gemfile
11
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.
|
# 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/
|
# 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}"
|
gem 'rails', "#{rails_version}"
|
||||||
|
|
||||||
@@ -15,6 +15,7 @@ end
|
|||||||
|
|
||||||
gem 'sqlite3'
|
gem 'sqlite3'
|
||||||
|
|
||||||
|
gem 'rswag-specs', path: './rswag-specs'
|
||||||
gem 'rswag-api', path: './rswag-api'
|
gem 'rswag-api', path: './rswag-api'
|
||||||
gem 'rswag-ui', path: './rswag-ui'
|
gem 'rswag-ui', path: './rswag-ui'
|
||||||
|
|
||||||
@@ -25,5 +26,11 @@ group :test do
|
|||||||
gem 'test-unit'
|
gem 'test-unit'
|
||||||
gem 'rspec-rails'
|
gem 'rspec-rails'
|
||||||
gem 'generator_spec'
|
gem 'generator_spec'
|
||||||
gem 'rswag-specs', path: './rswag-specs'
|
gem 'capybara'
|
||||||
|
gem 'capybara-webkit'
|
||||||
|
end
|
||||||
|
|
||||||
|
group :assets do
|
||||||
|
gem 'uglifier'
|
||||||
|
gem 'therubyracer'
|
||||||
end
|
end
|
||||||
|
|||||||
146
Gemfile.lock
146
Gemfile.lock
@@ -1,146 +0,0 @@
|
|||||||
PATH
|
|
||||||
remote: ./rswag-api
|
|
||||||
specs:
|
|
||||||
rswag-api (1.0.0)
|
|
||||||
rails (>= 3.1, < 5.1)
|
|
||||||
|
|
||||||
PATH
|
|
||||||
remote: ./rswag-specs
|
|
||||||
specs:
|
|
||||||
rswag-specs (1.0.0)
|
|
||||||
json-schema (~> 2.2)
|
|
||||||
rails (>= 3.1, < 5.1)
|
|
||||||
rspec-rails (>= 2.14, < 4)
|
|
||||||
|
|
||||||
PATH
|
|
||||||
remote: ./rswag-ui
|
|
||||||
specs:
|
|
||||||
rswag-ui (1.0.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)
|
|
||||||
diff-lcs (1.2.5)
|
|
||||||
erubis (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)
|
|
||||||
mail (2.5.4)
|
|
||||||
mime-types (~> 1.16)
|
|
||||||
treetop (~> 1.4.8)
|
|
||||||
mime-types (1.25.1)
|
|
||||||
multi_json (1.12.1)
|
|
||||||
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)
|
|
||||||
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
|
|
||||||
thor (0.19.1)
|
|
||||||
tilt (1.4.1)
|
|
||||||
treetop (1.4.15)
|
|
||||||
polyglot
|
|
||||||
polyglot (>= 0.3.1)
|
|
||||||
tzinfo (0.3.51)
|
|
||||||
|
|
||||||
PLATFORMS
|
|
||||||
ruby
|
|
||||||
|
|
||||||
DEPENDENCIES
|
|
||||||
generator_spec
|
|
||||||
rails (= 3.2.22)
|
|
||||||
rspec-rails
|
|
||||||
rswag-api!
|
|
||||||
rswag-specs!
|
|
||||||
rswag-ui!
|
|
||||||
sqlite3
|
|
||||||
strong_parameters
|
|
||||||
test-unit
|
|
||||||
168
README.md
168
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' }
|
let(:id) { 'invalid' }
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
response '406', 'unsupported accept header' do
|
||||||
|
let(:'Accept') { 'application/foo' }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -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).
|
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:
|
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
|
```ruby
|
||||||
@@ -119,6 +135,31 @@ response '201', 'blog created' do
|
|||||||
end
|
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 ###
|
### 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:
|
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:
|
||||||
@@ -127,7 +168,7 @@ In addition to paths, operations and responses, Swagger also supports global API
|
|||||||
# spec/swagger_helper.rb
|
# spec/swagger_helper.rb
|
||||||
RSpec.configure do |config|
|
RSpec.configure do |config|
|
||||||
config.swagger_root = Rails.root.to_s + '/swagger'
|
config.swagger_root = Rails.root.to_s + '/swagger'
|
||||||
|
|
||||||
config.swagger_docs = {
|
config.swagger_docs = {
|
||||||
'v1/swagger.json' => {
|
'v1/swagger.json' => {
|
||||||
swagger: '2.0',
|
swagger: '2.0',
|
||||||
@@ -164,6 +205,58 @@ describe 'Blogs API', swagger_doc: 'v2/swagger.json' do
|
|||||||
end
|
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 ##
|
## 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.
|
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.
|
||||||
@@ -171,8 +264,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 ###
|
||||||
|
|
||||||
@@ -224,25 +317,82 @@ describe 'Blogs API' do
|
|||||||
path '/blogs' do
|
path '/blogs' do
|
||||||
|
|
||||||
post 'Creates a blog' do
|
post 'Creates a blog' do
|
||||||
|
|
||||||
response 422, 'invalid request' do
|
response 422, 'invalid request' do
|
||||||
schema '$ref' => '#/definitions/errors_object'
|
schema '$ref' => '#/definitions/errors_object'
|
||||||
...
|
...
|
||||||
end
|
end
|
||||||
|
|
||||||
# spec/integration/comments_spec.rb
|
# spec/integration/comments_spec.rb
|
||||||
describe 'Blogs API' do
|
describe 'Blogs API' do
|
||||||
|
|
||||||
path '/blogs/{blog_id}/comments' do
|
path '/blogs/{blog_id}/comments' do
|
||||||
|
|
||||||
post 'Creates a comment' do
|
post 'Creates a comment' do
|
||||||
|
|
||||||
response 422, 'invalid request' do
|
response 422, 'invalid request' do
|
||||||
schema '$ref' => '#/definitions/errors_object'
|
schema '$ref' => '#/definitions/errors_object'
|
||||||
...
|
...
|
||||||
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
|
||||||
|
```
|
||||||
|
|
||||||
|
### Enable generation examples from responses ###
|
||||||
|
|
||||||
|
To enable examples generation from responses add callback above run_test! like:
|
||||||
|
```ruby
|
||||||
|
after do |example|
|
||||||
|
example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) }
|
||||||
|
end
|
||||||
|
```
|
||||||
|
You need to disable --dry-run option for Rspec > 3
|
||||||
|
|
||||||
|
Add to application.rb:
|
||||||
|
```ruby
|
||||||
|
RSpec.configure do |config|
|
||||||
|
config.swagger_dry_run = false
|
||||||
|
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_:
|
||||||
@@ -273,7 +423,7 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
__NOTE__: If you're using rswag-specs to generate Swagger files, you'll want to ensure they both use the same <swagger_root>. The reason for separate settings is to maintain independence between the two gems. For example, you could install rswag-api independently and create your Swagger files manually.
|
__NOTE__: If you're using rswag-specs to generate Swagger files, you'll want to ensure they both use the same <swagger_root>. The reason for separate settings is to maintain independence between the two gems. For example, you could install rswag-api independently and create your Swagger files manually.
|
||||||
|
|
||||||
### Dynamic Values for Swagger JSON ##
|
### Dynamic Values for Swagger JSON ##
|
||||||
|
|
||||||
There may be cases where you need to add dynamic values to the Swagger JSON that's returned by rswag-api. For example, you may want to provide an explicit host name. Rather than hardcoding it, you can configure a filter that's executed prior to serializing every Swagger document:
|
There may be cases where you need to add dynamic values to the Swagger JSON that's returned by rswag-api. For example, you may want to provide an explicit host name. Rather than hardcoding it, you can configure a filter that's executed prior to serializing every Swagger document:
|
||||||
@@ -287,7 +437,7 @@ end
|
|||||||
```
|
```
|
||||||
|
|
||||||
Note how the filter is passed the rack env for the current request. This provides a lot of flexibilty. For example, you can assign the "host" property (as shown) or you could inspect session information or an Authoriation header and remove operations based on user permissions.
|
Note how the filter is passed the rack env for the current request. This provides a lot of flexibilty. For example, you can assign the "host" property (as shown) or you could inspect session information or an Authoriation header and remove operations based on user permissions.
|
||||||
|
|
||||||
### Enable Swagger Endpoints for swagger-ui ###
|
### Enable Swagger Endpoints for swagger-ui ###
|
||||||
|
|
||||||
You can update the _rswag-ui.rb_ initializer, installed with rswag-ui, to specify which Swagger endpoints should be available to power the documentation UI. If you're using rswag-api, these should correspond to the Swagger endpoints it exposes. When the UI is rendered, you'll see these listed in a drop-down to the top right of the page:
|
You can update the _rswag-ui.rb_ initializer, installed with rswag-ui, to specify which Swagger endpoints should be available to power the documentation UI. If you're using rswag-api, these should correspond to the Swagger endpoints it exposes. When the UI is rendered, you'll see these listed in a drop-down to the top right of the page:
|
||||||
@@ -314,7 +464,7 @@ end
|
|||||||
|
|
||||||
### Customizing the swagger-ui ###
|
### 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
|
```ruby
|
||||||
rails g rswag:ui:custom
|
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/configuration'
|
||||||
require 'rswag/api/engine'
|
require 'rswag/api/engine'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module Rswag
|
|
||||||
module Api
|
|
||||||
VERSION = '1.0.1'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
$:.push File.expand_path("../lib", __FILE__)
|
$:.push File.expand_path("../lib", __FILE__)
|
||||||
|
|
||||||
# Maintain your gem's version:
|
|
||||||
require 'rswag/api/version'
|
|
||||||
|
|
||||||
# Describe your gem and declare its dependencies:
|
# Describe your gem and declare its dependencies:
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "rswag-api"
|
s.name = "rswag-api"
|
||||||
s.version = Rswag::Api::VERSION
|
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
s.authors = ["Richie Morris"]
|
s.authors = ["Richie Morris"]
|
||||||
s.email = ["domaindrivendev@gmail.com"]
|
s.email = ["domaindrivendev@gmail.com"]
|
||||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||||
@@ -14,7 +11,7 @@ Gem::Specification.new do |s|
|
|||||||
s.description = "Open up your API to the phenomenal Swagger ecosystem by exposing Swagger files, that describe your service, as JSON endpoints"
|
s.description = "Open up your API to the phenomenal Swagger ecosystem by exposing Swagger files, that describe your service, as JSON endpoints"
|
||||||
s.license = "MIT"
|
s.license = "MIT"
|
||||||
|
|
||||||
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile"]
|
||||||
|
|
||||||
s.add_dependency "rails", ">= 3.1", "< 5.1"
|
s.add_dependency 'railties', '>= 3.1', '< 6.0'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
require 'rspec/core'
|
require 'rspec/core'
|
||||||
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
|
||||||
@@ -11,10 +11,15 @@ module Rswag
|
|||||||
::RSpec.configure do |c|
|
::RSpec.configure do |c|
|
||||||
c.add_setting :swagger_root
|
c.add_setting :swagger_root
|
||||||
c.add_setting :swagger_docs
|
c.add_setting :swagger_docs
|
||||||
|
c.add_setting :swagger_dry_run
|
||||||
c.extend ExampleGroupHelpers, type: :request
|
c.extend ExampleGroupHelpers, type: :request
|
||||||
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
|
||||||
|
|||||||
43
rswag-specs/lib/rswag/specs/configuration.rb
Normal file
43
rswag-specs/lib/rswag/specs/configuration.rb
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
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 swagger_dry_run
|
||||||
|
@swagger_dry_run ||= begin
|
||||||
|
@rspec_config.swagger_dry_run.nil? || @rspec_config.swagger_dry_run
|
||||||
|
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
|
||||||
@@ -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|
|
||||||
@@ -25,7 +25,7 @@ module Rswag
|
|||||||
# functionality while also setting the appropriate metadata if applicable
|
# functionality while also setting the appropriate metadata if applicable
|
||||||
def description(value=nil)
|
def description(value=nil)
|
||||||
return super() if value.nil?
|
return super() if value.nil?
|
||||||
metadata[:operation][:description] = value
|
metadata[:operation][:description] = value
|
||||||
end
|
end
|
||||||
|
|
||||||
# These are array properties - note the splat operator
|
# These are array properties - note the splat operator
|
||||||
@@ -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
|
||||||
metadata[:operation][:parameters] ||= []
|
|
||||||
metadata[:operation][:parameters] << attributes
|
if metadata.has_key?(:operation)
|
||||||
|
metadata[:operation][:parameters] ||= []
|
||||||
|
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,7 +61,15 @@ module Rswag
|
|||||||
metadata[:response][:headers][name] = attributes
|
metadata[:response][:headers][name] = attributes
|
||||||
end
|
end
|
||||||
|
|
||||||
def run_test!
|
# 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!(&block)
|
||||||
# NOTE: rspec 2.x support
|
# NOTE: rspec 2.x support
|
||||||
if RSPEC_VERSION < 3
|
if RSPEC_VERSION < 3
|
||||||
before do
|
before do
|
||||||
@@ -63,7 +77,8 @@ module Rswag
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "returns a #{metadata[:response][:code]} response" do
|
it "returns a #{metadata[:response][:code]} response" do
|
||||||
assert_response_matches_metadata(example.metadata)
|
assert_response_matches_metadata(metadata)
|
||||||
|
block.call(response) if block_given?
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
before do |example|
|
before do |example|
|
||||||
@@ -71,7 +86,8 @@ module Rswag
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "returns a #{metadata[:response][:code]} response" do |example|
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,38 +5,30 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
module ExampleHelpers
|
module ExampleHelpers
|
||||||
|
|
||||||
def submit_request(api_metadata)
|
def submit_request(metadata)
|
||||||
factory = RequestFactory.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
|
request = RequestFactory.new.build_request(metadata, self)
|
||||||
|
|
||||||
if RAILS_VERSION < 5
|
if RAILS_VERSION < 5
|
||||||
send(
|
send(
|
||||||
api_metadata[:operation][:verb],
|
request[:verb],
|
||||||
factory.build_fullpath(self),
|
request[:path],
|
||||||
factory.build_body(self),
|
request[:payload],
|
||||||
factory.build_headers(self)
|
request[:headers]
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
send(
|
send(
|
||||||
api_metadata[:operation][:verb],
|
request[:verb],
|
||||||
factory.build_fullpath(self),
|
request[:path],
|
||||||
{
|
{
|
||||||
params: factory.build_body(self),
|
params: request[:payload],
|
||||||
headers: factory.build_headers(self)
|
headers: request[:headers]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def assert_response_matches_metadata(api_metadata)
|
def assert_response_matches_metadata(metadata)
|
||||||
validator = ResponseValidator.new(api_metadata, global_metadata(api_metadata[:swagger_doc]))
|
ResponseValidator.new.validate!(metadata, response)
|
||||||
validator.validate!(response)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def global_metadata(swagger_doc)
|
|
||||||
swagger_docs = ::RSpec.configuration.swagger_docs
|
|
||||||
swagger_doc.nil? ? swagger_docs.values.first : swagger_docs[swagger_doc]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
25
rswag-specs/lib/rswag/specs/extended_schema.rb
Normal file
25
rswag-specs/lib/rswag/specs/extended_schema.rb
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
require 'json-schema'
|
||||||
|
|
||||||
|
module Rswag
|
||||||
|
module Specs
|
||||||
|
class ExtendedSchema < JSON::Schema::Draft4
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
super
|
||||||
|
@attributes['type'] = ExtendedTypeAttribute
|
||||||
|
@uri = URI.parse('http://tempuri.org/rswag/specs/extended_schema')
|
||||||
|
@names = ['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
|
||||||
@@ -1,88 +1,158 @@
|
|||||||
require 'active_support/core_ext/hash/slice'
|
require 'active_support/core_ext/hash/slice'
|
||||||
|
require 'active_support/core_ext/hash/conversions'
|
||||||
require 'json'
|
require 'json'
|
||||||
|
|
||||||
module Rswag
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
class RequestFactory
|
class RequestFactory
|
||||||
|
|
||||||
def initialize(api_metadata, global_metadata)
|
def initialize(config = ::Rswag::Specs.config)
|
||||||
@api_metadata = api_metadata
|
@config = config
|
||||||
@global_metadata = global_metadata
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_fullpath(example)
|
def build_request(metadata, example)
|
||||||
@api_metadata[:path].dup.tap do |t|
|
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||||
t.prepend(@global_metadata[:basePath] || '')
|
parameters = expand_parameters(metadata, swagger_doc, example)
|
||||||
parameters_in(:path).each { |p| t.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s) }
|
|
||||||
t.concat(build_query_string(example))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_query_string(example)
|
{}.tap do |request|
|
||||||
query_string = parameters_in(:query)
|
add_verb(request, metadata)
|
||||||
.map { |p| build_query_string_part(p, example.send(p[:name])) }
|
add_path(request, metadata, swagger_doc, parameters, example)
|
||||||
.join('&')
|
add_headers(request, metadata, swagger_doc, parameters, example)
|
||||||
|
add_payload(request, parameters, example)
|
||||||
query_string.empty? ? '' : "?#{query_string}"
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_body(example)
|
|
||||||
body_parameter = parameters_in(:body).first
|
|
||||||
body_parameter.nil? ? '' : example.send(body_parameter[:name]).to_json
|
|
||||||
end
|
|
||||||
|
|
||||||
def build_headers(example)
|
|
||||||
headers = Hash[ parameters_in(:header).map { |p| [ p[:name], example.send(p[:name]).to_s ] } ]
|
|
||||||
headers.tap do |h|
|
|
||||||
produces = @api_metadata[:operation][:produces] || @global_metadata[:produces]
|
|
||||||
consumes = @api_metadata[:operation][:consumes] || @global_metadata[:consumes]
|
|
||||||
h['ACCEPT'] = produces.join(';') unless produces.nil?
|
|
||||||
h['CONTENT_TYPE'] = consumes.join(';') unless consumes.nil?
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def parameters_in(location)
|
def expand_parameters(metadata, swagger_doc, example)
|
||||||
(@api_metadata[:operation][:parameters] || [])
|
operation_params = metadata[:operation][:parameters] || []
|
||||||
.map { |p| p['$ref'] ? resolve_parameter(p['$ref']) : p } # resolve any references
|
path_item_params = metadata[:path_item][:parameters] || []
|
||||||
.concat(resolve_api_key_parameters)
|
security_params = derive_security_params(metadata, swagger_doc)
|
||||||
.select { |p| p[:in] == location }
|
|
||||||
|
operation_params
|
||||||
|
.concat(path_item_params)
|
||||||
|
.concat(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
|
end
|
||||||
|
|
||||||
def resolve_parameter(ref)
|
def derive_security_params(metadata, swagger_doc)
|
||||||
defined_params = @global_metadata[:parameters]
|
requirements = metadata[:operation][:security] || swagger_doc[:security]
|
||||||
|
scheme_names = requirements ? requirements.map { |r| r.keys.first } : []
|
||||||
|
applicable_schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values
|
||||||
|
|
||||||
|
applicable_schemes.map do |scheme|
|
||||||
|
param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header }
|
||||||
|
param.merge(type: :string)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def resolve_parameter(ref, swagger_doc)
|
||||||
|
definitions = swagger_doc[:parameters]
|
||||||
key = ref.sub('#/parameters/', '')
|
key = ref.sub('#/parameters/', '')
|
||||||
raise "Referenced parameter '#{ref}' must be defined" unless defined_params && defined_params[key]
|
raise "Referenced parameter '#{ref}' must be defined" unless definitions && definitions[key]
|
||||||
defined_params[key]
|
definitions[key]
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_api_key_parameters
|
def add_verb(request, metadata)
|
||||||
@api_key_params ||= begin
|
request[:verb] = metadata[:operation][:verb]
|
||||||
global_requirements = (@global_metadata[:security] || {})
|
end
|
||||||
requirements = global_requirements.merge(@api_metadata[:operation][:security] || {})
|
|
||||||
definitions = (@global_metadata[:securityDefinitions] || {}).slice(*requirements.keys)
|
def add_path(request, metadata, swagger_doc, parameters, example)
|
||||||
definitions.values.select { |d| d[:type] == :apiKey }
|
template = (swagger_doc[:basePath] || '') + metadata[:path_item][:template]
|
||||||
|
|
||||||
|
request[:path] = template.tap do |template|
|
||||||
|
parameters.select { |p| p[:in] == :path }.each do |p|
|
||||||
|
template.gsub!("{#{p[:name]}}", example.send(p[:name]).to_s)
|
||||||
|
end
|
||||||
|
|
||||||
|
parameters.select { |p| p[:in] == :query }.each_with_index do |p, i|
|
||||||
|
template.concat(i == 0 ? '?' : '&')
|
||||||
|
template.concat(build_query_string_part(p, example.send(p[:name])))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_query_string_part(param, value)
|
def build_query_string_part(param, value)
|
||||||
return "#{param[:name]}=#{value.to_s}" unless param[:type].to_sym == :array
|
|
||||||
|
|
||||||
name = param[:name]
|
name = param[:name]
|
||||||
|
return "#{name}=#{value.to_s}" unless param[:type].to_sym == :array
|
||||||
|
|
||||||
case param[:collectionFormat]
|
case param[:collectionFormat]
|
||||||
when :ssv
|
when :ssv
|
||||||
"#{name}=#{value.join(' ')}"
|
"#{name}=#{value.join(' ')}"
|
||||||
when :tsv
|
when :tsv
|
||||||
"#{name}=#{value.join('\t')}"
|
"#{name}=#{value.join('\t')}"
|
||||||
when :pipes
|
when :pipes
|
||||||
"#{name}=#{value.join('|')}"
|
"#{name}=#{value.join('|')}"
|
||||||
when :multi
|
when :multi
|
||||||
value.map { |v| "#{name}=#{v}" }.join('&')
|
value.map { |v| "#{name}=#{v}" }.join('&')
|
||||||
else
|
else
|
||||||
"#{name}=#{value.join(',')}" # csv is default
|
"#{name}=#{value.join(',')}" # csv is default
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def add_headers(request, metadata, swagger_doc, parameters, example)
|
||||||
|
tuples = parameters
|
||||||
|
.select { |p| p[:in] == :header }
|
||||||
|
.map { |p| [ p[:name], example.send(p[:name]).to_s ] }
|
||||||
|
|
||||||
|
# Accept header
|
||||||
|
produces = metadata[:operation][:produces] || swagger_doc[:produces]
|
||||||
|
if produces
|
||||||
|
accept = example.respond_to?(:'Accept') ? example.send(:'Accept') : produces.first
|
||||||
|
tuples << [ 'Accept', accept ]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Content-Type header
|
||||||
|
consumes = metadata[:operation][:consumes] || swagger_doc[:consumes]
|
||||||
|
if consumes
|
||||||
|
content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first
|
||||||
|
tuples << [ 'Content-Type', content_type ]
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rails test infrastructure requires rackified headers
|
||||||
|
rackified_tuples = tuples.map do |pair|
|
||||||
|
[
|
||||||
|
case pair[0]
|
||||||
|
when 'Accept' then 'HTTP_ACCEPT'
|
||||||
|
when 'Content-Type' then 'CONTENT_TYPE'
|
||||||
|
when 'Authorization' then 'HTTP_AUTHORIZATION'
|
||||||
|
else pair[0]
|
||||||
|
end,
|
||||||
|
pair[1]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
request[:headers] = Hash[ rackified_tuples ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_payload(request, parameters, example)
|
||||||
|
content_type = request[:headers]['CONTENT_TYPE']
|
||||||
|
return if content_type.nil?
|
||||||
|
|
||||||
|
if [ 'application/x-www-form-urlencoded', 'multipart/form-data' ].include?(content_type)
|
||||||
|
request[:payload] = build_form_payload(parameters, example)
|
||||||
|
else
|
||||||
|
request[:payload] = build_json_payload(parameters, example)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_form_payload(parameters, example)
|
||||||
|
# See http://seejohncode.com/2012/04/29/quick-tip-testing-multipart-uploads-with-rspec/
|
||||||
|
# Rather that serializing with the appropriate encoding (e.g. multipart/form-data),
|
||||||
|
# Rails test infrastructure allows us to send the values directly as a hash
|
||||||
|
# PROS: simple to implement, CONS: serialization/deserialization is bypassed in test
|
||||||
|
tuples = parameters
|
||||||
|
.select { |p| p[:in] == :formData }
|
||||||
|
.map { |p| [ p[:name], example.send(p[:name]) ] }
|
||||||
|
Hash[ tuples ]
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_json_payload(parameters, example)
|
||||||
|
body_param = parameters.select { |p| p[:in] == :body }.first
|
||||||
|
body_param ? example.send(body_param[:name]).to_json : nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,36 +1,50 @@
|
|||||||
|
require 'active_support/core_ext/hash/slice'
|
||||||
require 'json-schema'
|
require 'json-schema'
|
||||||
|
require 'json'
|
||||||
|
require 'rswag/specs/extended_schema'
|
||||||
|
|
||||||
module Rswag
|
module Rswag
|
||||||
module Specs
|
module Specs
|
||||||
class ResponseValidator
|
class ResponseValidator
|
||||||
|
|
||||||
def initialize(api_metadata, global_metadata)
|
def initialize(config = ::Rswag::Specs.config)
|
||||||
@api_metadata = api_metadata
|
@config = config
|
||||||
@global_metadata = global_metadata
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate!(response)
|
def validate!(metadata, response)
|
||||||
validate_code!(response.code)
|
swagger_doc = @config.get_swagger_doc(metadata[:swagger_doc])
|
||||||
validate_body!(response.body)
|
|
||||||
|
validate_code!(metadata, response.code)
|
||||||
|
validate_headers!(metadata, response.headers)
|
||||||
|
validate_body!(metadata, swagger_doc, response.body)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def validate_code!(code)
|
def validate_code!(metadata, code)
|
||||||
if code.to_s != @api_metadata[:response][:code].to_s
|
expected = metadata[:response][:code].to_s
|
||||||
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{@api_metadata[:response][:code]}'"
|
if code != expected
|
||||||
|
raise UnexpectedResponse, "Expected response code '#{code}' to match '#{expected}'"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_body!(body)
|
def validate_headers!(metadata, headers)
|
||||||
schema = @api_metadata[:response][:schema]
|
expected = (metadata[:response][:headers] || {}).keys
|
||||||
return if schema.nil?
|
expected.each do |name|
|
||||||
begin
|
raise UnexpectedResponse, "Expected response header #{name} to be present" if headers[name.to_s].nil?
|
||||||
JSON::Validator.validate!(schema.merge(@global_metadata), body)
|
|
||||||
rescue JSON::Schema::ValidationError => ex
|
|
||||||
raise UnexpectedResponse, "Expected response body to match schema: #{ex.message}"
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_body!(metadata, swagger_doc, body)
|
||||||
|
response_schema = metadata[:response][:schema]
|
||||||
|
return if response_schema.nil?
|
||||||
|
|
||||||
|
validation_schema = response_schema
|
||||||
|
.merge('$schema' => 'http://tempuri.org/rswag/specs/extended_schema')
|
||||||
|
.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
|
end
|
||||||
|
|
||||||
class UnexpectedResponse < StandardError; end
|
class UnexpectedResponse < StandardError; end
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
|
||||||
}
|
{ paths: { path_template => path_item } }
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
class ConfigurationError < StandardError; end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module Rswag
|
|
||||||
module Specs
|
|
||||||
VERSION = '1.0.1'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -8,7 +8,7 @@ namespace :rswag do
|
|||||||
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
t.pattern = 'spec/requests/**/*_spec.rb, spec/api/**/*_spec.rb, spec/integration/**/*_spec.rb'
|
||||||
|
|
||||||
# NOTE: rspec 2.x support
|
# NOTE: rspec 2.x support
|
||||||
if Rswag::Specs::RSPEC_VERSION > 2
|
if Rswag::Specs::RSPEC_VERSION > 2 && Rswag::Specs.config.swagger_dry_run
|
||||||
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
|
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--dry-run', '--order defined' ]
|
||||||
else
|
else
|
||||||
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
|
t.rspec_opts = [ '--format Rswag::Specs::SwaggerFormatter', '--order defined' ]
|
||||||
|
|||||||
@@ -1,12 +1,9 @@
|
|||||||
$:.push File.expand_path("../lib", __FILE__)
|
$:.push File.expand_path("../lib", __FILE__)
|
||||||
|
|
||||||
# Maintain your gem's version:
|
|
||||||
require 'rswag/specs/version'
|
|
||||||
|
|
||||||
# Describe your gem and declare its dependencies:
|
# Describe your gem and declare its dependencies:
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "rswag-specs"
|
s.name = "rswag-specs"
|
||||||
s.version = Rswag::Specs::VERSION
|
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
s.authors = ["Richie Morris"]
|
s.authors = ["Richie Morris"]
|
||||||
s.email = ["domaindrivendev@gmail.com"]
|
s.email = ["domaindrivendev@gmail.com"]
|
||||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||||
@@ -16,7 +13,7 @@ Gem::Specification.new do |s|
|
|||||||
|
|
||||||
s.files = Dir["{lib}/**/*"] + ["MIT-LICENSE", "Rakefile" ]
|
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 'json-schema', '~> 2.2'
|
||||||
s.add_dependency 'rspec-rails', '>= 2.14', '< 4'
|
|
||||||
end
|
end
|
||||||
|
|||||||
77
rswag-specs/spec/rswag/specs/configuration_spec.rb
Normal file
77
rswag-specs/spec/rswag/specs/configuration_spec.rb
Normal 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
|
||||||
@@ -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,7 +112,8 @@ 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(
|
||||||
[ name: :id, in: :path, required: true ]
|
[ name: :id, in: :path, required: true ]
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -4,25 +4,16 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe ExampleHelpers do
|
describe ExampleHelpers do
|
||||||
let(:api_metadata) do
|
subject { double('example') }
|
||||||
{
|
|
||||||
path: '/blogs/{blog_id}/comments/{id}',
|
before do
|
||||||
operation: {
|
subject.extend(ExampleHelpers)
|
||||||
verb: :put,
|
allow(Rswag::Specs).to receive(:config).and_return(config)
|
||||||
summary: 'Updates a blog',
|
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||||
parameters: [
|
stub_const('Rswag::Specs::RAILS_VERSION', 3)
|
||||||
{ name: :blog_id, in: :path, type: 'integer' },
|
|
||||||
{ name: 'id', in: :path, type: 'integer' },
|
|
||||||
{ name: 'q1', in: :query, type: 'string' },
|
|
||||||
{ name: :blog, in: :body, schema: { type: 'object' } }
|
|
||||||
],
|
|
||||||
security: {
|
|
||||||
api_key: []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
let(:global_metadata) do
|
let(:config) { double('config') }
|
||||||
|
let(:swagger_doc) do
|
||||||
{
|
{
|
||||||
securityDefinitions: {
|
securityDefinitions: {
|
||||||
api_key: {
|
api_key: {
|
||||||
@@ -33,31 +24,42 @@ module Rswag
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
let(:metadata) do
|
||||||
subject { double('example') }
|
{
|
||||||
|
path_item: { template: '/blogs/{blog_id}/comments/{id}' },
|
||||||
before do
|
operation: {
|
||||||
subject.extend ExampleHelpers
|
verb: :put,
|
||||||
allow(subject).to receive(:blog_id).and_return(1)
|
summary: 'Updates a blog',
|
||||||
allow(subject).to receive(:id).and_return(2)
|
consumes: [ 'application/json' ],
|
||||||
allow(subject).to receive(:q1).and_return('foo')
|
parameters: [
|
||||||
allow(subject).to receive(:api_key).and_return('fookey')
|
{ name: :blog_id, in: :path, type: 'integer' },
|
||||||
allow(subject).to receive(:blog).and_return(text: 'Some comment')
|
{ name: 'id', in: :path, type: 'integer' },
|
||||||
allow(subject).to receive(:global_metadata).and_return(global_metadata)
|
{ name: 'q1', in: :query, type: 'string' },
|
||||||
allow(subject).to receive(:put)
|
{ name: :blog, in: :body, schema: { type: 'object' } }
|
||||||
|
],
|
||||||
|
security: [
|
||||||
|
{ api_key: [] }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#submit_request(api_metadata)' do
|
describe '#submit_request(metadata)' do
|
||||||
before do
|
before do
|
||||||
stub_const('Rails::VERSION::MAJOR', 3)
|
allow(subject).to receive(:blog_id).and_return(1)
|
||||||
subject.submit_request(api_metadata)
|
allow(subject).to receive(:id).and_return(2)
|
||||||
|
allow(subject).to receive(:q1).and_return('foo')
|
||||||
|
allow(subject).to receive(:api_key).and_return('fookey')
|
||||||
|
allow(subject).to receive(:blog).and_return(text: 'Some comment')
|
||||||
|
allow(subject).to receive(:put)
|
||||||
|
subject.submit_request(metadata)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "submits a request built from metadata and 'let' values" do
|
it "submits a request built from metadata and 'let' values" do
|
||||||
expect(subject).to have_received(:put).with(
|
expect(subject).to have_received(:put).with(
|
||||||
'/blogs/1/comments/2?q1=foo&api_key=fookey',
|
'/blogs/1/comments/2?q1=foo&api_key=fookey',
|
||||||
"{\"text\":\"Some comment\"}",
|
"{\"text\":\"Some comment\"}",
|
||||||
{}
|
{ 'CONTENT_TYPE' => 'application/json' }
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,212 +4,301 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe RequestFactory do
|
describe RequestFactory do
|
||||||
let(:api_metadata) do
|
subject { RequestFactory.new(config) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
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
|
||||||
{
|
{
|
||||||
path: '/blogs/{blog_id}/comments/{id}',
|
path_item: { template: '/blogs' },
|
||||||
operation: {
|
operation: { verb: :get }
|
||||||
verb: :put,
|
|
||||||
summary: 'Updates a blog',
|
|
||||||
parameters: [
|
|
||||||
{ name: :blog_id, in: :path, type: 'integer' },
|
|
||||||
{ name: 'id', in: :path, type: 'integer' }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
let(:global_metadata) { {} }
|
|
||||||
|
|
||||||
subject { RequestFactory.new(api_metadata, global_metadata) }
|
describe '#build_request(metadata, example)' do
|
||||||
|
let(:request) { subject.build_request(metadata, example) }
|
||||||
|
|
||||||
let(:example) { double('example') }
|
it 'builds request hash for given example' do
|
||||||
before do
|
expect(request[:verb]).to eq(:get)
|
||||||
allow(example).to receive(:blog_id).and_return(1)
|
expect(request[:path]).to eq('/blogs')
|
||||||
allow(example).to receive(:id).and_return('2')
|
end
|
||||||
end
|
|
||||||
|
|
||||||
describe '#build_fullpath(example)' do
|
context "'path' parameters" do
|
||||||
let(:path) { subject.build_fullpath(example) }
|
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
|
||||||
|
|
||||||
context 'always' do
|
it 'builds the path from example values' do
|
||||||
it "builds a path using metadata and example values" do
|
expect(request[:path]).to eq('/blogs/1/comments/2')
|
||||||
expect(path).to eq('/blogs/1/comments/2')
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "'query' parameters" do
|
context "'query' parameters" do
|
||||||
before do
|
before do
|
||||||
api_metadata[:operation][:parameters] << { name: 'q1', in: :query, type: 'string' }
|
metadata[:operation][:parameters] = [
|
||||||
api_metadata[:operation][:parameters] << { name: 'q2', in: :query, type: 'string' }
|
{ name: 'q1', in: :query, type: :string },
|
||||||
|
{ name: 'q2', in: :query, type: :string }
|
||||||
|
]
|
||||||
allow(example).to receive(:q1).and_return('foo')
|
allow(example).to receive(:q1).and_return('foo')
|
||||||
allow(example).to receive(:q2).and_return('bar')
|
allow(example).to receive(:q2).and_return('bar')
|
||||||
end
|
end
|
||||||
|
|
||||||
it "appends a query string using metadata and example values" do
|
it "builds the query string from example values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?q1=foo&q2=bar')
|
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "'query' parameter of type 'array'" do
|
context "'query' parameters of type 'array'" do
|
||||||
before do
|
before do
|
||||||
api_metadata[:operation][:parameters] << {
|
metadata[:operation][:parameters] = [
|
||||||
name: 'things',
|
{ name: 'things', in: :query, type: :array, collectionFormat: collection_format }
|
||||||
in: :query,
|
]
|
||||||
type: :array,
|
|
||||||
collectionFormat: collectionFormat
|
|
||||||
}
|
|
||||||
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
|
allow(example).to receive(:things).and_return([ 'foo', 'bar' ])
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = csv' do
|
context 'collectionFormat = csv' do
|
||||||
let(:collectionFormat) { :csv }
|
let(:collection_format) { :csv }
|
||||||
it "formats as comma separated values" do
|
it "formats as comma separated values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo,bar')
|
expect(request[:path]).to eq('/blogs?things=foo,bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = ssv' do
|
context 'collectionFormat = ssv' do
|
||||||
let(:collectionFormat) { :ssv }
|
let(:collection_format) { :ssv }
|
||||||
it "formats as space separated values" do
|
it "formats as space separated values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo bar')
|
expect(request[:path]).to eq('/blogs?things=foo bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = tsv' do
|
context 'collectionFormat = tsv' do
|
||||||
let(:collectionFormat) { :tsv }
|
let(:collection_format) { :tsv }
|
||||||
it "formats as tab separated values" do
|
it "formats as tab separated values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo\tbar')
|
expect(request[:path]).to eq('/blogs?things=foo\tbar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = pipes' do
|
context 'collectionFormat = pipes' do
|
||||||
let(:collectionFormat) { :pipes }
|
let(:collection_format) { :pipes }
|
||||||
it "formats as pipe separated values" do
|
it "formats as pipe separated values" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo|bar')
|
expect(request[:path]).to eq('/blogs?things=foo|bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'collectionFormat = multi' do
|
context 'collectionFormat = multi' do
|
||||||
let(:collectionFormat) { :multi }
|
let(:collection_format) { :multi }
|
||||||
it "formats as multiple parameter instances" do
|
it "formats as multiple parameter instances" do
|
||||||
expect(path).to eq('/blogs/1/comments/2?things=foo&things=bar')
|
expect(request[:path]).to eq('/blogs?things=foo&things=bar')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "global definition for 'api_key in query'" do
|
context "'header' parameters" do
|
||||||
before do
|
before do
|
||||||
global_metadata[:securityDefinitions] = { api_key: { type: :apiKey, name: 'api_key', in: :query } }
|
metadata[:operation][:parameters] = [ { name: 'Api-Key', in: :header, type: :string } ]
|
||||||
allow(example).to receive(:api_key).and_return('fookey')
|
allow(example).to receive(:'Api-Key').and_return('foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'global requirement' do
|
it 'adds names and example values to headers' do
|
||||||
before { global_metadata[:security] = { api_key: [] } }
|
expect(request[:headers]).to eq({ 'Api-Key' => 'foobar' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "appends the api_key using metadata and example value" do
|
context 'optional parameters not provided' do
|
||||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'operation-specific requirement' do
|
context "explicit 'Content-Type' provided" do
|
||||||
before { api_metadata[:operation][:security] = { api_key: [] } }
|
before do
|
||||||
|
allow(example).to receive(:'Content-Type').and_return('application/xml')
|
||||||
it "appends the api_key using metadata and example value" do
|
|
||||||
expect(path).to eq('/blogs/1/comments/2?api_key=fookey')
|
|
||||||
end
|
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 '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
|
||||||
|
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 '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 "path-level parameters" do
|
||||||
|
before do
|
||||||
|
metadata[:operation][:parameters] = [ { name: 'q1', in: :query, type: :string } ]
|
||||||
|
metadata[:path_item][:parameters] = [ { name: 'q2', in: :query, type: :string } ]
|
||||||
|
allow(example).to receive(:q1).and_return('foo')
|
||||||
|
allow(example).to receive(:q2).and_return('bar')
|
||||||
|
end
|
||||||
|
|
||||||
|
it "populates operation and path level parameters " do
|
||||||
|
expect(request[:path]).to eq('/blogs?q1=foo&q2=bar')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'referenced parameters' do
|
||||||
|
before do
|
||||||
|
swagger_doc[:parameters] = { 'q1' => { name: 'q1', in: :query, type: :string } }
|
||||||
|
metadata[:operation][:parameters] = [ { '$ref' => '#/parameters/q1' } ]
|
||||||
|
allow(example).to receive(:q1).and_return('foo')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'uses the referenced metadata to build the request' do
|
||||||
|
expect(request[:path]).to eq('/blogs?q1=foo')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'global basePath' do
|
context 'global basePath' do
|
||||||
before { global_metadata[:basePath] = '/foobar' }
|
before { swagger_doc[:basePath] = '/api' }
|
||||||
|
|
||||||
it 'prepends the basePath' do
|
it 'prepends to the path' do
|
||||||
expect(path).to eq('/foobar/blogs/1/comments/2')
|
expect(request[:path]).to eq('/api/blogs')
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
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
|
||||||
end
|
end
|
||||||
|
|
||||||
context "'body' parameter" do
|
context "global consumes" do
|
||||||
|
before { swagger_doc[:consumes] = [ 'application/xml' ] }
|
||||||
|
|
||||||
|
it "defaults 'CONTENT_TYPE' to global value(s)" do
|
||||||
|
expect(request[:headers]).to eq('CONTENT_TYPE' => 'application/xml')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "global security requirements" do
|
||||||
before do
|
before do
|
||||||
api_metadata[:operation][:parameters] << { name: 'comment', in: :body, schema: { type: 'object' } }
|
swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: :query } }
|
||||||
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
swagger_doc[:security] = [ apiKey: [] ]
|
||||||
|
allow(example).to receive(:api_key).and_return('foobar')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns the example value as a json string' do
|
it 'applieds the scheme by default' do
|
||||||
expect(body).to eq("{\"text\":\"Some comment\"}")
|
expect(request[:path]).to eq('/blogs?api_key=foobar')
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "referenced 'body' parameter" do
|
|
||||||
before do
|
|
||||||
api_metadata[:operation][:parameters] << { '$ref' => '#/parameters/comment' }
|
|
||||||
global_metadata[:parameters] = {
|
|
||||||
'comment' => { name: 'comment', in: :body, schema: { type: 'object' } }
|
|
||||||
}
|
|
||||||
allow(example).to receive(:comment).and_return(text: 'Some comment')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns the example value as a json string' do
|
|
||||||
expect(body).to eq("{\"text\":\"Some comment\"}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe '#build_headers' do
|
|
||||||
let(:headers) { subject.build_headers(example) }
|
|
||||||
|
|
||||||
context "no 'header' params" do
|
|
||||||
it 'returns an empty hash' do
|
|
||||||
expect(headers).to eq({})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "'header' params" do
|
|
||||||
before do
|
|
||||||
api_metadata[:operation][:parameters] << { name: 'Api-Key', in: :header, type: 'string' }
|
|
||||||
allow(example).to receive(:'Api-Key').and_return('foobar')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'returns a hash of names with example values' do
|
|
||||||
expect(headers).to eq({ 'Api-Key' => 'foobar' })
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'consumes & produces' do
|
|
||||||
before do
|
|
||||||
api_metadata[:operation][:consumes] = [ 'application/json', 'application/xml' ]
|
|
||||||
api_metadata[:operation][:produces] = [ 'application/json', 'application/xml' ]
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes corresponding 'Accept' & 'Content-Type' headers" do
|
|
||||||
expect(headers).to match(
|
|
||||||
'ACCEPT' => 'application/json;application/xml',
|
|
||||||
'CONTENT_TYPE' => 'application/json;application/xml'
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'global consumes & produces' do
|
|
||||||
let(:global_metadata) do
|
|
||||||
{
|
|
||||||
consumes: [ 'application/json', 'application/xml' ],
|
|
||||||
produces: [ 'application/json', 'application/xml' ]
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
it "includes corresponding 'Accept' & 'Content-Type' headers" do
|
|
||||||
expect(headers).to match(
|
|
||||||
'ACCEPT' => 'application/json;application/xml',
|
|
||||||
'CONTENT_TYPE' => 'application/json;application/xml'
|
|
||||||
)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,66 +4,71 @@ module Rswag
|
|||||||
module Specs
|
module Specs
|
||||||
|
|
||||||
describe ResponseValidator do
|
describe ResponseValidator do
|
||||||
let(:api_metadata) { { response: { code: 200 } } }
|
subject { ResponseValidator.new(config) }
|
||||||
let(:global_metadata) { {} }
|
|
||||||
|
|
||||||
subject { ResponseValidator.new(api_metadata, global_metadata) }
|
before do
|
||||||
|
allow(config).to receive(:get_swagger_doc).and_return(swagger_doc)
|
||||||
describe '#validate!(response)' do
|
end
|
||||||
let(:call) { subject.validate!(response) }
|
let(:config) { double('config') }
|
||||||
|
let(:swagger_doc) { {} }
|
||||||
context "no 'schema' provided" do
|
let(:example) { double('example') }
|
||||||
context 'response code matches' do
|
let(:metadata) do
|
||||||
let(:response) { OpenStruct.new(code: 200, body: '') }
|
{
|
||||||
it { expect { call }.to_not raise_error }
|
response: {
|
||||||
end
|
code: 200,
|
||||||
|
headers: { 'X-Rate-Limit-Limit' => { type: :integer } },
|
||||||
context 'response code does not match' do
|
schema: {
|
||||||
let(:response) { OpenStruct.new(code: 201, body: '') }
|
type: :object,
|
||||||
it { expect { call }.to raise_error UnexpectedResponse }
|
properties: { text: { type: :string } },
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "'schema' provided" do
|
|
||||||
before do
|
|
||||||
api_metadata[:response][:schema] = {
|
|
||||||
type: 'object',
|
|
||||||
properties: { text: { type: 'string' } },
|
|
||||||
required: [ 'text' ]
|
required: [ 'text' ]
|
||||||
}
|
}
|
||||||
end
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
context 'response code & body matches' do
|
describe '#validate!(metadata, response)' do
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"text\":\"Some comment\"}") }
|
let(:call) { subject.validate!(metadata, response) }
|
||||||
it { expect { call }.to_not raise_error }
|
let(:response) do
|
||||||
end
|
OpenStruct.new(
|
||||||
|
code: '200',
|
||||||
context 'response code matches & body does not' do
|
headers: { 'X-Rate-Limit-Limit' => '10' },
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some comment\"}") }
|
body: "{\"text\":\"Some comment\"}"
|
||||||
it { expect { call }.to raise_error UnexpectedResponse }
|
)
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "referenced 'schema' provided" do
|
context "response matches metadata" do
|
||||||
|
it { expect { call }.to_not raise_error }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "response code differs from metadata" do
|
||||||
|
before { response.code = '400' }
|
||||||
|
it { expect { call }.to raise_error /Expected response code/ }
|
||||||
|
end
|
||||||
|
|
||||||
|
context "response headers differ from metadata" do
|
||||||
|
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
|
before do
|
||||||
api_metadata[:response][:schema] = { '$ref' => '#/definitions/author' }
|
swagger_doc[:definitions] = {
|
||||||
global_metadata[:definitions] = {
|
'blog' => {
|
||||||
author: {
|
type: :object,
|
||||||
type: 'object',
|
properties: { foo: { type: :string } },
|
||||||
properties: { name: { type: 'string' } },
|
required: [ 'foo' ]
|
||||||
required: [ 'name' ]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
metadata[:response][:schema] = { '$ref' => '#/definitions/blog' }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'response code & body matches' do
|
it 'uses the referenced schema to validate the response body' do
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"name\":\"Some name\"}") }
|
expect { call }.to raise_error /Expected response body/
|
||||||
it { expect { call }.to_not raise_error }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'response code matches & body does not' do
|
|
||||||
let(:response) { OpenStruct.new(code: 200, body: "{\"foo\":\"Some name\"}") }
|
|
||||||
it { expect { call }.to raise_error UnexpectedResponse }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,112 +5,59 @@ 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
|
paths: {
|
||||||
before { call }
|
'/blogs' => {
|
||||||
|
post: {
|
||||||
it 'converts metadata to swagger and merges into the doc' do
|
summary: 'Creates a blog',
|
||||||
expect(swagger_docs.values.first).to match(
|
responses: {
|
||||||
info: { version: 'v1' },
|
'201' => { description: 'blog created' }
|
||||||
paths: {
|
|
||||||
'/blogs' => {
|
|
||||||
post: {
|
|
||||||
summary: 'Creates a blog',
|
|
||||||
responses: {
|
|
||||||
'201' => { description: 'blog created' }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'multiple swagger_docs' do
|
|
||||||
let(:swagger_docs) do
|
|
||||||
{
|
|
||||||
'v1/swagger.json' => {},
|
|
||||||
'v2/swagger.json' => {}
|
|
||||||
}
|
}
|
||||||
end
|
)
|
||||||
|
|
||||||
context "no 'swagger_doc' tag" do
|
|
||||||
before { call }
|
|
||||||
|
|
||||||
it 'merges into the first doc' do
|
|
||||||
expect(swagger_docs.values.first).to have_key(:paths)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "matching 'swagger_doc' tag" do
|
|
||||||
before do
|
|
||||||
api_metadata[:swagger_doc] = 'v2/swagger.json'
|
|
||||||
call
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'merges into the matched doc' do
|
|
||||||
expect(swagger_docs.values.last).to have_key(:paths)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context "non matching 'swagger_doc' tag" do
|
|
||||||
before { api_metadata[:swagger_doc] = 'foobar' }
|
|
||||||
it { expect { call }.to raise_error ConfigurationError }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
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")
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
require 'rswag/ui/version'
|
|
||||||
require 'rswag/ui/configuration'
|
require 'rswag/ui/configuration'
|
||||||
require 'rswag/ui/engine'
|
require 'rswag/ui/engine'
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,14 @@ module Rswag
|
|||||||
|
|
||||||
initializer 'rswag-ui.initialize' do |app|
|
initializer 'rswag-ui.initialize' do |app|
|
||||||
if app.config.respond_to?(:assets)
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
module Rswag
|
|
||||||
module Ui
|
|
||||||
VERSION = '1.0.1'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
$:.push File.expand_path("../lib", __FILE__)
|
$:.push File.expand_path("../lib", __FILE__)
|
||||||
|
|
||||||
# Maintain your gem's version:
|
|
||||||
require 'rswag/ui/version'
|
|
||||||
|
|
||||||
# Describe your gem and declare its dependencies:
|
# Describe your gem and declare its dependencies:
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "rswag-ui"
|
s.name = "rswag-ui"
|
||||||
s.version = Rswag::Ui::VERSION
|
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
s.authors = ["Richie Morris"]
|
s.authors = ["Richie Morris"]
|
||||||
s.email = ["domaindrivendev@gmail.com"]
|
s.email = ["domaindrivendev@gmail.com"]
|
||||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
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.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
|
end
|
||||||
|
|||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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;
|
||||||
@@ -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');
|
|
||||||
}
|
|
||||||
14
rswag-ui/vendor/assets/components/swagger-ui/css/typography.css.erb
vendored
Normal file
14
rswag-ui/vendor/assets/components/swagger-ui/css/typography.css.erb
vendored
Normal 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');
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
require 'rswag/version'
|
|
||||||
require 'rswag/specs'
|
require 'rswag/specs'
|
||||||
require 'rswag/api'
|
require 'rswag/api'
|
||||||
require 'rswag/ui'
|
require 'rswag/ui'
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
module Rswag
|
|
||||||
VERSION = '1.0.1'
|
|
||||||
end
|
|
||||||
@@ -1,12 +1,9 @@
|
|||||||
$:.push File.expand_path("../lib", __FILE__)
|
$:.push File.expand_path("../lib", __FILE__)
|
||||||
|
|
||||||
# Maintain your gem's version:
|
|
||||||
require 'rswag/version'
|
|
||||||
|
|
||||||
# Describe your gem and declare its dependencies:
|
# Describe your gem and declare its dependencies:
|
||||||
Gem::Specification.new do |s|
|
Gem::Specification.new do |s|
|
||||||
s.name = "rswag"
|
s.name = "rswag"
|
||||||
s.version = Rswag::VERSION
|
s.version = ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
s.authors = ["Richie Morris"]
|
s.authors = ["Richie Morris"]
|
||||||
s.email = ["domaindrivendev@gmail.com"]
|
s.email = ["domaindrivendev@gmail.com"]
|
||||||
s.homepage = "https://github.com/domaindrivendev/rswag"
|
s.homepage = "https://github.com/domaindrivendev/rswag"
|
||||||
@@ -16,7 +13,7 @@ Gem::Specification.new do |s|
|
|||||||
|
|
||||||
s.files = Dir["{lib}/**/*"] + [ "MIT-LICENSE" ]
|
s.files = Dir["{lib}/**/*"] + [ "MIT-LICENSE" ]
|
||||||
|
|
||||||
s.add_dependency 'rswag-specs', Rswag::VERSION
|
s.add_dependency 'rswag-specs', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
s.add_dependency 'rswag-api', Rswag::VERSION
|
s.add_dependency 'rswag-api', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
s.add_dependency 'rswag-ui', Rswag::VERSION
|
s.add_dependency 'rswag-ui', ENV['TRAVIS_TAG'] || '0.0.0'
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
class ApplicationController < ActionController::Base
|
class ApplicationController < ActionController::Base
|
||||||
# Prevent CSRF attacks by raising an exception.
|
# Prevent CSRF attacks by raising an exception.
|
||||||
# For APIs, you may want to use :null_session instead.
|
# 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 ]
|
wrap_parameters format: [ :json ]
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
rescue_from 'ActionController::UnknownFormat' do |ex|
|
||||||
|
head :not_acceptable
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
11
test-app/app/controllers/auth_tests_controller.rb
Normal file
11
test-app/app/controllers/auth_tests_controller.rb
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
class AuthTestsController < ApplicationController
|
||||||
|
|
||||||
|
# POST /auth-tests/basic
|
||||||
|
def basic
|
||||||
|
if authenticate_with_http_basic { |u, p| u == 'jsmith' && p == 'jspass' }
|
||||||
|
head :no_content
|
||||||
|
else
|
||||||
|
request_http_basic_authentication
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
require 'fileutils'
|
||||||
|
|
||||||
class BlogsController < ApplicationController
|
class BlogsController < ApplicationController
|
||||||
wrap_parameters Blog
|
|
||||||
respond_to :json
|
|
||||||
|
|
||||||
# POST /blogs
|
# POST /blogs
|
||||||
def create
|
def create
|
||||||
@@ -8,6 +8,14 @@ class BlogsController < ApplicationController
|
|||||||
respond_with @blog
|
respond_with @blog
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Put /blogs/1
|
||||||
|
def upload
|
||||||
|
@blog = Blog.find_by_id(params[:id])
|
||||||
|
return head :not_found if @blog.nil?
|
||||||
|
@blog.thumbnail = save_uploaded_file params[:file]
|
||||||
|
head @blog.save ? :ok : :unprocsessible_entity
|
||||||
|
end
|
||||||
|
|
||||||
# GET /blogs
|
# GET /blogs
|
||||||
def index
|
def index
|
||||||
@blogs = Blog.all
|
@blogs = Blog.all
|
||||||
@@ -17,7 +25,20 @@ 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
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def save_uploaded_file(field)
|
||||||
|
return if field.nil?
|
||||||
|
file = File.join('tmp', field.original_filename)
|
||||||
|
FileUtils.cp field.tempfile.path, file
|
||||||
|
field.original_filename
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ class Blog < ActiveRecord::Base
|
|||||||
{
|
{
|
||||||
id: id,
|
id: id,
|
||||||
title: title,
|
title: title,
|
||||||
content: content
|
content: nil,
|
||||||
|
thumbnail: thumbnail
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
require 'rubygems'
|
require 'rubygems'
|
||||||
gemfile = File.expand_path('../../../../Gemfile', __FILE__)
|
gemfile = File.expand_path('../../../Gemfile', __FILE__)
|
||||||
|
|
||||||
if File.exist?(gemfile)
|
if File.exist?(gemfile)
|
||||||
ENV['BUNDLE_GEMFILE'] = gemfile
|
ENV['BUNDLE_GEMFILE'] = gemfile
|
||||||
@@ -7,4 +7,4 @@ if File.exist?(gemfile)
|
|||||||
Bundler.setup
|
Bundler.setup
|
||||||
end
|
end
|
||||||
|
|
||||||
$:.unshift File.expand_path('../../../../lib', __FILE__)
|
$:.unshift File.expand_path('../../../../lib', __FILE__)
|
||||||
|
|||||||
@@ -28,4 +28,6 @@ TestApp::Application.configure do
|
|||||||
|
|
||||||
# Expands the lines which load the assets
|
# Expands the lines which load the assets
|
||||||
config.assets.debug = true
|
config.assets.debug = true
|
||||||
|
|
||||||
|
config.eager_load = false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -32,4 +32,6 @@ TestApp::Application.configure do
|
|||||||
|
|
||||||
# Print deprecation notices to the stderr
|
# Print deprecation notices to the stderr
|
||||||
config.active_support.deprecation = :stderr
|
config.active_support.deprecation = :stderr
|
||||||
|
|
||||||
|
config.eager_load = false
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,3 +5,6 @@
|
|||||||
# Make sure the secret is at least 30 characters and all random,
|
# Make sure the secret is at least 30 characters and all random,
|
||||||
# no regular words or you'll be exposed to dictionary attacks.
|
# no regular words or you'll be exposed to dictionary attacks.
|
||||||
TestApp::Application.config.secret_token = '60f36cd33756d73f362053f1d45256ae50d75440b634ae73b070a6e35a2df38692f59e28e5ecbd1f9f2e850255f6d29a468bc59ac4484c2b7f0548ddbfc1b870'
|
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,8 @@
|
|||||||
TestApp::Application.routes.draw do
|
TestApp::Application.routes.draw do
|
||||||
resources :blogs, defaults: { :format => :json }
|
resources :blogs
|
||||||
|
put '/blogs/:id/upload', to: 'blogs#upload'
|
||||||
|
|
||||||
|
post 'auth-tests/basic', to: 'auth_tests#basic'
|
||||||
|
|
||||||
mount Rswag::Api::Engine => 'api-docs'
|
mount Rswag::Api::Engine => 'api-docs'
|
||||||
mount Rswag::Ui::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
|
def change
|
||||||
create_table :blogs do |t|
|
create_table :blogs do |t|
|
||||||
t.string :title
|
t.string :title
|
||||||
t.text :content
|
t.text :content
|
||||||
|
t.string :thumbnail
|
||||||
|
|
||||||
t.timestamps
|
t.timestamps
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
# encoding: UTF-8
|
|
||||||
# This file is auto-generated from the current state of the database. Instead
|
# This file is auto-generated from the current state of the database. Instead
|
||||||
# of editing this file, please use the migrations feature of Active Record to
|
# of editing this file, please use the migrations feature of Active Record to
|
||||||
# incrementally modify your database, and then regenerate this schema definition.
|
# incrementally modify your database, and then regenerate this schema definition.
|
||||||
@@ -9,15 +8,16 @@
|
|||||||
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
|
||||||
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
# you'll amass, the slower it'll run and the greater likelihood for issues).
|
||||||
#
|
#
|
||||||
# It's strongly recommended to check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(:version => 20160218212104) do
|
ActiveRecord::Schema.define(version: 20160218212104) do
|
||||||
|
|
||||||
create_table "blogs", :force => true do |t|
|
create_table "blogs", force: :cascade do |t|
|
||||||
t.string "title"
|
t.string "title"
|
||||||
t.text "content"
|
t.text "content"
|
||||||
t.datetime "created_at", :null => false
|
t.string "thumbnail"
|
||||||
t.datetime "updated_at", :null => false
|
t.datetime "created_at"
|
||||||
|
t.datetime "updated_at"
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
12
test-app/spec/features/swagger_ui_spec.rb
Normal file
12
test-app/spec/features/swagger_ui_spec.rb
Normal 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
|
||||||
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 |
22
test-app/spec/integration/auth_tests_spec.rb
Normal file
22
test-app/spec/integration/auth_tests_spec.rb
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
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 Test'
|
||||||
|
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
|
||||||
|
end
|
||||||
@@ -9,10 +9,12 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
description 'Creates a new blog from provided data'
|
description 'Creates a new blog from provided data'
|
||||||
operationId 'createBlog'
|
operationId 'createBlog'
|
||||||
consumes 'application/json'
|
consumes 'application/json'
|
||||||
parameter name: :blog, :in => :body, schema: { '$ref' => '#/definitions/blog' }
|
produces 'application/json'
|
||||||
|
parameter name: :blog, in: :body, schema: { '$ref' => '#/definitions/blog' }
|
||||||
|
|
||||||
|
let(:blog) { { title: 'foo', content: 'bar' } }
|
||||||
|
|
||||||
response '201', 'blog created' do
|
response '201', 'blog created' do
|
||||||
let(:blog) { { title: 'foo', content: 'bar' } }
|
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -20,7 +22,9 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
schema '$ref' => '#/definitions/errors_object'
|
schema '$ref' => '#/definitions/errors_object'
|
||||||
|
|
||||||
let(:blog) { { title: 'foo' } }
|
let(:blog) { { title: 'foo' } }
|
||||||
run_test!
|
run_test! do |response|
|
||||||
|
expect(response.body).to include("can't be blank")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -31,27 +35,45 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
produces 'application/json'
|
produces 'application/json'
|
||||||
parameter name: :keywords, in: :query, type: 'string'
|
parameter name: :keywords, in: :query, type: 'string'
|
||||||
|
|
||||||
|
let(:keywords) { 'foo bar' }
|
||||||
|
|
||||||
response '200', 'success' do
|
response '200', 'success' do
|
||||||
schema type: 'array', items: { '$ref' => '#/definitions/blog' }
|
schema type: 'array', items: { '$ref' => '#/definitions/blog' }
|
||||||
|
end
|
||||||
|
|
||||||
let(:keywords) { 'foo bar' }
|
response '406', 'unsupported accept header' do
|
||||||
|
let(:'Accept') { 'application/foo' }
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
path '/blogs/{id}' do
|
path '/blogs/{id}' do
|
||||||
|
parameter name: :id, in: :path, type: :string
|
||||||
|
|
||||||
|
let(:id) { blog.id }
|
||||||
|
let(:blog) { Blog.create(title: 'foo', content: 'bar', thumbnail: 'thumbnail.png') }
|
||||||
|
|
||||||
get 'Retrieves a blog' do
|
get 'Retrieves a blog' do
|
||||||
tags 'Blogs'
|
tags 'Blogs'
|
||||||
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'
|
||||||
|
|
||||||
let(:blog) { Blog.create(title: 'foo', content: 'bar') }
|
examples 'application/json' => {
|
||||||
|
id: 1,
|
||||||
|
title: 'Hello world!',
|
||||||
|
content: 'Hello world and hello universe. Thank you all very much!!!',
|
||||||
|
thumbnail: "thumbnail.png"
|
||||||
|
}
|
||||||
|
|
||||||
let(:id) { blog.id }
|
let(:id) { blog.id }
|
||||||
run_test!
|
run_test!
|
||||||
end
|
end
|
||||||
@@ -62,4 +84,24 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
path '/blogs/{id}/upload' do
|
||||||
|
parameter name: :id, in: :path, type: :string
|
||||||
|
|
||||||
|
let(:id) { blog.id }
|
||||||
|
let(:blog) { Blog.create(title: 'foo', content: 'bar') }
|
||||||
|
|
||||||
|
put 'upload a blog thumbnail' do
|
||||||
|
tags 'Blogs'
|
||||||
|
description 'Upload a thumbnail for specific blog by id'
|
||||||
|
operationId 'uploadThumbnailBlog'
|
||||||
|
consumes 'multipart/form-data'
|
||||||
|
parameter name: :file, :in => :formData, :type => :file, required: true
|
||||||
|
|
||||||
|
response '200', 'blog updated' do
|
||||||
|
let(:file) { Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/thumbnail.png")) }
|
||||||
|
run_test!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -47,6 +47,10 @@ RSpec.configure do |config|
|
|||||||
# triggering implicit auto-inclusion in groups with matching metadata.
|
# triggering implicit auto-inclusion in groups with matching metadata.
|
||||||
config.shared_context_metadata_behavior = :apply_to_host_groups
|
config.shared_context_metadata_behavior = :apply_to_host_groups
|
||||||
|
|
||||||
|
config.after(:suite) do
|
||||||
|
File.delete("#{Rails.root}/tmp/thumbnail.png") if File.file?("#{Rails.root}/tmp/thumbnail.png")
|
||||||
|
end
|
||||||
|
|
||||||
# The settings below are suggested to provide a good initial experience
|
# The settings below are suggested to provide a good initial experience
|
||||||
# with RSpec, but feel free to customize to your heart's content.
|
# with RSpec, but feel free to customize to your heart's content.
|
||||||
=begin
|
=begin
|
||||||
|
|||||||
@@ -39,21 +39,25 @@ RSpec.configure do |config|
|
|||||||
properties: {
|
properties: {
|
||||||
id: { type: 'integer' },
|
id: { type: 'integer' },
|
||||||
title: { type: 'string' },
|
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: {
|
securityDefinitions: {
|
||||||
|
basic_auth: {
|
||||||
|
type: :basic
|
||||||
|
},
|
||||||
api_key: {
|
api_key: {
|
||||||
type: :apiKey,
|
type: :apiKey,
|
||||||
name: 'api_key',
|
name: 'api_key',
|
||||||
in: :query
|
in: :query
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
security: {
|
security: [
|
||||||
api_key: []
|
{ api_key: [] }
|
||||||
}
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -5,6 +5,30 @@
|
|||||||
"version": "v1"
|
"version": "v1"
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
|
"/auth-tests/basic": {
|
||||||
|
"post": {
|
||||||
|
"summary": "Authenticates with basic auth",
|
||||||
|
"tags": [
|
||||||
|
"Auth Test"
|
||||||
|
],
|
||||||
|
"operationId": "testBasicAuth",
|
||||||
|
"security": [
|
||||||
|
{
|
||||||
|
"basic_auth": [
|
||||||
|
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"204": {
|
||||||
|
"description": "Valid credentials"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "Invalid credentials"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"/blogs": {
|
"/blogs": {
|
||||||
"post": {
|
"post": {
|
||||||
"summary": "Creates a blog",
|
"summary": "Creates a blog",
|
||||||
@@ -16,6 +40,9 @@
|
|||||||
"consumes": [
|
"consumes": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
"produces": [
|
||||||
|
"application/json"
|
||||||
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "blog",
|
"name": "blog",
|
||||||
@@ -55,19 +82,21 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"406": {
|
||||||
"description": "success",
|
"description": "unsupported accept header"
|
||||||
"schema": {
|
|
||||||
"type": "array",
|
|
||||||
"items": {
|
|
||||||
"$ref": "#/definitions/blog"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/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,23 +107,68 @@
|
|||||||
"produces": [
|
"produces": [
|
||||||
"application/json"
|
"application/json"
|
||||||
],
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "blog found",
|
||||||
|
"headers": {
|
||||||
|
"ETag": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Last-Modified": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"Cache-Control": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/blog"
|
||||||
|
},
|
||||||
|
"examples": {
|
||||||
|
"application/json": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "Hello world!",
|
||||||
|
"content": "Hello world and hello universe. Thank you all very much!!!",
|
||||||
|
"thumbnail": "thumbnail.png"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "blog not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/blogs/{id}/upload": {
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"type": "string",
|
||||||
|
"required": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"put": {
|
||||||
|
"summary": "upload a blog thumbnail",
|
||||||
|
"tags": [
|
||||||
|
"Blogs"
|
||||||
|
],
|
||||||
|
"description": "Upload a thumbnail for specific blog by id",
|
||||||
|
"operationId": "uploadThumbnailBlog",
|
||||||
|
"consumes": [
|
||||||
|
"multipart/form-data"
|
||||||
|
],
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "id",
|
"name": "file",
|
||||||
"in": "path",
|
"in": "formData",
|
||||||
"type": "string",
|
"type": "file",
|
||||||
"required": true
|
"required": true
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "blog found",
|
"description": "blog updated"
|
||||||
"schema": {
|
|
||||||
"$ref": "#/definitions/blog"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"404": {
|
|
||||||
"description": "blog not found"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -128,26 +202,36 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"content": {
|
"content": {
|
||||||
|
"type": "string",
|
||||||
|
"x-nullable": true
|
||||||
|
},
|
||||||
|
"thumbnail": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
"id",
|
"id",
|
||||||
"title",
|
"title",
|
||||||
"content"
|
"content",
|
||||||
|
"thumbnail"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"securityDefinitions": {
|
"securityDefinitions": {
|
||||||
|
"basic_auth": {
|
||||||
|
"type": "basic"
|
||||||
|
},
|
||||||
"api_key": {
|
"api_key": {
|
||||||
"type": "apiKey",
|
"type": "apiKey",
|
||||||
"name": "api_key",
|
"name": "api_key",
|
||||||
"in": "query"
|
"in": "query"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"security": {
|
"security": [
|
||||||
"api_key": [
|
{
|
||||||
|
"api_key": [
|
||||||
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user