mirror of
https://github.com/ditkrg/rswag.git
synced 2026-01-24 23:06:41 +00:00
Basic functionality via minitest
This commit is contained in:
parent
007b82a9e4
commit
f9225a8a22
85
lib/swagger_rails/testing/example_builder.rb
Normal file
85
lib/swagger_rails/testing/example_builder.rb
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
module SwaggerRails
|
||||||
|
|
||||||
|
class ExampleBuilder
|
||||||
|
attr_reader :expected_status
|
||||||
|
|
||||||
|
def initialize(path_template, http_method, swagger)
|
||||||
|
@path_template = path_template
|
||||||
|
@http_method = http_method
|
||||||
|
@swagger = swagger
|
||||||
|
@swagger_operation = find_swagger_operation!
|
||||||
|
@expected_status = find_swagger_success_status!
|
||||||
|
@param_values = {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def expect(status)
|
||||||
|
@expected_status = status
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(param_values)
|
||||||
|
@param_values.merge!(param_values.stringify_keys)
|
||||||
|
end
|
||||||
|
|
||||||
|
def path
|
||||||
|
@path_template.dup.tap do |template|
|
||||||
|
template.prepend(@swagger['basePath'].presence || '')
|
||||||
|
path_params = param_values_for('path')
|
||||||
|
path_params.each { |name, value| template.sub!("\{#{name}\}", value) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def params
|
||||||
|
query_params = param_values_for('query')
|
||||||
|
body_params = param_values_for('body')
|
||||||
|
query_params.merge(body_params.values.first || {})
|
||||||
|
end
|
||||||
|
|
||||||
|
def headers
|
||||||
|
param_values_for('header')
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def find_swagger_operation!
|
||||||
|
find_swagger_item!('paths', @path_template, @http_method)
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_swagger_success_status!
|
||||||
|
path_keys = [ 'paths', @path_template, @http_method, 'responses' ]
|
||||||
|
responses = find_swagger_item!(*path_keys)
|
||||||
|
key = responses.keys.find { |k| k.start_with?('2') }
|
||||||
|
key ? key.to_i : (raise MetadataError.new(path_keys.concat('2xx')))
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_swagger_item!(*path_keys)
|
||||||
|
item = @swagger
|
||||||
|
path_keys.each do |key|
|
||||||
|
item = item[key] || (raise MetadataError.new(*path_keys))
|
||||||
|
end
|
||||||
|
item
|
||||||
|
end
|
||||||
|
|
||||||
|
def param_values_for(location)
|
||||||
|
params = (@swagger_operation['parameters'] || []).select { |p| p['in'] == location }
|
||||||
|
Hash[params.map { |param| [ param['name'], value_for(param) ] }]
|
||||||
|
end
|
||||||
|
|
||||||
|
def value_for(param)
|
||||||
|
return @param_values[param['name']] if @param_values.has_key?(param['name'])
|
||||||
|
return param['default'] unless param['in'] == 'body'
|
||||||
|
schema_for(param['schema'])['example']
|
||||||
|
end
|
||||||
|
|
||||||
|
def schema_for(schema_or_ref)
|
||||||
|
return schema_or_ref if schema_or_ref['$ref'].nil?
|
||||||
|
@swagger['definitions'][schema_or_ref['$ref'].sub('#/definitions/', '')]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
class MetadataError < StandardError
|
||||||
|
def initialize(*path_keys)
|
||||||
|
path = path_keys.map { |key| "['#{key}']" }.join('')
|
||||||
|
super("Swagger document is missing expected metadata at #{path}")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
34
lib/swagger_rails/testing/test_helpers.rb
Normal file
34
lib/swagger_rails/testing/test_helpers.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
require 'swagger_rails/testing/test_visitor'
|
||||||
|
|
||||||
|
module SwaggerRails
|
||||||
|
module TestHelpers
|
||||||
|
|
||||||
|
def self.included(klass)
|
||||||
|
klass.extend(ClassMethods)
|
||||||
|
end
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
attr_reader :test_visitor
|
||||||
|
|
||||||
|
def swagger_doc(swagger_doc)
|
||||||
|
file_path = File.join(Rails.root, 'config/swagger', swagger_doc)
|
||||||
|
@swagger = JSON.parse(File.read(file_path))
|
||||||
|
@test_visitor = SwaggerRails::TestVisitor.new(@swagger)
|
||||||
|
end
|
||||||
|
|
||||||
|
def swagger_test_all
|
||||||
|
@swagger['paths'].each do |path, path_item|
|
||||||
|
path_item.keys.each do |method|
|
||||||
|
test "#{path} #{method}" do
|
||||||
|
swagger_test path, method
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def swagger_test(path, method)
|
||||||
|
self.class.test_visitor.run_test(path, method, self)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
25
lib/swagger_rails/testing/test_visitor.rb
Normal file
25
lib/swagger_rails/testing/test_visitor.rb
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
require 'swagger_rails/testing/example_builder'
|
||||||
|
|
||||||
|
module SwaggerRails
|
||||||
|
|
||||||
|
class TestVisitor
|
||||||
|
|
||||||
|
def initialize(swagger)
|
||||||
|
@swagger = swagger
|
||||||
|
end
|
||||||
|
|
||||||
|
def run_test(path_template, http_method, test, &block)
|
||||||
|
example = ExampleBuilder.new(path_template, http_method, @swagger)
|
||||||
|
example.instance_exec(&block) if block_given?
|
||||||
|
|
||||||
|
test.send(http_method,
|
||||||
|
example.path,
|
||||||
|
example.params,
|
||||||
|
example.headers
|
||||||
|
)
|
||||||
|
|
||||||
|
test.assert_response(example.expected_status)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@ -2,4 +2,6 @@ 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: :exception
|
||||||
|
|
||||||
|
wrap_parameters format: [ :json ]
|
||||||
end
|
end
|
||||||
|
|||||||
14
spec/dummy/app/controllers/blogs_controller.rb
Normal file
14
spec/dummy/app/controllers/blogs_controller.rb
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
class BlogsController < ApplicationController
|
||||||
|
|
||||||
|
def create
|
||||||
|
render json: {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: {}
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: {}
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -6,7 +6,7 @@ require "action_controller/railtie"
|
|||||||
require "action_mailer/railtie"
|
require "action_mailer/railtie"
|
||||||
require "action_view/railtie"
|
require "action_view/railtie"
|
||||||
require "sprockets/railtie"
|
require "sprockets/railtie"
|
||||||
# require "rails/test_unit/railtie"
|
require "rails/test_unit/railtie"
|
||||||
|
|
||||||
Bundler.require(*Rails.groups)
|
Bundler.require(*Rails.groups)
|
||||||
require "swagger_rails"
|
require "swagger_rails"
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
Rails.application.routes.draw do
|
Rails.application.routes.draw do
|
||||||
mount SwaggerRails::Engine => '/api-docs'
|
mount SwaggerRails::Engine => '/api-docs'
|
||||||
|
|
||||||
|
resources :blogs, only: [ :create, :index, :show ]
|
||||||
end
|
end
|
||||||
|
|||||||
@ -2,43 +2,117 @@
|
|||||||
"swagger": "2.0",
|
"swagger": "2.0",
|
||||||
"info": {
|
"info": {
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"title": "[Enter a description for your API here]",
|
"title": "Dummy app for testing swagger_rails"
|
||||||
"description": "The docs below are powered by the default swagger.json that gets installed with swagger_rails. You'll need to update it to describe your API. See here for the complete swagger spec - https://github.com/swagger-api/swagger-spec/blob/master/versions/2.0.md"
|
|
||||||
},
|
},
|
||||||
"paths": {
|
"paths": {
|
||||||
"/a/sample/resource": {
|
"/blogs": {
|
||||||
"post": {
|
"post": {
|
||||||
"tags": [
|
"description": "Creates a new Blog",
|
||||||
"a/sample/resource"
|
|
||||||
],
|
|
||||||
"description": "Create a new sample resource",
|
|
||||||
"parameters": [
|
"parameters": [
|
||||||
{
|
{
|
||||||
"name": "body",
|
"name": "X-Forwarded-For",
|
||||||
|
"in": "header",
|
||||||
|
"type": "string",
|
||||||
|
"default": "client1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "blog",
|
||||||
"in": "body",
|
"in": "body",
|
||||||
"schema": {
|
"schema": {
|
||||||
"$ref": "#/definitions/CreateSampleResource"
|
"$ref": "#/definitions/Blog"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"200": {
|
||||||
"description": "Ok"
|
"description": "Ok",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Blog"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"400": {
|
||||||
|
"description": "Invalid Request",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/RequestError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"get": {
|
||||||
|
"description": "Searches Bloggs",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "published",
|
||||||
|
"in": "query",
|
||||||
|
"type": "boolean",
|
||||||
|
"default": "true"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "keywords",
|
||||||
|
"in": "query",
|
||||||
|
"type": "string",
|
||||||
|
"default": "Ruby on Rails"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Ok",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Blog"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
"/blogs/{id}": {
|
||||||
|
"get": {
|
||||||
|
"description": "Retrieves a specific Blog by unique ID",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"type": "string",
|
||||||
|
"default": "123"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Ok",
|
||||||
|
"schema": {
|
||||||
|
"$ref": "#/definitions/Blog"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"definitions": {
|
"definitions": {
|
||||||
"CreateSampleResource": {
|
"Blog": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": {
|
"title": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"date_time": {
|
"content": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"title": "Test Blog",
|
||||||
|
"content": "Hello World"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RequestError": {
|
||||||
|
"type": "object",
|
||||||
|
"additionalProperties": {
|
||||||
|
"type": "array",
|
||||||
|
"item": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"title": [ "is required" ]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
9
spec/dummy/test/integration/v1_contract_test.rb
Normal file
9
spec/dummy/test/integration/v1_contract_test.rb
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
require 'test_helper'
|
||||||
|
require 'swagger_rails/testing/test_helpers'
|
||||||
|
|
||||||
|
class V1ContractTest < ActionDispatch::IntegrationTest
|
||||||
|
include SwaggerRails::TestHelpers
|
||||||
|
|
||||||
|
swagger_doc 'v1/swagger.json'
|
||||||
|
swagger_test_all
|
||||||
|
end
|
||||||
7
spec/dummy/test/test_helper.rb
Normal file
7
spec/dummy/test/test_helper.rb
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ENV["RAILS_ENV"] = "test"
|
||||||
|
require File.expand_path('../../config/environment', __FILE__)
|
||||||
|
require 'rails/test_help'
|
||||||
|
|
||||||
|
class ActiveSupport::TestCase
|
||||||
|
# Add more helper methods to be used by all tests here...
|
||||||
|
end
|
||||||
119
spec/testing/example_builder_spec.rb
Normal file
119
spec/testing/example_builder_spec.rb
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
require 'swagger_rails/testing/example_builder'
|
||||||
|
|
||||||
|
module SwaggerRails
|
||||||
|
|
||||||
|
describe ExampleBuilder do
|
||||||
|
subject { described_class.new(path, method, swagger) }
|
||||||
|
let(:swagger) do
|
||||||
|
file_path = File.join(Rails.root, 'config/swagger', 'v1/swagger.json')
|
||||||
|
JSON.parse(File.read(file_path))
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#path' do
|
||||||
|
context 'operation with path params' do
|
||||||
|
let(:path) { '/blogs/{id}' }
|
||||||
|
let(:method) { 'get' }
|
||||||
|
|
||||||
|
context 'by default' do
|
||||||
|
it "returns path based on 'default' values" do
|
||||||
|
expect(subject.path).to eq('/blogs/123')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'values explicitly set' do
|
||||||
|
before { subject.set id: '456' }
|
||||||
|
it 'returns path based on set values' do
|
||||||
|
expect(subject.path).to eq('/blogs/456')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'swagger includes basePath' do
|
||||||
|
before { swagger['basePath'] = '/foobar' }
|
||||||
|
let(:path) { '/blogs' }
|
||||||
|
let(:method) { 'post' }
|
||||||
|
|
||||||
|
it 'returns path prefixed with basePath' do
|
||||||
|
expect(subject.path).to eq('/foobar/blogs')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#params' do
|
||||||
|
context 'operation with body param' do
|
||||||
|
let(:path) { '/blogs' }
|
||||||
|
let(:method) { 'post' }
|
||||||
|
|
||||||
|
context 'by default' do
|
||||||
|
it "returns schema 'example'" do
|
||||||
|
expect(subject.params).to eq(swagger['definitions']['Blog']['example'])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'value explicitly set' do
|
||||||
|
before { subject.set blog: { 'title' => 'foobar' } }
|
||||||
|
it 'returns params value' do
|
||||||
|
expect(subject.params).to eq({ 'title' => 'foobar' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'operation with query params' do
|
||||||
|
let(:path) { '/blogs' }
|
||||||
|
let(:method) { 'get' }
|
||||||
|
|
||||||
|
context 'by default' do
|
||||||
|
it "returns query params based on 'default' values" do
|
||||||
|
expect(subject.params).to eq({ 'published' => 'true', 'keywords' => 'Ruby on Rails' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'values explicitly set' do
|
||||||
|
before { subject.set keywords: 'Java' }
|
||||||
|
it 'returns query params based on set values' do
|
||||||
|
expect(subject.params).to eq({ 'published' => 'true', 'keywords' => 'Java' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#headers' do
|
||||||
|
context 'operation with header params' do
|
||||||
|
let(:path) { '/blogs' }
|
||||||
|
let(:method) { 'post' }
|
||||||
|
|
||||||
|
context 'by default' do
|
||||||
|
it "returns headers based on 'default' values" do
|
||||||
|
expect(subject.headers).to eq({ 'X-Forwarded-For' => 'client1' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'values explicitly params' do
|
||||||
|
before { subject.set 'X-Forwarded-For' => '192.168.1.1' }
|
||||||
|
it 'returns headers based on params values' do
|
||||||
|
expect(subject.headers).to eq({ 'X-Forwarded-For' => '192.168.1.1' })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe '#expected_status' do
|
||||||
|
let(:path) { '/blogs' }
|
||||||
|
let(:method) { 'post' }
|
||||||
|
|
||||||
|
context 'by default' do
|
||||||
|
it "returns first 2xx status in 'responses'" do
|
||||||
|
expect(subject.expected_status).to eq(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'expected status explicitly params' do
|
||||||
|
before { subject.expect 400 }
|
||||||
|
it "returns params status" do
|
||||||
|
expect(subject.expected_status).to eq(400)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
61
spec/testing/test_visitor_spec.rb
Normal file
61
spec/testing/test_visitor_spec.rb
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
require 'swagger_rails/testing/test_visitor'
|
||||||
|
|
||||||
|
module SwaggerRails
|
||||||
|
|
||||||
|
describe TestVisitor do
|
||||||
|
subject { described_class.new(swagger) }
|
||||||
|
let(:swagger) do
|
||||||
|
file_path = File.join(Rails.root, 'config/swagger', 'v1/swagger.json')
|
||||||
|
JSON.parse(File.read(file_path))
|
||||||
|
end
|
||||||
|
let(:test) { spy('test') }
|
||||||
|
|
||||||
|
describe '#run_test' do
|
||||||
|
context 'by default' do
|
||||||
|
before { subject.run_test('/blogs', 'post', test) }
|
||||||
|
|
||||||
|
it "submits request based on 'default' and 'example' param values" do
|
||||||
|
expect(test).to have_received(:post).with(
|
||||||
|
'/blogs',
|
||||||
|
{ 'title' => 'Test Blog', 'content' => 'Hello World' },
|
||||||
|
{ 'X-Forwarded-For' => 'client1' }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "asserts response matches first 2xx status in operation 'responses'" do
|
||||||
|
expect(test).to have_received(:assert_response).with(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'param values explicitly provided' do
|
||||||
|
before do
|
||||||
|
subject.run_test('/blogs', 'post', test) do
|
||||||
|
set blog: { 'title' => 'foobar' }
|
||||||
|
set 'X-Forwarded-For' => '192.168.1.1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'submits a request based on provided param values' do
|
||||||
|
expect(test).to have_received(:post).with(
|
||||||
|
'/blogs',
|
||||||
|
{ 'title' => 'foobar' },
|
||||||
|
{ 'X-Forwarded-For' => '192.168.1.1' }
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'expected status explicitly params' do
|
||||||
|
before do
|
||||||
|
subject.run_test('/blogs', 'post', test) do
|
||||||
|
expect 400
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "asserts response matches params status" do
|
||||||
|
expect(test).to have_received(:assert_response).with(400)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in New Issue
Block a user