From f9225a8a22a31370b37bdc99c0707f3937159218 Mon Sep 17 00:00:00 2001 From: domaindrivendev Date: Tue, 16 Feb 2016 17:32:05 -0800 Subject: [PATCH 1/4] Basic functionality via minitest --- lib/swagger_rails/testing/example_builder.rb | 85 +++++++++++++ lib/swagger_rails/testing/test_helpers.rb | 34 +++++ lib/swagger_rails/testing/test_visitor.rb | 25 ++++ .../app/controllers/application_controller.rb | 2 + .../dummy/app/controllers/blogs_controller.rb | 14 +++ spec/dummy/config/application.rb | 2 +- spec/dummy/config/routes.rb | 1 + spec/dummy/config/swagger/v1/swagger.json | 100 +++++++++++++-- .../test/integration/v1_contract_test.rb | 9 ++ spec/dummy/test/test_helper.rb | 7 ++ spec/testing/example_builder_spec.rb | 119 ++++++++++++++++++ spec/testing/test_visitor_spec.rb | 61 +++++++++ 12 files changed, 445 insertions(+), 14 deletions(-) create mode 100644 lib/swagger_rails/testing/example_builder.rb create mode 100644 lib/swagger_rails/testing/test_helpers.rb create mode 100644 lib/swagger_rails/testing/test_visitor.rb create mode 100644 spec/dummy/app/controllers/blogs_controller.rb create mode 100644 spec/dummy/test/integration/v1_contract_test.rb create mode 100644 spec/dummy/test/test_helper.rb create mode 100644 spec/testing/example_builder_spec.rb create mode 100644 spec/testing/test_visitor_spec.rb diff --git a/lib/swagger_rails/testing/example_builder.rb b/lib/swagger_rails/testing/example_builder.rb new file mode 100644 index 0000000..8cfcbae --- /dev/null +++ b/lib/swagger_rails/testing/example_builder.rb @@ -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 diff --git a/lib/swagger_rails/testing/test_helpers.rb b/lib/swagger_rails/testing/test_helpers.rb new file mode 100644 index 0000000..517002d --- /dev/null +++ b/lib/swagger_rails/testing/test_helpers.rb @@ -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 diff --git a/lib/swagger_rails/testing/test_visitor.rb b/lib/swagger_rails/testing/test_visitor.rb new file mode 100644 index 0000000..9e74ad8 --- /dev/null +++ b/lib/swagger_rails/testing/test_visitor.rb @@ -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 + diff --git a/spec/dummy/app/controllers/application_controller.rb b/spec/dummy/app/controllers/application_controller.rb index d83690e..a4506fc 100644 --- a/spec/dummy/app/controllers/application_controller.rb +++ b/spec/dummy/app/controllers/application_controller.rb @@ -2,4 +2,6 @@ class ApplicationController < ActionController::Base # Prevent CSRF attacks by raising an exception. # For APIs, you may want to use :null_session instead. protect_from_forgery with: :exception + + wrap_parameters format: [ :json ] end diff --git a/spec/dummy/app/controllers/blogs_controller.rb b/spec/dummy/app/controllers/blogs_controller.rb new file mode 100644 index 0000000..ea51be9 --- /dev/null +++ b/spec/dummy/app/controllers/blogs_controller.rb @@ -0,0 +1,14 @@ +class BlogsController < ApplicationController + + def create + render json: {} + end + + def index + render json: {} + end + + def show + render json: {} + end +end diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index 56a55cb..f0d61fd 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -6,7 +6,7 @@ require "action_controller/railtie" require "action_mailer/railtie" require "action_view/railtie" require "sprockets/railtie" -# require "rails/test_unit/railtie" +require "rails/test_unit/railtie" Bundler.require(*Rails.groups) require "swagger_rails" diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb index 8264b95..2636b39 100644 --- a/spec/dummy/config/routes.rb +++ b/spec/dummy/config/routes.rb @@ -1,4 +1,5 @@ Rails.application.routes.draw do mount SwaggerRails::Engine => '/api-docs' + resources :blogs, only: [ :create, :index, :show ] end diff --git a/spec/dummy/config/swagger/v1/swagger.json b/spec/dummy/config/swagger/v1/swagger.json index e18a96f..d5584c7 100644 --- a/spec/dummy/config/swagger/v1/swagger.json +++ b/spec/dummy/config/swagger/v1/swagger.json @@ -2,43 +2,117 @@ "swagger": "2.0", "info": { "version": "0.0.0", - "title": "[Enter a description for your API here]", - "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" + "title": "Dummy app for testing swagger_rails" }, "paths": { - "/a/sample/resource": { + "/blogs": { "post": { - "tags": [ - "a/sample/resource" - ], - "description": "Create a new sample resource", + "description": "Creates a new Blog", "parameters": [ { - "name": "body", + "name": "X-Forwarded-For", + "in": "header", + "type": "string", + "default": "client1" + }, + { + "name": "blog", "in": "body", "schema": { - "$ref": "#/definitions/CreateSampleResource" + "$ref": "#/definitions/Blog" } } ], "responses": { "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": { - "CreateSampleResource": { + "Blog": { "properties": { - "name": { + "title": { "type": "string" }, - "date_time": { + "content": { "type": "string", "format": "date-time" } + }, + "example": { + "title": "Test Blog", + "content": "Hello World" + } + }, + "RequestError": { + "type": "object", + "additionalProperties": { + "type": "array", + "item": { + "type": "string" + } + }, + "example": { + "title": [ "is required" ] } } } diff --git a/spec/dummy/test/integration/v1_contract_test.rb b/spec/dummy/test/integration/v1_contract_test.rb new file mode 100644 index 0000000..01f3fc2 --- /dev/null +++ b/spec/dummy/test/integration/v1_contract_test.rb @@ -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 diff --git a/spec/dummy/test/test_helper.rb b/spec/dummy/test/test_helper.rb new file mode 100644 index 0000000..8298517 --- /dev/null +++ b/spec/dummy/test/test_helper.rb @@ -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 diff --git a/spec/testing/example_builder_spec.rb b/spec/testing/example_builder_spec.rb new file mode 100644 index 0000000..be8b73b --- /dev/null +++ b/spec/testing/example_builder_spec.rb @@ -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 diff --git a/spec/testing/test_visitor_spec.rb b/spec/testing/test_visitor_spec.rb new file mode 100644 index 0000000..18ce6ef --- /dev/null +++ b/spec/testing/test_visitor_spec.rb @@ -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 From 071239727a04c2e6c128a7d78abb5ad905cbae08 Mon Sep 17 00:00:00 2001 From: domaindrivendev Date: Thu, 18 Feb 2016 11:10:09 -0800 Subject: [PATCH 2/4] rename & refactor example_builder --- lib/swagger_rails/testing/example_builder.rb | 85 ------------- .../testing/test_data_builder.rb | 92 ++++++++++++++ lib/swagger_rails/testing/test_visitor.rb | 15 +-- spec/testing/example_builder_spec.rb | 119 ------------------ spec/testing/test_data_builder_spec.rb | 99 +++++++++++++++ 5 files changed, 199 insertions(+), 211 deletions(-) delete mode 100644 lib/swagger_rails/testing/example_builder.rb create mode 100644 lib/swagger_rails/testing/test_data_builder.rb delete mode 100644 spec/testing/example_builder_spec.rb create mode 100644 spec/testing/test_data_builder_spec.rb diff --git a/lib/swagger_rails/testing/example_builder.rb b/lib/swagger_rails/testing/example_builder.rb deleted file mode 100644 index 8cfcbae..0000000 --- a/lib/swagger_rails/testing/example_builder.rb +++ /dev/null @@ -1,85 +0,0 @@ -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 diff --git a/lib/swagger_rails/testing/test_data_builder.rb b/lib/swagger_rails/testing/test_data_builder.rb new file mode 100644 index 0000000..0433475 --- /dev/null +++ b/lib/swagger_rails/testing/test_data_builder.rb @@ -0,0 +1,92 @@ +module SwaggerRails + + class TestDataBuilder + + def initialize(path_template, http_method, swagger) + @path_template = path_template + @http_method = http_method + @swagger = swagger + @param_values = {} + @expected_status = nil + end + + def set(param_values) + @param_values.merge!(param_values.stringify_keys) + end + + def expect(status) + @expected_status = status + end + + def test_data + operation = find_operation! + parameters = operation['parameters'] || [] + responses = operation['responses'] + { + path: build_path(parameters), + params: build_params(parameters), + headers: build_headers(parameters), + expected_response: build_expected_response(responses) + } + end + + private + + def find_operation! + keys = [ 'paths', @path_template, @http_method ] + operation = find_hash_item!(@swagger, keys) + operation || (raise MetadataError.new(keys)) + end + + def find_hash_item!(hash, keys) + item = hash[keys[0]] || (return nil) + keys.length == 1 ? item : find_hash_item!(item, keys.drop(1)) + end + + def build_path(parameters) + param_values = param_values_for(parameters, 'path') + @path_template.dup.tap do |template| + template.prepend(@swagger['basePath'].presence || '') + param_values.each { |name, value| template.sub!("\{#{name}\}", value) } + end + end + + def build_params(parameters) + {}.tap do |params| + params.merge!(param_values_for(parameters, 'query')) + body_param_values = param_values_for(parameters, 'body') + params.merge!(body_param_values.values.first) if body_param_values.any? + end + end + + def build_headers(parameters) + param_values_for(parameters, 'header') + end + + def build_expected_response(responses) + end + + def param_values_for(parameters, location) + applicable_parameters = parameters.select { |p| p['in'] == location } + Hash[applicable_parameters.map { |p| [ p['name'], value_for(p) ] }] + 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 diff --git a/lib/swagger_rails/testing/test_visitor.rb b/lib/swagger_rails/testing/test_visitor.rb index 9e74ad8..f024b5a 100644 --- a/lib/swagger_rails/testing/test_visitor.rb +++ b/lib/swagger_rails/testing/test_visitor.rb @@ -1,4 +1,4 @@ -require 'swagger_rails/testing/example_builder' +require 'swagger_rails/testing/test_data_builder' module SwaggerRails @@ -9,16 +9,17 @@ module SwaggerRails 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? + builder = TestDataBuilder.new(path_template, http_method, @swagger) + builder.instance_exec(&block) if block_given? + test_data = builder.test_data test.send(http_method, - example.path, - example.params, - example.headers + test_data[:path], + test_data[:params], + test_data[:headers] ) - test.assert_response(example.expected_status) + test.assert_response(test_data[:expected_status]) end end end diff --git a/spec/testing/example_builder_spec.rb b/spec/testing/example_builder_spec.rb deleted file mode 100644 index be8b73b..0000000 --- a/spec/testing/example_builder_spec.rb +++ /dev/null @@ -1,119 +0,0 @@ -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 diff --git a/spec/testing/test_data_builder_spec.rb b/spec/testing/test_data_builder_spec.rb new file mode 100644 index 0000000..f403107 --- /dev/null +++ b/spec/testing/test_data_builder_spec.rb @@ -0,0 +1,99 @@ +require 'rails_helper' +require 'swagger_rails/testing/test_data_builder' + +module SwaggerRails + + describe TestDataBuilder 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 '#test_data' do + let(:test_data) { subject.test_data } + + context 'swagger includes basePath' do + before { swagger['basePath'] = '/foobar' } + let(:path) { '/blogs' } + let(:method) { 'post' } + + it 'includes a path prefixed with basePath' do + expect(test_data[:path]).to eq('/foobar/blogs') + end + end + + context 'operation has path params' do + let(:path) { '/blogs/{id}' } + let(:method) { 'get' } + + context 'by default' do + it "includes a path built from 'default' values" do + expect(test_data[:path]).to eq('/blogs/123') + end + end + + context 'values explicitly set' do + before { subject.set id: '456' } + it 'includes a path built from set values' do + expect(test_data[:path]).to eq('/blogs/456') + end + end + end + + context 'operation has query params' do + let(:path) { '/blogs' } + let(:method) { 'get' } + + context 'by default' do + it "includes params built from 'default' values" do + expect(test_data[:params]).to eq({ 'published' => 'true', 'keywords' => 'Ruby on Rails' }) + end + end + + context 'values explicitly set' do + before { subject.set keywords: 'Java' } + it 'includes params build from set values' do + expect(test_data[:params]).to eq({ 'published' => 'true', 'keywords' => 'Java' }) + end + end + end + + context 'operation has body params' do + let(:path) { '/blogs' } + let(:method) { 'post' } + + context 'by default' do + it "includes params built from 'default' values" do + expect(test_data[:params]).to eq({ 'title' => 'Test Blog', 'content' => 'Hello World' }) + end + end + + context 'values explicitly set' do + before { subject.set blog: { 'title' => 'foobar' } } + it 'includes params build from set values' do + expect(test_data[:params]).to eq({ 'title' => 'foobar' }) + end + end + end + + context 'operation has header params' do + let(:path) { '/blogs' } + let(:method) { 'post' } + + context 'by default' do + it "includes headers built from 'default' values" do + expect(test_data[: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 'includes headers built from set values' do + expect(test_data[:headers]).to eq({ 'X-Forwarded-For' => '192.168.1.1' }) + end + end + end + end + end +end From 9ad7a49e6dd4b963f466d84e12c91d50e4dbafb9 Mon Sep 17 00:00:00 2001 From: domaindrivendev Date: Tue, 23 Feb 2016 00:30:10 -0800 Subject: [PATCH 3/4] assert response body based on spec examples --- Gemfile | 1 + Gemfile.lock | 2 + ...t_data_builder.rb => test_case_builder.rb} | 53 ++++++++++++----- lib/swagger_rails/testing/test_visitor.rb | 7 ++- .../dummy/app/controllers/blogs_controller.rb | 14 ++++- spec/dummy/app/models/blog.rb | 10 ++++ .../app/views/layouts/application.html.erb | 14 ----- spec/dummy/config/application.rb | 2 +- spec/dummy/config/database.yml | 25 ++++++++ spec/dummy/config/routes.rb | 4 +- spec/dummy/config/swagger/v1/swagger.json | 9 ++- .../db/migrate/20160218212104_create_blogs.rb | 10 ++++ spec/dummy/db/schema.rb | 23 ++++++++ .../test/integration/v1_contract_test.rb | 11 +++- ...lder_spec.rb => test_case_builder_spec.rb} | 58 ++++++++++++++++--- spec/testing/test_visitor_spec.rb | 34 ++++++++--- 16 files changed, 218 insertions(+), 59 deletions(-) rename lib/swagger_rails/testing/{test_data_builder.rb => test_case_builder.rb} (56%) create mode 100644 spec/dummy/app/models/blog.rb delete mode 100644 spec/dummy/app/views/layouts/application.html.erb create mode 100644 spec/dummy/config/database.yml create mode 100644 spec/dummy/db/migrate/20160218212104_create_blogs.rb create mode 100644 spec/dummy/db/schema.rb rename spec/testing/{test_data_builder_spec.rb => test_case_builder_spec.rb} (57%) diff --git a/Gemfile b/Gemfile index d4c5bc7..670cece 100644 --- a/Gemfile +++ b/Gemfile @@ -13,6 +13,7 @@ gemspec # To use a debugger # gem 'debugger', group: [:development, :test] +gem 'sqlite3' gem 'pry' gem 'rspec-rails' gem 'generator_spec' diff --git a/Gemfile.lock b/Gemfile.lock index 9863a5b..a957712 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -105,6 +105,7 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) + sqlite3 (1.3.11) thor (0.19.1) tilt (1.4.1) treetop (1.4.15) @@ -119,4 +120,5 @@ DEPENDENCIES generator_spec pry rspec-rails + sqlite3 swagger_rails! diff --git a/lib/swagger_rails/testing/test_data_builder.rb b/lib/swagger_rails/testing/test_case_builder.rb similarity index 56% rename from lib/swagger_rails/testing/test_data_builder.rb rename to lib/swagger_rails/testing/test_case_builder.rb index 0433475..4336a6a 100644 --- a/lib/swagger_rails/testing/test_data_builder.rb +++ b/lib/swagger_rails/testing/test_case_builder.rb @@ -1,13 +1,12 @@ module SwaggerRails - - class TestDataBuilder + + class TestCaseBuilder def initialize(path_template, http_method, swagger) @path_template = path_template @http_method = http_method @swagger = swagger @param_values = {} - @expected_status = nil end def set(param_values) @@ -15,7 +14,7 @@ module SwaggerRails end def expect(status) - @expected_status = status + @expected_status = status.to_s end def test_data @@ -33,7 +32,7 @@ module SwaggerRails private def find_operation! - keys = [ 'paths', @path_template, @http_method ] + keys = [ 'paths', @path_template, @http_method ] operation = find_hash_item!(@swagger, keys) operation || (raise MetadataError.new(keys)) end @@ -52,35 +51,59 @@ module SwaggerRails end def build_params(parameters) - {}.tap do |params| - params.merge!(param_values_for(parameters, 'query')) - body_param_values = param_values_for(parameters, 'body') - params.merge!(body_param_values.values.first) if body_param_values.any? - end + body_param_values = param_values_for(parameters, 'body') + return body_param_values.values.first.to_json if body_param_values.any? + param_values_for(parameters, 'query') end def build_headers(parameters) param_values_for(parameters, 'header') + .merge({ + 'CONTENT_TYPE' => 'application/json', + 'ACCEPT' => 'application/json' + }) end def build_expected_response(responses) + status = @expected_status || responses.keys.find { |k| k.start_with?('2') } + response = responses[status] || (raise MetadataError.new('paths', @path_template, @http_method, 'responses', status)) + { + status: status.to_i, + body: response_body_for(response) + } end def param_values_for(parameters, location) applicable_parameters = parameters.select { |p| p['in'] == location } - Hash[applicable_parameters.map { |p| [ p['name'], value_for(p) ] }] + Hash[applicable_parameters.map { |p| [ p['name'], param_value_for(p) ] }] 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'] + def param_value_for(parameter) + return @param_values[parameter['name']] if @param_values.has_key?(parameter['name']) + return parameter['default'] unless parameter['in'] == 'body' + schema = schema_for(parameter['schema']) + schema_example_for(schema) + end + + def response_body_for(response) + return nil if response['schema'].nil? + schema = schema_for(response['schema']) + schema_example_for(schema) 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 + + def schema_example_for(schema) + return schema['example'] if schema['example'].present? + # If an array, try construct from the item example + if schema['type'] == 'array' && schema['item'].present? + item_schema = schema_for(schema['item']) + return [ schema_example_for(item_schema) ] + end + end end class MetadataError < StandardError diff --git a/lib/swagger_rails/testing/test_visitor.rb b/lib/swagger_rails/testing/test_visitor.rb index f024b5a..9603e1c 100644 --- a/lib/swagger_rails/testing/test_visitor.rb +++ b/lib/swagger_rails/testing/test_visitor.rb @@ -1,4 +1,4 @@ -require 'swagger_rails/testing/test_data_builder' +require 'swagger_rails/testing/test_case_builder' module SwaggerRails @@ -9,7 +9,7 @@ module SwaggerRails end def run_test(path_template, http_method, test, &block) - builder = TestDataBuilder.new(path_template, http_method, @swagger) + builder = TestCaseBuilder.new(path_template, http_method, @swagger) builder.instance_exec(&block) if block_given? test_data = builder.test_data @@ -19,7 +19,8 @@ module SwaggerRails test_data[:headers] ) - test.assert_response(test_data[:expected_status]) + test.assert_response(test_data[:expected_response][:status]) + test.assert_equal(test_data[:expected_response][:body], JSON.parse(test.response.body)) end end end diff --git a/spec/dummy/app/controllers/blogs_controller.rb b/spec/dummy/app/controllers/blogs_controller.rb index ea51be9..a36145a 100644 --- a/spec/dummy/app/controllers/blogs_controller.rb +++ b/spec/dummy/app/controllers/blogs_controller.rb @@ -1,14 +1,22 @@ class BlogsController < ApplicationController + wrap_parameters Blog + respond_to :json + # POST /blogs def create - render json: {} + @blog = Blog.create(params[:blog]) + respond_with @blog end + # GET /blogs def index - render json: {} + @blogs = Blog.all + respond_with @blogs end + # GET /blogs/1 def show - render json: {} + @blog = Blog.find(params[:id]) + respond_with @blog end end diff --git a/spec/dummy/app/models/blog.rb b/spec/dummy/app/models/blog.rb new file mode 100644 index 0000000..2f4b8f8 --- /dev/null +++ b/spec/dummy/app/models/blog.rb @@ -0,0 +1,10 @@ +class Blog < ActiveRecord::Base + attr_accessible :content, :title + + def as_json(options) + { + title: title, + content: content + } + end +end diff --git a/spec/dummy/app/views/layouts/application.html.erb b/spec/dummy/app/views/layouts/application.html.erb deleted file mode 100644 index 593a778..0000000 --- a/spec/dummy/app/views/layouts/application.html.erb +++ /dev/null @@ -1,14 +0,0 @@ - - - - Dummy - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> - <%= csrf_meta_tags %> - - - -<%= yield %> - - - diff --git a/spec/dummy/config/application.rb b/spec/dummy/config/application.rb index f0d61fd..11d35dc 100644 --- a/spec/dummy/config/application.rb +++ b/spec/dummy/config/application.rb @@ -1,7 +1,7 @@ require File.expand_path('../boot', __FILE__) # Pick the frameworks you want: -# require "active_record/railtie" +require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" require "action_view/railtie" diff --git a/spec/dummy/config/database.yml b/spec/dummy/config/database.yml new file mode 100644 index 0000000..dc954e3 --- /dev/null +++ b/spec/dummy/config/database.yml @@ -0,0 +1,25 @@ +#SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +development: + adapter: sqlite3 + database: db/development.sqlite3 + pool: 5 + timeout: 5000 + +# Warning: The database defined as "test" will be erased and +# re-generated from your development database when you run "rake". +# Do not set this db to the same as development or production. +test: + adapter: sqlite3 + database: db/test.sqlite3 + pool: 5 + timeout: 5000 + +production: + adapter: sqlite3 + database: db/production.sqlite3 + pool: 5 + timeout: 5000 diff --git a/spec/dummy/config/routes.rb b/spec/dummy/config/routes.rb index 2636b39..99850bd 100644 --- a/spec/dummy/config/routes.rb +++ b/spec/dummy/config/routes.rb @@ -1,5 +1,5 @@ Rails.application.routes.draw do - mount SwaggerRails::Engine => '/api-docs' + resources :blogs, defaults: { :format => :json } - resources :blogs, only: [ :create, :index, :show ] + mount SwaggerRails::Engine => '/api-docs' end diff --git a/spec/dummy/config/swagger/v1/swagger.json b/spec/dummy/config/swagger/v1/swagger.json index d5584c7..8b859d9 100644 --- a/spec/dummy/config/swagger/v1/swagger.json +++ b/spec/dummy/config/swagger/v1/swagger.json @@ -24,8 +24,8 @@ } ], "responses": { - "200": { - "description": "Ok", + "201": { + "description": "Created", "schema": { "$ref": "#/definitions/Blog" } @@ -58,7 +58,10 @@ "200": { "description": "Ok", "schema": { - "$ref": "#/definitions/Blog" + "type": "array", + "item": { + "$ref": "#/definitions/Blog" + } } } } diff --git a/spec/dummy/db/migrate/20160218212104_create_blogs.rb b/spec/dummy/db/migrate/20160218212104_create_blogs.rb new file mode 100644 index 0000000..81d9813 --- /dev/null +++ b/spec/dummy/db/migrate/20160218212104_create_blogs.rb @@ -0,0 +1,10 @@ +class CreateBlogs < ActiveRecord::Migration + def change + create_table :blogs do |t| + t.string :title + t.text :content + + t.timestamps + end + end +end diff --git a/spec/dummy/db/schema.rb b/spec/dummy/db/schema.rb new file mode 100644 index 0000000..082bb8d --- /dev/null +++ b/spec/dummy/db/schema.rb @@ -0,0 +1,23 @@ +# encoding: UTF-8 +# 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 +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the 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). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20160218212104) do + + create_table "blogs", :force => true do |t| + t.string "title" + t.text "content" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + +end diff --git a/spec/dummy/test/integration/v1_contract_test.rb b/spec/dummy/test/integration/v1_contract_test.rb index 01f3fc2..586d94b 100644 --- a/spec/dummy/test/integration/v1_contract_test.rb +++ b/spec/dummy/test/integration/v1_contract_test.rb @@ -5,5 +5,14 @@ class V1ContractTest < ActionDispatch::IntegrationTest include SwaggerRails::TestHelpers swagger_doc 'v1/swagger.json' - swagger_test_all +# +# test '/blogs post' do +# swagger_test '/blogs', 'post' +# end + + test '/blogs get' do + blog = Blog.create(title: 'Test Blog', content: 'Hello World') + + swagger_test '/blogs', 'get' + end end diff --git a/spec/testing/test_data_builder_spec.rb b/spec/testing/test_case_builder_spec.rb similarity index 57% rename from spec/testing/test_data_builder_spec.rb rename to spec/testing/test_case_builder_spec.rb index f403107..d5f7404 100644 --- a/spec/testing/test_data_builder_spec.rb +++ b/spec/testing/test_case_builder_spec.rb @@ -1,9 +1,9 @@ require 'rails_helper' -require 'swagger_rails/testing/test_data_builder' +require 'swagger_rails/testing/test_case_builder' module SwaggerRails - describe TestDataBuilder do + describe TestCaseBuilder do subject { described_class.new(path, method, swagger) } let(:swagger) do file_path = File.join(Rails.root, 'config/swagger', 'v1/swagger.json') @@ -59,20 +59,20 @@ module SwaggerRails end end - context 'operation has body params' do + context 'operation has body param' do let(:path) { '/blogs' } let(:method) { 'post' } context 'by default' do - it "includes params built from 'default' values" do - expect(test_data[:params]).to eq({ 'title' => 'Test Blog', 'content' => 'Hello World' }) + it "includes params string based on schema 'example'" do + expect(test_data[:params]).to eq({ 'title' => 'Test Blog', 'content' => 'Hello World' }.to_json) end end context 'values explicitly set' do before { subject.set blog: { 'title' => 'foobar' } } - it 'includes params build from set values' do - expect(test_data[:params]).to eq({ 'title' => 'foobar' }) + it 'includes params string based on set value' do + expect(test_data[:params]).to eq({ 'title' => 'foobar' }.to_json) end end end @@ -83,14 +83,54 @@ module SwaggerRails context 'by default' do it "includes headers built from 'default' values" do - expect(test_data[:headers]).to eq({ 'X-Forwarded-For' => 'client1' }) + expect(test_data[:headers]).to eq({ + 'X-Forwarded-For' => 'client1', + 'CONTENT_TYPE' => 'application/json', + 'ACCEPT' => 'application/json' + }) end end context 'values explicitly params' do before { subject.set 'X-Forwarded-For' => '192.168.1.1' } it 'includes headers built from set values' do - expect(test_data[:headers]).to eq({ 'X-Forwarded-For' => '192.168.1.1' }) + expect(test_data[:headers]).to eq({ + 'X-Forwarded-For' => '192.168.1.1', + 'CONTENT_TYPE' => 'application/json', + 'ACCEPT' => 'application/json' + }) + end + end + end + + context 'operation returns an object' do + let(:path) { '/blogs' } + let(:method) { 'post' } + + context 'by default' do + it "includes expected_response based on spec'd 2xx status" do + expect(test_data[:expected_response][:status]).to eq(201) + expect(test_data[:expected_response][:body]).to eq({ 'title' => 'Test Blog', 'content' => 'Hello World' }) + end + end + + context 'expected status explicitly set' do + before { subject.expect 400 } + it "includes expected_response based on set status" do + expect(test_data[:expected_response][:status]).to eq(400) + expect(test_data[:expected_response][:body]).to eq({ 'title' => [ 'is required' ] }) + end + end + end + + context 'operation returns an array' do + let(:path) { '/blogs' } + let(:method) { 'get' } + + context 'by default' do + it "includes expected_response based on spec'd 2xx status" do + expect(test_data[:expected_response][:status]).to eq(200) + expect(test_data[:expected_response][:body]).to eq([ { 'title' => 'Test Blog', 'content' => 'Hello World' } ]) end end end diff --git a/spec/testing/test_visitor_spec.rb b/spec/testing/test_visitor_spec.rb index 18ce6ef..7409fe9 100644 --- a/spec/testing/test_visitor_spec.rb +++ b/spec/testing/test_visitor_spec.rb @@ -12,19 +12,30 @@ module SwaggerRails let(:test) { spy('test') } describe '#run_test' do + before do + allow(test).to receive(:response).and_return(OpenStruct.new(body: "{}")) + end + 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' } + { 'title' => 'Test Blog', 'content' => 'Hello World' }.to_json, + { 'X-Forwarded-For' => 'client1', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } ) end - it "asserts response matches first 2xx status in operation 'responses'" do - expect(test).to have_received(:assert_response).with(200) + it "asserts response matches spec'd 2xx status" do + expect(test).to have_received(:assert_response).with(201) + end + + it "asserts response body matches schema 'example' for 2xx status" do + expect(test).to have_received(:assert_equal).with( + { 'title' => 'Test Blog', 'content' => 'Hello World' }, + {} + ) end end @@ -39,22 +50,29 @@ module SwaggerRails 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' } + { 'title' => 'foobar' }.to_json, + { 'X-Forwarded-For' => '192.168.1.1', 'CONTENT_TYPE' => 'application/json', 'ACCEPT' => 'application/json' } ) end end - context 'expected status explicitly params' do + context 'expected status explicitly set' do before do subject.run_test('/blogs', 'post', test) do expect 400 end end - it "asserts response matches params status" do + it "asserts response matches set status" do expect(test).to have_received(:assert_response).with(400) end + + it "asserts response body matches schema 'example' for set status" do + expect(test).to have_received(:assert_equal).with( + { 'title' => [ 'is required' ] }, + {} + ) + end end end end From 9297120adbeecee3fe185091c90c9660c9035261 Mon Sep 17 00:00:00 2001 From: domaindrivendev Date: Thu, 25 Feb 2016 09:46:21 -0800 Subject: [PATCH 4/4] Demo swagger-driven tests with minitest and rspec --- .../testing/test_case_builder.rb | 2 +- lib/swagger_rails/testing/test_helpers.rb | 4 +- spec/dummy/.rspec | 2 + .../spec/integration/v1_contract_spec.rb | 19 ++++ spec/dummy/spec/rails_helper.rb | 53 +++++++++++ spec/dummy/spec/spec_helper.rb | 92 +++++++++++++++++++ .../test/integration/v1_contract_test.rb | 15 +-- 7 files changed, 177 insertions(+), 10 deletions(-) create mode 100644 spec/dummy/.rspec create mode 100644 spec/dummy/spec/integration/v1_contract_spec.rb create mode 100644 spec/dummy/spec/rails_helper.rb create mode 100644 spec/dummy/spec/spec_helper.rb diff --git a/lib/swagger_rails/testing/test_case_builder.rb b/lib/swagger_rails/testing/test_case_builder.rb index 4336a6a..775fd5a 100644 --- a/lib/swagger_rails/testing/test_case_builder.rb +++ b/lib/swagger_rails/testing/test_case_builder.rb @@ -46,7 +46,7 @@ module SwaggerRails param_values = param_values_for(parameters, 'path') @path_template.dup.tap do |template| template.prepend(@swagger['basePath'].presence || '') - param_values.each { |name, value| template.sub!("\{#{name}\}", value) } + param_values.each { |name, value| template.sub!("\{#{name}\}", value.to_s) } end end diff --git a/lib/swagger_rails/testing/test_helpers.rb b/lib/swagger_rails/testing/test_helpers.rb index 517002d..086c852 100644 --- a/lib/swagger_rails/testing/test_helpers.rb +++ b/lib/swagger_rails/testing/test_helpers.rb @@ -27,8 +27,8 @@ module SwaggerRails end end - def swagger_test(path, method) - self.class.test_visitor.run_test(path, method, self) + def swagger_test(path, method, &block) + self.class.test_visitor.run_test(path, method, self, &block) end end end diff --git a/spec/dummy/.rspec b/spec/dummy/.rspec new file mode 100644 index 0000000..83e16f8 --- /dev/null +++ b/spec/dummy/.rspec @@ -0,0 +1,2 @@ +--color +--require spec_helper diff --git a/spec/dummy/spec/integration/v1_contract_spec.rb b/spec/dummy/spec/integration/v1_contract_spec.rb new file mode 100644 index 0000000..2d05f30 --- /dev/null +++ b/spec/dummy/spec/integration/v1_contract_spec.rb @@ -0,0 +1,19 @@ +require 'rails_helper' +require 'swagger_rails/testing/test_helpers' + +describe 'V1 Contract' do + include SwaggerRails::TestHelpers + swagger_doc 'v1/swagger.json' + + # TODO: improve DSL + + it 'exposes an API for managing blogs' do + swagger_test '/blogs', 'post' + + swagger_test '/blogs', 'get' + + swagger_test '/blogs/{id}', 'get' do + set id: Blog.last!.id + end + end +end diff --git a/spec/dummy/spec/rails_helper.rb b/spec/dummy/spec/rails_helper.rb new file mode 100644 index 0000000..97096a2 --- /dev/null +++ b/spec/dummy/spec/rails_helper.rb @@ -0,0 +1,53 @@ +# This file is copied to spec/ when you run 'rails generate rspec:install' +ENV['RAILS_ENV'] ||= 'test' +require File.expand_path('../../config/environment', __FILE__) +# Prevent database truncation if the environment is production +abort("The Rails environment is running in production mode!") if Rails.env.production? +require 'spec_helper' +require 'rspec/rails' +# Add additional requires below this line. Rails is not loaded until this point! + +# Requires supporting ruby files with custom matchers and macros, etc, in +# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are +# run as spec files by default. This means that files in spec/support that end +# in _spec.rb will both be required and run as specs, causing the specs to be +# run twice. It is recommended that you do not name files matching this glob to +# end with _spec.rb. You can configure this pattern with the --pattern +# option on the command line or in ~/.rspec, .rspec or `.rspec-local`. +# +# The following line is provided for convenience purposes. It has the downside +# of increasing the boot-up time by auto-requiring all files in the support +# directory. Alternatively, in the individual `*_spec.rb` files, manually +# require only the support files necessary. +# +# Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } + +RSpec.configure do |config| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + config.fixture_path = "#{::Rails.root}/spec/fixtures" + + # If you're not using ActiveRecord, or you'd prefer not to run each of your + # examples within a transaction, remove the following line or assign false + # instead of true. + config.use_transactional_fixtures = true + + # RSpec Rails can automatically mix in different behaviours to your tests + # based on their file location, for example enabling you to call `get` and + # `post` in specs under `spec/controllers`. + # + # You can disable this behaviour by removing the line below, and instead + # explicitly tag your specs with their type, e.g.: + # + # RSpec.describe UsersController, :type => :controller do + # # ... + # end + # + # The different available types are documented in the features, such as in + # https://relishapp.com/rspec/rspec-rails/docs + config.infer_spec_type_from_file_location! + + # Filter lines from Rails gems in backtraces. + config.filter_rails_from_backtrace! + # arbitrary gems may also be filtered via: + # config.filter_gems_from_backtrace("gem name") +end diff --git a/spec/dummy/spec/spec_helper.rb b/spec/dummy/spec/spec_helper.rb new file mode 100644 index 0000000..61e2738 --- /dev/null +++ b/spec/dummy/spec/spec_helper.rb @@ -0,0 +1,92 @@ +# This file was generated by the `rails generate rspec:install` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# The `.rspec` file also contains a few flags that are not defaults but that +# users commonly want. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # These two settings work together to allow you to limit a spec run + # to individual examples or groups you care about by tagging them with + # `:focus` metadata. When nothing is tagged with `:focus`, all examples + # get run. + config.filter_run :focus + config.run_all_when_everything_filtered = true + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = 'doc' + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/spec/dummy/test/integration/v1_contract_test.rb b/spec/dummy/test/integration/v1_contract_test.rb index 586d94b..23602cc 100644 --- a/spec/dummy/test/integration/v1_contract_test.rb +++ b/spec/dummy/test/integration/v1_contract_test.rb @@ -3,16 +3,17 @@ require 'swagger_rails/testing/test_helpers' class V1ContractTest < ActionDispatch::IntegrationTest include SwaggerRails::TestHelpers - swagger_doc 'v1/swagger.json' -# -# test '/blogs post' do -# swagger_test '/blogs', 'post' -# end - test '/blogs get' do - blog = Blog.create(title: 'Test Blog', content: 'Hello World') + # TODO: improve DSL + + test 'Blogs API' do + swagger_test '/blogs', 'post' swagger_test '/blogs', 'get' + + swagger_test '/blogs/{id}', 'get' do + set id: Blog.last!.id + end end end