From b81b2927be2cbe014c9aa23cb6581324654f42e7 Mon Sep 17 00:00:00 2001 From: richie Date: Thu, 13 Oct 2016 16:56:44 -0700 Subject: [PATCH] More useful error messages in rswag-specs --- rswag-specs/lib/rswag/specs.rb | 5 + rswag-specs/lib/rswag/specs/configuration.rb | 37 ++++++ .../lib/rswag/specs/example_helpers.rb | 9 +- .../lib/rswag/specs/swagger_formatter.rb | 22 +--- .../spec/rswag/specs/configuration_spec.rb | 77 ++++++++++++ .../rswag/specs/example_group_helpers_spec.rb | 11 +- .../spec/rswag/specs/example_helpers_spec.rb | 30 ++--- .../spec/rswag/specs/request_factory_spec.rb | 13 +- .../rswag/specs/response_validator_spec.rb | 4 +- .../rswag/specs/swagger_formatter_spec.rb | 111 +++++------------- 10 files changed, 187 insertions(+), 132 deletions(-) create mode 100644 rswag-specs/lib/rswag/specs/configuration.rb create mode 100644 rswag-specs/spec/rswag/specs/configuration_spec.rb diff --git a/rswag-specs/lib/rswag/specs.rb b/rswag-specs/lib/rswag/specs.rb index 5d04e97..e4f2e1b 100644 --- a/rswag-specs/lib/rswag/specs.rb +++ b/rswag-specs/lib/rswag/specs.rb @@ -2,6 +2,7 @@ require 'rspec/core' require 'rswag/specs/version' require 'rswag/specs/example_group_helpers' require 'rswag/specs/example_helpers' +require 'rswag/specs/configuration' require 'rswag/specs/railtie' if defined?(Rails::Railtie) module Rswag @@ -15,6 +16,10 @@ module Rswag c.include ExampleHelpers, type: :request end + def self.config + @config ||= Configuration.new(RSpec.configuration) + end + # Support Rails 3+ and RSpec 2+ (sigh!) RAILS_VERSION = Rails::VERSION::MAJOR RSPEC_VERSION = RSpec::Core::Version::STRING.split('.').first.to_i diff --git a/rswag-specs/lib/rswag/specs/configuration.rb b/rswag-specs/lib/rswag/specs/configuration.rb new file mode 100644 index 0000000..121caf0 --- /dev/null +++ b/rswag-specs/lib/rswag/specs/configuration.rb @@ -0,0 +1,37 @@ +module Rswag + module Specs + + class Configuration + + def initialize(rspec_config) + @rspec_config = rspec_config + end + + def swagger_root + @swagger_root ||= begin + if @rspec_config.swagger_root.nil? + raise ConfigurationError, 'No swagger_root provided. See swagger_helper.rb' + end + @rspec_config.swagger_root + end + end + + def swagger_docs + @swagger_docs ||= begin + if @rspec_config.swagger_docs.nil? || @rspec_config.swagger_docs.empty? + raise ConfigurationError, 'No swagger_docs defined. See swagger_helper.rb' + end + @rspec_config.swagger_docs + end + end + + def get_swagger_doc(name) + return swagger_docs.values.first if name.nil? + raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name] + swagger_docs[name] + end + end + + class ConfigurationError < StandardError; end + end +end diff --git a/rswag-specs/lib/rswag/specs/example_helpers.rb b/rswag-specs/lib/rswag/specs/example_helpers.rb index dbfd302..cb8f11b 100644 --- a/rswag-specs/lib/rswag/specs/example_helpers.rb +++ b/rswag-specs/lib/rswag/specs/example_helpers.rb @@ -6,7 +6,7 @@ module Rswag module ExampleHelpers def submit_request(api_metadata) - factory = RequestFactory.new(api_metadata, global_metadata(api_metadata[:swagger_doc])) + factory = RequestFactory.new(api_metadata, config.get_swagger_doc(api_metadata[:swagger_doc])) if RAILS_VERSION < 5 send( @@ -28,15 +28,14 @@ module Rswag end def assert_response_matches_metadata(api_metadata) - validator = ResponseValidator.new(api_metadata, global_metadata(api_metadata[:swagger_doc])) + validator = ResponseValidator.new(api_metadata, config.get_swagger_doc(api_metadata[:swagger_doc])) 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] + def config + ::Rswag::Specs.config end end end diff --git a/rswag-specs/lib/rswag/specs/swagger_formatter.rb b/rswag-specs/lib/rswag/specs/swagger_formatter.rb index 2df6262..11359c1 100644 --- a/rswag-specs/lib/rswag/specs/swagger_formatter.rb +++ b/rswag-specs/lib/rswag/specs/swagger_formatter.rb @@ -1,5 +1,4 @@ require 'active_support/core_ext/hash/deep_merge' -require 'rspec/core/formatters/base_text_formatter' require 'swagger_helper' module Rswag @@ -11,12 +10,9 @@ module Rswag ::RSpec::Core::Formatters.register self, :example_group_finished, :stop end - def initialize(output) + def initialize(output, config = Rswag::Specs.config) @output = output - @swagger_root = ::RSpec.configuration.swagger_root - 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? + @config = config @output.puts 'Generating Swagger docs ...' end @@ -30,13 +26,13 @@ module Rswag end 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)) end def stop(notification=nil) - @swagger_docs.each do |url_path, doc| - file_path = File.join(@swagger_root, url_path) + @config.swagger_docs.each do |url_path, doc| + file_path = File.join(@config.swagger_root, url_path) dirname = File.dirname(file_path) FileUtils.mkdir_p dirname unless File.exists?(dirname) @@ -50,12 +46,6 @@ module Rswag 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) response_code = metadata[:response][:code] response = metadata[:response].reject { |k,v| k == :code } @@ -73,7 +63,5 @@ module Rswag } end end - - class ConfigurationError < StandardError; end end end diff --git a/rswag-specs/spec/rswag/specs/configuration_spec.rb b/rswag-specs/spec/rswag/specs/configuration_spec.rb new file mode 100644 index 0000000..b75d843 --- /dev/null +++ b/rswag-specs/spec/rswag/specs/configuration_spec.rb @@ -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 diff --git a/rswag-specs/spec/rswag/specs/example_group_helpers_spec.rb b/rswag-specs/spec/rswag/specs/example_group_helpers_spec.rb index e8eecdf..15ed51c 100644 --- a/rswag-specs/spec/rswag/specs/example_group_helpers_spec.rb +++ b/rswag-specs/spec/rswag/specs/example_group_helpers_spec.rb @@ -5,13 +5,14 @@ module Rswag describe ExampleGroupHelpers do subject { double('example_group') } - let(:api_metadata) { {} } + before do subject.extend ExampleGroupHelpers allow(subject).to receive(:describe) allow(subject).to receive(:context) allow(subject).to receive(:metadata).and_return(api_metadata) end + let(:api_metadata) { {} } describe '#path(path)' do before { subject.path('/blogs') } @@ -34,7 +35,6 @@ module Rswag end describe '#tags|description|operationId|consumes|produces|schemes|deprecated(value)' do - let(:api_metadata) { { operation: {} } } before do subject.tags('Blogs', 'Admin') subject.description('Some description') @@ -44,6 +44,7 @@ module Rswag subject.schemes('http', 'https') subject.deprecated(true) end + let(:api_metadata) { { operation: {} } } it "adds to the 'operation' metadata" do expect(api_metadata[:operation]).to match( @@ -59,7 +60,6 @@ module Rswag end describe '#tags|description|operationId|consumes|produces|schemes|deprecated|security(value)' do - let(:api_metadata) { { operation: {} } } before do subject.tags('Blogs', 'Admin') subject.description('Some description') @@ -70,6 +70,7 @@ module Rswag subject.deprecated(true) subject.security(api_key: []) end + let(:api_metadata) { { operation: {} } } it "adds to the 'operation' metadata" do expect(api_metadata[:operation]).to match( @@ -120,8 +121,8 @@ module Rswag end describe '#schema(value)' do - let(:api_metadata) { { response: {} } } before { subject.schema(type: 'object') } + let(:api_metadata) { { response: {} } } it "adds to the 'response' metadata" do expect(api_metadata[:response][:schema]).to match(type: 'object') @@ -129,8 +130,8 @@ module Rswag end describe '#header(name, attributes)' do - let(:api_metadata) { { response: {} } } before { subject.header('Date', type: 'string') } + let(:api_metadata) { { response: {} } } it "adds to the 'response headers' metadata" do expect(api_metadata[:response][:headers]).to match( diff --git a/rswag-specs/spec/rswag/specs/example_helpers_spec.rb b/rswag-specs/spec/rswag/specs/example_helpers_spec.rb index 4c02a1f..cb541df 100644 --- a/rswag-specs/spec/rswag/specs/example_helpers_spec.rb +++ b/rswag-specs/spec/rswag/specs/example_helpers_spec.rb @@ -4,6 +4,16 @@ module Rswag module Specs describe ExampleHelpers do + subject { double('example') } + + before do + subject.extend ExampleHelpers + # Mock out some infrastructure + stub_const('Rails::VERSION::MAJOR', 3) + config = double('config') + allow(config).to receive(:get_swagger_doc).and_return(global_metadata) + allow(subject).to receive(:config).and_return(config) + end let(:api_metadata) do { path: '/blogs/{blog_id}/comments/{id}', @@ -34,22 +44,14 @@ module Rswag } end - subject { double('example') } - - before do - subject.extend ExampleHelpers - allow(subject).to receive(:blog_id).and_return(1) - 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(:global_metadata).and_return(global_metadata) - allow(subject).to receive(:put) - end - describe '#submit_request(api_metadata)' do before do - stub_const('Rails::VERSION::MAJOR', 3) + allow(subject).to receive(:blog_id).and_return(1) + 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(api_metadata) end diff --git a/rswag-specs/spec/rswag/specs/request_factory_spec.rb b/rswag-specs/spec/rswag/specs/request_factory_spec.rb index 32106f3..40799f4 100644 --- a/rswag-specs/spec/rswag/specs/request_factory_spec.rb +++ b/rswag-specs/spec/rswag/specs/request_factory_spec.rb @@ -4,6 +4,12 @@ module Rswag module Specs describe RequestFactory do + subject { RequestFactory.new(api_metadata, global_metadata) } + + before do + allow(example).to receive(:blog_id).and_return(1) + allow(example).to receive(:id).and_return('2') + end let(:api_metadata) do { path: '/blogs/{blog_id}/comments/{id}', @@ -18,14 +24,7 @@ module Rswag } end let(:global_metadata) { {} } - - subject { RequestFactory.new(api_metadata, global_metadata) } - let(:example) { double('example') } - before do - allow(example).to receive(:blog_id).and_return(1) - allow(example).to receive(:id).and_return('2') - end describe '#build_fullpath(example)' do let(:path) { subject.build_fullpath(example) } diff --git a/rswag-specs/spec/rswag/specs/response_validator_spec.rb b/rswag-specs/spec/rswag/specs/response_validator_spec.rb index fcafea8..c914264 100644 --- a/rswag-specs/spec/rswag/specs/response_validator_spec.rb +++ b/rswag-specs/spec/rswag/specs/response_validator_spec.rb @@ -4,11 +4,11 @@ module Rswag module Specs describe ResponseValidator do + subject { ResponseValidator.new(api_metadata, global_metadata) } + let(:api_metadata) { { response: { code: 200 } } } let(:global_metadata) { {} } - subject { ResponseValidator.new(api_metadata, global_metadata) } - describe '#validate!(response)' do let(:call) { subject.validate!(response) } diff --git a/rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb b/rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb index 56f3964..93afc71 100644 --- a/rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb +++ b/rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb @@ -5,33 +5,23 @@ module Rswag module Specs 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(:swagger_root) { File.expand_path('../tmp', __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 + let(:swagger_root) { File.expand_path('../tmp/swagger', __FILE__) } 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 { path: '/blogs', @@ -39,78 +29,35 @@ module Rswag response: { code: '201', description: 'blog created' } } end - let(:notification) { OpenStruct.new(group: OpenStruct.new(metadata: api_metadata)) } - let(:call) { subject.example_group_finished(notification) } - - context 'single swagger_doc' do - before { call } - - it 'converts metadata to swagger and merges into the doc' do - expect(swagger_docs.values.first).to match( - info: { version: 'v1' }, - paths: { - '/blogs' => { - post: { - summary: 'Creates a blog', - responses: { - '201' => { description: 'blog created' } - } + it 'converts to swagger and merges into the corresponding swagger doc' do + expect(swagger_doc).to match( + 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 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 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) end + let(:notification) { double('notification') } + it 'writes the swagger_doc(s) to file' do expect(File).to exist("#{swagger_root}/v1/swagger.json") expect(File).to exist("#{swagger_root}/v2/swagger.json")