rswag-specs-2.1 2.9.1.ruby21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 5cce88ce3a4009e856838f9b55855ddacc6236c5079d6db1489f20a58a4a6044
4
+ data.tar.gz: 853c4742c3e67170fee783a499133124cb214d22754b077c00bb4340aed2dbc4
5
+ SHA512:
6
+ metadata.gz: cc0b712f93dc012c75e7a1a7bc0adb371f7bc173e21f073670f1a140d8d4b8b214dad149ef1b4d01d17e79d0eb64e35611feadaf1ad910bc28dfb9dc21280d1b
7
+ data.tar.gz: d9e1beb5fce87764e603ffecf72a627eeb51edd89a398db741af2c84780d698fd14c2d142496418eabd209930fc4dc182a39b84c0e51534213669ab30471fdc9
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 domaindrivendev
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'rswag-specs'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,9 @@
1
+ Description:
2
+ This creates an RSpec request spec to define Swagger documentation for a
3
+ controller. It will create a test for each of the controller's methods.
4
+
5
+ Example:
6
+ rails generate rspec:swagger V3::AccountsController
7
+
8
+ This will create:
9
+ spec/requests/v3/accounts_spec.rb
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rswag/route_parser'
4
+ require 'rails/generators'
5
+
6
+ module Rspec
7
+ class SwaggerGenerator < ::Rails::Generators::NamedBase
8
+ source_root File.expand_path('templates', __dir__)
9
+ class_option :spec_path, type: :string, default: 'requests'
10
+
11
+ def setup
12
+ @routes = Rswag::RouteParser.new(controller_path).routes
13
+ end
14
+
15
+ def create_spec_file
16
+ template 'spec.rb', File.join('spec', options['spec_path'], "#{controller_path}_spec.rb")
17
+ end
18
+
19
+ private
20
+
21
+ def controller_path
22
+ file_path.chomp('_controller')
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,34 @@
1
+ require 'swagger_helper'
2
+
3
+ RSpec.describe '<%= controller_path %>', type: :request do
4
+ <% @routes.each do | template, path_item | %>
5
+ path '<%= template %>' do
6
+ <% unless path_item[:params].empty? -%>
7
+ # You'll want to customize the parameter types...
8
+ <% path_item[:params].each do |param| -%>
9
+ parameter name: '<%= param %>', in: :path, type: :string, description: '<%= param %>'
10
+ <% end -%>
11
+ <% end -%>
12
+ <% path_item[:actions].each do | action, details | %>
13
+ <%= action %>('<%= details[:summary] %>') do
14
+ response(200, 'successful') do
15
+ <% unless path_item[:params].empty? -%>
16
+ <% path_item[:params].each do |param| -%>
17
+ let(:<%= param %>) { '123' }
18
+ <% end -%>
19
+ <% end -%>
20
+
21
+ after do |example|
22
+ example.metadata[:response][:content] = {
23
+ 'application/json' => {
24
+ example: JSON.parse(response.body, symbolize_names: true)
25
+ }
26
+ }
27
+ end
28
+ run_test!
29
+ end
30
+ end
31
+ <% end -%>
32
+ end
33
+ <% end -%>
34
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Adds swagger_helper to enable Swagger DSL in integration specs
3
+
4
+ Example:
5
+ rails generate rswag:specs:install
6
+
7
+ This will create:
8
+ spec/swagger_helper.rb
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails/generators'
4
+
5
+ module Rswag
6
+ module Specs
7
+ class InstallGenerator < Rails::Generators::Base
8
+ source_root File.expand_path('templates', __dir__)
9
+
10
+ def add_swagger_helper
11
+ template('swagger_helper.rb', 'spec/swagger_helper.rb')
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rails_helper'
4
+
5
+ RSpec.configure do |config|
6
+ # Specify a root folder where Swagger JSON files are generated
7
+ # NOTE: If you're using the rswag-api to serve API descriptions, you'll need
8
+ # to ensure that it's configured to serve Swagger from the same folder
9
+ config.swagger_root = Rails.root.join('swagger').to_s
10
+
11
+ # Define one or more Swagger documents and provide global metadata for each one
12
+ # When you run the 'rswag:specs:swaggerize' rake task, the complete Swagger will
13
+ # be generated at the provided relative path under swagger_root
14
+ # By default, the operations defined in spec files are added to the first
15
+ # document below. You can override this behavior by adding a swagger_doc tag to the
16
+ # the root example_group in your specs, e.g. describe '...', swagger_doc: 'v2/swagger.json'
17
+ config.swagger_docs = {
18
+ 'v1/swagger.yaml' => {
19
+ openapi: '3.0.1',
20
+ info: {
21
+ title: 'API V1',
22
+ version: 'v1'
23
+ },
24
+ paths: {},
25
+ servers: [
26
+ {
27
+ url: 'https://{defaultHost}',
28
+ variables: {
29
+ defaultHost: {
30
+ default: 'www.example.com'
31
+ }
32
+ }
33
+ }
34
+ ]
35
+ }
36
+ }
37
+
38
+ # Specify the format of the output Swagger file when running 'rswag:specs:swaggerize'.
39
+ # The swagger_docs configuration option has the filename including format in
40
+ # the key, this may want to be changed to avoid putting yaml in json files.
41
+ # Defaults to json. Accepts ':json' and ':yaml'.
42
+ config.swagger_format = :yaml
43
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rswag
4
+ class RouteParser
5
+ attr_reader :controller
6
+
7
+ def initialize(controller)
8
+ @controller = controller
9
+ end
10
+
11
+ def routes
12
+ ::Rails.application.routes.routes.select do |route|
13
+ route.defaults[:controller] == controller
14
+ end.each_with_object({}) do |route, tree|
15
+ path = path_from(route)
16
+ verb = verb_from(route)
17
+ tree[path] ||= { params: params_from(route), actions: {} }
18
+ tree[path][:actions][verb] = { summary: summary_from(route) }
19
+ tree
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def path_from(route)
26
+ route.path.spec.to_s
27
+ .chomp('(.:format)') # Ignore any format suffix
28
+ .gsub(/:([^\/.?]+)/, '{\1}') # Convert :id to {id}
29
+ end
30
+
31
+ def verb_from(route)
32
+ verb = route.verb
33
+ if verb.is_a? String
34
+ verb.downcase
35
+ else
36
+ verb.source.gsub(/[$^]/, '').downcase
37
+ end
38
+ end
39
+
40
+ def summary_from(route)
41
+ verb = route.requirements[:action]
42
+ noun = route.requirements[:controller].split('/').last.singularize
43
+
44
+ # Apply a few customizations to make things more readable
45
+ case verb
46
+ when 'index'
47
+ verb = 'list'
48
+ noun = noun.pluralize
49
+ when 'destroy'
50
+ verb = 'delete'
51
+ end
52
+
53
+ "#{verb} #{noun}"
54
+ end
55
+
56
+ def params_from(route)
57
+ route.segments - ['format']
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rswag
4
+ module Specs
5
+ class Configuration
6
+ def initialize(rspec_config)
7
+ @rspec_config = rspec_config
8
+ end
9
+
10
+ def swagger_root
11
+ @swagger_root ||= begin
12
+ if @rspec_config.swagger_root.nil?
13
+ raise ConfigurationError, 'No swagger_root provided. See swagger_helper.rb'
14
+ end
15
+
16
+ @rspec_config.swagger_root
17
+ end
18
+ end
19
+
20
+ def swagger_docs
21
+ @swagger_docs ||= begin
22
+ if @rspec_config.swagger_docs.nil? || @rspec_config.swagger_docs.empty?
23
+ raise ConfigurationError, 'No swagger_docs defined. See swagger_helper.rb'
24
+ end
25
+
26
+ @rspec_config.swagger_docs
27
+ end
28
+ end
29
+
30
+ def swagger_dry_run
31
+ return @swagger_dry_run if defined? @swagger_dry_run
32
+ if ENV.key?('SWAGGER_DRY_RUN')
33
+ @rspec_config.swagger_dry_run = ENV['SWAGGER_DRY_RUN'] == '1'
34
+ end
35
+ @swagger_dry_run = @rspec_config.swagger_dry_run.nil? || @rspec_config.swagger_dry_run
36
+ end
37
+
38
+ def swagger_format
39
+ @swagger_format ||= begin
40
+ @rspec_config.swagger_format = :json if @rspec_config.swagger_format.nil? || @rspec_config.swagger_format.empty?
41
+ raise ConfigurationError, "Unknown swagger_format '#{@rspec_config.swagger_format}'" unless [:json, :yaml].include?(@rspec_config.swagger_format)
42
+
43
+ @rspec_config.swagger_format
44
+ end
45
+ end
46
+
47
+ def get_swagger_doc(name)
48
+ return swagger_docs.values.first if name.nil?
49
+ raise ConfigurationError, "Unknown swagger_doc '#{name}'" unless swagger_docs[name]
50
+
51
+ swagger_docs[name]
52
+ end
53
+
54
+ def get_swagger_doc_version(name)
55
+ doc = get_swagger_doc(name)
56
+ doc[:openapi] || doc[:swagger]
57
+ end
58
+
59
+ def swagger_strict_schema_validation
60
+ @swagger_strict_schema_validation ||= (@rspec_config.swagger_strict_schema_validation || false)
61
+ end
62
+ end
63
+
64
+ class ConfigurationError < StandardError; end
65
+ end
66
+ end
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+
5
+ module Rswag
6
+ module Specs
7
+ module ExampleGroupHelpers
8
+ ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: Support for Ruby 2.6 will be dropped in v3.0') if RUBY_VERSION.start_with? '2.6'
9
+
10
+ def path(template, metadata = {}, &block)
11
+ metadata[:path_item] = { template: template }
12
+ describe(template, metadata, &block)
13
+ end
14
+
15
+ [:get, :post, :patch, :put, :delete, :head, :options, :trace].each do |verb|
16
+ define_method(verb) do |summary, &block|
17
+ api_metadata = { operation: { verb: verb, summary: summary } }
18
+ describe(verb, api_metadata, &block)
19
+ end
20
+ end
21
+
22
+ [:operationId, :deprecated, :security].each do |attr_name|
23
+ define_method(attr_name) do |value|
24
+ metadata[:operation][attr_name] = value
25
+ end
26
+ end
27
+
28
+ # NOTE: 'description' requires special treatment because ExampleGroup already
29
+ # defines a method with that name. Provide an override that supports the existing
30
+ # functionality while also setting the appropriate metadata if applicable
31
+ def description(value = nil)
32
+ return super() if value.nil?
33
+
34
+ metadata[:operation][:description] = value
35
+ end
36
+
37
+ # These are array properties - note the splat operator
38
+ [:tags, :consumes, :produces, :schemes].each do |attr_name|
39
+ define_method(attr_name) do |*value|
40
+ metadata[:operation][attr_name] = value
41
+ end
42
+ end
43
+
44
+ def parameter(attributes)
45
+ if attributes[:in] && attributes[:in].to_sym == :path
46
+ attributes[:required] = true
47
+ end
48
+
49
+ if metadata.key?(:operation)
50
+ metadata[:operation][:parameters] ||= []
51
+ metadata[:operation][:parameters] << attributes
52
+ else
53
+ metadata[:path_item][:parameters] ||= []
54
+ metadata[:path_item][:parameters] << attributes
55
+ end
56
+ end
57
+
58
+
59
+ def request_body_example(value:, summary: nil, name: nil)
60
+ if metadata.key?(:operation)
61
+ metadata[:operation][:request_examples] ||= []
62
+ example = { value: value }
63
+ example[:summary] = summary if summary
64
+ # We need the examples to have a unique name for a set of examples, so just make the name the length if one isn't provided.
65
+ example[:name] = name || metadata[:operation][:request_examples].length()
66
+ metadata[:operation][:request_examples] << example
67
+ end
68
+ end
69
+
70
+ def response(code, description, metadata = {}, &block)
71
+ metadata[:response] = { code: code, description: description }
72
+ context(description, metadata, &block)
73
+ end
74
+
75
+ def schema(value)
76
+ metadata[:response][:schema] = value
77
+ end
78
+
79
+ def header(name, attributes)
80
+ metadata[:response][:headers] ||= {}
81
+
82
+ metadata[:response][:headers][name] = attributes
83
+ end
84
+
85
+ # NOTE: Similar to 'description', 'examples' need to handle the case when
86
+ # being invoked with no params to avoid overriding 'examples' method of
87
+ # rspec-core ExampleGroup
88
+ def examples(examples = nil)
89
+ return super() if examples.nil?
90
+ # should we add a deprecation warning?
91
+ examples.each_with_index do |(mime, example_object), index|
92
+ example(mime, "example_#{index}", example_object)
93
+ end
94
+ end
95
+
96
+ def example(mime, name, value, summary=nil, description=nil)
97
+ # Todo - move initialization of metadata somewhere else.
98
+ if metadata[:response][:content].blank?
99
+ metadata[:response][:content] = {}
100
+ end
101
+
102
+ if metadata[:response][:content][mime].blank?
103
+ metadata[:response][:content][mime] = {}
104
+ metadata[:response][:content][mime][:examples] = {}
105
+ end
106
+
107
+ example_object = {
108
+ value: value,
109
+ summary: summary,
110
+ description: description
111
+ }.select { |_, v| v.present? }
112
+ # TODO, issue a warning if example is being overridden with the same key
113
+ metadata[:response][:content][mime][:examples].merge!(
114
+ { name.to_sym => example_object }
115
+ )
116
+ end
117
+
118
+ #
119
+ # Perform request and assert response matches swagger definitions
120
+ #
121
+ # @param options [Hash] options to pass to the `it` method
122
+ # @param &block [Proc] you can make additional assertions within that block
123
+ # @return [void]
124
+ def run_test!(**options, &block)
125
+ options[:rswag] = true unless options.key?(:rswag)
126
+
127
+ if RSPEC_VERSION < 3
128
+ ActiveSupport::Deprecation.warn('Rswag::Specs: WARNING: Support for RSpec 2.X will be dropped in v3.0')
129
+ before do
130
+ submit_request(example.metadata)
131
+ end
132
+
133
+ it "returns a #{metadata[:response][:code]} response", **options do
134
+ assert_response_matches_metadata(metadata)
135
+ block.call(response) if block_given?
136
+ end
137
+ else
138
+ before do |example|
139
+ submit_request(example.metadata)
140
+ end
141
+
142
+ it "returns a #{metadata[:response][:code]} response", **options do |example|
143
+ assert_response_matches_metadata(example.metadata, &block)
144
+ example.instance_exec(response, &block) if block_given?
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rswag/specs/request_factory'
4
+ require 'rswag/specs/response_validator'
5
+
6
+ module Rswag
7
+ module Specs
8
+ module ExampleHelpers
9
+ def submit_request(metadata)
10
+ request = RequestFactory.new.build_request(metadata, self)
11
+
12
+ if RAILS_VERSION < 5
13
+ send(
14
+ request[:verb],
15
+ request[:path],
16
+ request[:payload],
17
+ request[:headers]
18
+ )
19
+ else
20
+ send(
21
+ request[:verb],
22
+ request[:path],
23
+ params: request[:payload],
24
+ headers: request[:headers]
25
+ )
26
+ end
27
+ end
28
+
29
+ def assert_response_matches_metadata(metadata)
30
+ ResponseValidator.new.validate!(metadata, response)
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json-schema'
4
+
5
+ module Rswag
6
+ module Specs
7
+ class ExtendedSchema < JSON::Schema::Draft4
8
+ def initialize
9
+ super
10
+ @uri = URI.parse('http://tempuri.org/rswag/specs/extended_schema')
11
+ @names = ['http://tempuri.org/rswag/specs/extended_schema']
12
+ end
13
+
14
+ def validate(current_schema, data, *)
15
+ return if data.nil? && (current_schema.schema['nullable'] == true || current_schema.schema['x-nullable'] == true)
16
+
17
+ super
18
+ end
19
+ end
20
+
21
+ JSON::Validator.register_validator(ExtendedSchema.new)
22
+ end
23
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rswag
4
+ module Specs
5
+ class Railtie < ::Rails::Railtie
6
+ rake_tasks do
7
+ load File.expand_path('../../tasks/rswag-specs_tasks.rake', __dir__)
8
+ end
9
+
10
+ generators do
11
+ require 'generators/rspec/swagger_generator.rb'
12
+ end
13
+ end
14
+ end
15
+ end