diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a8b599..fa57b8b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,15 +12,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed ### Security +## [2.1.0] - 2019-10-17 +### Added +- New Spec Generator [#75](https://github.com/rswag/rswag/pull/75) +- Support for Options and Trace verbs; You must use a framework that supports this, for Options Rails 6.1+ Rails 6 does not support Trace. [#237](https://github.com/rswag/rswag/pull/75) +### Changed +- Update swagger-ui to 3.18.2 [#240](https://github.com/rswag/rswag/pull/240) + ## [2.0.6] - 2019-10-03 ### Added - Support for Rails 6 [#228](https://github.com/rswag/rswag/pull/228) - Support for Windows paths [#176](https://github.com/rswag/rswag/pull/176) ### Changed - Show response body when error code is not expected [#117](https://github.com/rswag/rswag/pull/177) -### Deprecated -### Removed -### Fixed -### Security ## [2.0.5] - 2018-07-10 diff --git a/README.md b/README.md index 267b563..07cc88d 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ Once you have an API that can describe itself in Swagger, you've opened the trea |Rswag Version|Swagger (OpenAPI) Spec.|swagger-ui| |----------|----------|----------| -|[master](https://github.com/rswag/rswag/tree/master)|2.0|3.17.3| -|[2.0.6](https://github.com/rswag/rswag/tree/2.0.6)|2.0|3.17.3| +|[master](https://github.com/rswag/rswag/tree/master)|2.0|3.18.2| +|[2.1.0](https://github.com/rswag/rswag/tree/2.1.0)|2.0|3.18.2| |[1.6.0](https://github.com/rswag/rswag/tree/1.6.0)|2.0|2.2.5| ## Getting Started ## @@ -48,7 +48,8 @@ Once you have an API that can describe itself in Swagger, you've opened the trea Or run the install generators for each package separately if you installed Rswag as separate gems, as indicated above: ```ruby - rails g rswag:api:install rswag:ui:install + rails g rswag:api:install + rails g rswag:ui:install RAILS_ENV=test rails g rswag:specs:install ``` diff --git a/rswag-specs/lib/generators/rspec/USAGE b/rswag-specs/lib/generators/rspec/USAGE new file mode 100644 index 0000000..bc354c4 --- /dev/null +++ b/rswag-specs/lib/generators/rspec/USAGE @@ -0,0 +1,9 @@ +Description: + This creates an RSpec request spec to define Swagger documentation for a + controller. It will create a test for each of the controller's methods. + +Example: + rails generate rspec:swagger V3::AccountsController + + This will create: + spec/requests/v3/accounts_spec.rb diff --git a/rswag-specs/lib/generators/rspec/swagger_generator.rb b/rswag-specs/lib/generators/rspec/swagger_generator.rb new file mode 100644 index 0000000..ddb862c --- /dev/null +++ b/rswag-specs/lib/generators/rspec/swagger_generator.rb @@ -0,0 +1,22 @@ +require 'rswag/route_parser' +require 'rails/generators' + +module Rspec + class SwaggerGenerator < ::Rails::Generators::NamedBase + source_root File.expand_path('../templates', __FILE__) + + def setup + @routes = Rswag::RouteParser.new(controller_path).routes + end + + def create_spec_file + template 'spec.rb', File.join('spec', 'requests', "#{controller_path}_spec.rb") + end + + private + + def controller_path + file_path.chomp('_controller') + end + end +end diff --git a/rswag-specs/lib/generators/rspec/templates/spec.rb b/rswag-specs/lib/generators/rspec/templates/spec.rb new file mode 100644 index 0000000..346e348 --- /dev/null +++ b/rswag-specs/lib/generators/rspec/templates/spec.rb @@ -0,0 +1,30 @@ +require 'swagger_helper' + +RSpec.describe '<%= controller_path %>', type: :request do +<% @routes.each do | template, path_item | %> + path '<%= template %>' do +<% unless path_item[:params].empty? -%> + # You'll want to customize the parameter types... +<% path_item[:params].each do |param| -%> + parameter name: '<%= param %>', in: :path, type: :string, description: '<%= param %>' +<% end -%> +<% end -%> +<% path_item[:actions].each do | action, details | %> + <%= action %>('<%= details[:summary] %>') do + response(200, 'successful') do +<% unless path_item[:params].empty? -%> +<% path_item[:params].each do |param| -%> + let(:<%= param %>) { '123' } +<% end -%> +<% end -%> + + after do |example| + example.metadata[:response][:examples] = { 'application/json' => JSON.parse(response.body, symbolize_names: true) } + end + run_test! + end + end +<% end -%> + end +<% end -%> +end diff --git a/rswag-specs/lib/rswag/route_parser.rb b/rswag-specs/lib/rswag/route_parser.rb new file mode 100644 index 0000000..523b36b --- /dev/null +++ b/rswag-specs/lib/rswag/route_parser.rb @@ -0,0 +1,58 @@ +module Rswag + class RouteParser + attr_reader :controller + + def initialize(controller) + @controller = controller + end + + def routes + ::Rails.application.routes.routes.select do |route| + route.defaults[:controller] == controller + end.reduce({}) do |tree, route| + path = path_from(route) + verb = verb_from(route) + tree[path] ||= { params: params_from(route), actions: {} } + tree[path][:actions][verb] = { summary: summary_from(route) } + tree + end + end + + private + + def path_from(route) + route.path.spec.to_s + .chomp('(.:format)') # Ignore any format suffix + .gsub(/:([^\/.?]+)/, '{\1}') # Convert :id to {id} + end + + def verb_from(route) + verb = route.verb + if verb.kind_of? String + verb.downcase + else + verb.source.gsub(/[$^]/, '').downcase + end + end + + def summary_from(route) + verb = route.requirements[:action] + noun = route.requirements[:controller].split('/').last.singularize + + # Apply a few customizations to make things more readable + case verb + when 'index' + verb = 'list' + noun = noun.pluralize + when 'destroy' + verb = 'delete' + end + + "#{verb} #{noun}" + end + + def params_from(route) + route.segments - ['format'] + end + end +end diff --git a/rswag-specs/lib/rswag/specs/extended_schema.rb b/rswag-specs/lib/rswag/specs/extended_schema.rb index 62eb4ee..29888f8 100644 --- a/rswag-specs/lib/rswag/specs/extended_schema.rb +++ b/rswag-specs/lib/rswag/specs/extended_schema.rb @@ -3,7 +3,7 @@ require 'json-schema' module Rswag module Specs class ExtendedSchema < JSON::Schema::Draft4 - + def initialize super @attributes['type'] = ExtendedTypeAttribute @@ -13,7 +13,7 @@ module Rswag end class ExtendedTypeAttribute < JSON::Schema::TypeV4Attribute - + def self.validate(current_schema, data, fragments, processor, validator, options={}) return if data.nil? && current_schema.schema['x-nullable'] == true super diff --git a/rswag-specs/lib/rswag/specs/railtie.rb b/rswag-specs/lib/rswag/specs/railtie.rb index 8deec2b..994a8a4 100644 --- a/rswag-specs/lib/rswag/specs/railtie.rb +++ b/rswag-specs/lib/rswag/specs/railtie.rb @@ -5,6 +5,10 @@ module Rswag rake_tasks do load File.expand_path('../../../tasks/rswag-specs_tasks.rake', __FILE__) end + + generators do + require 'generators/rspec/swagger/swagger_generator.rb' + end end end end diff --git a/rswag-specs/lib/rswag/specs/request_factory.rb b/rswag-specs/lib/rswag/specs/request_factory.rb index 7106015..14b1edc 100644 --- a/rswag-specs/lib/rswag/specs/request_factory.rb +++ b/rswag-specs/lib/rswag/specs/request_factory.rb @@ -54,7 +54,7 @@ module Rswag definitions[key] end - def add_verb(request, metadata) + def add_verb(request, metadata) request[:verb] = metadata[:operation][:verb] end @@ -104,7 +104,7 @@ module Rswag end # Content-Type header - consumes = metadata[:operation][:consumes] || swagger_doc[:consumes] + consumes = metadata[:operation][:consumes] || swagger_doc[:consumes] if consumes content_type = example.respond_to?(:'Content-Type') ? example.send(:'Content-Type') : consumes.first tuples << [ 'Content-Type', content_type ] diff --git a/rswag-specs/spec/generators/rspec/fixtures/spec/.gitkeep b/rswag-specs/spec/generators/rspec/fixtures/spec/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/rswag-specs/spec/generators/rspec/swagger_generator_spec.rb b/rswag-specs/spec/generators/rspec/swagger_generator_spec.rb new file mode 100644 index 0000000..f11ea65 --- /dev/null +++ b/rswag-specs/spec/generators/rspec/swagger_generator_spec.rb @@ -0,0 +1,44 @@ +require 'generator_spec' +require 'generators/rspec/swagger_generator' +require 'tmpdir' + +module Rspec + describe SwaggerGenerator do + include GeneratorSpec::TestCase + destination Dir.mktmpdir + + before(:all) do + prepare_destination + fixtures_dir = File.expand_path('../fixtures', __FILE__) + FileUtils.cp_r("#{fixtures_dir}/spec", destination_root) + end + + after(:all) do + + end + + it 'installs the swagger_helper for rspec' do + allow_any_instance_of(Rswag::RouteParser).to receive(:routes).and_return(fake_routes) + run_generator ['Posts::CommentsController'] + + assert_file('spec/requests/posts/comments_spec.rb') do |content| + assert_match(/parameter name: 'post_id', in: :path, type: :string/, content) + assert_match(/patch\('update_comments comment'\)/, content) + end + end + + private + + def fake_routes + { + "/posts/{post_id}/comments/{id}" => { + :params => ["post_id", "id"], + :actions => { + "get" => { :summary=>"show comment" }, + "patch" => { :summary=>"update_comments comment" } + } + } + } + end + end +end diff --git a/rswag-specs/spec/generators/rswag/specs/install_generator_spec.rb b/rswag-specs/spec/generators/rswag/specs/install_generator_spec.rb index 39dc6fe..809e4f6 100644 --- a/rswag-specs/spec/generators/rswag/specs/install_generator_spec.rb +++ b/rswag-specs/spec/generators/rswag/specs/install_generator_spec.rb @@ -4,7 +4,7 @@ require 'generators/rswag/specs/install/install_generator' module Rswag module Specs - describe InstallGenerator do + RSpec.describe InstallGenerator do include GeneratorSpec::TestCase destination File.expand_path('../tmp', __FILE__) diff --git a/rswag-specs/spec/rswag/specs/configuration_spec.rb b/rswag-specs/spec/rswag/specs/configuration_spec.rb index b75d843..30da333 100644 --- a/rswag-specs/spec/rswag/specs/configuration_spec.rb +++ b/rswag-specs/spec/rswag/specs/configuration_spec.rb @@ -3,7 +3,7 @@ require 'rswag/specs/configuration' module Rswag module Specs - describe Configuration do + RSpec.describe Configuration do subject { described_class.new(rspec_config) } let(:rspec_config) { OpenStruct.new(swagger_root: swagger_root, swagger_docs: swagger_docs) } 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 c870bfc..ed667ca 100644 --- a/rswag-specs/spec/rswag/specs/example_group_helpers_spec.rb +++ b/rswag-specs/spec/rswag/specs/example_group_helpers_spec.rb @@ -3,7 +3,7 @@ require 'rswag/specs/example_group_helpers' module Rswag module Specs - describe ExampleGroupHelpers do + RSpec.describe ExampleGroupHelpers do subject { double('example_group') } before do diff --git a/rswag-specs/spec/rswag/specs/example_helpers_spec.rb b/rswag-specs/spec/rswag/specs/example_helpers_spec.rb index 3b22af4..283d4fd 100644 --- a/rswag-specs/spec/rswag/specs/example_helpers_spec.rb +++ b/rswag-specs/spec/rswag/specs/example_helpers_spec.rb @@ -3,7 +3,7 @@ require 'rswag/specs/example_helpers' module Rswag module Specs - describe ExampleHelpers do + RSpec.describe ExampleHelpers do subject { double('example') } before do @@ -12,7 +12,7 @@ module Rswag allow(config).to receive(:get_swagger_doc).and_return(swagger_doc) stub_const('Rswag::Specs::RAILS_VERSION', 3) end - let(:config) { double('config') } + let(:config) { double('config') } let(:swagger_doc) do { securityDefinitions: { diff --git a/rswag-specs/spec/rswag/specs/request_factory_spec.rb b/rswag-specs/spec/rswag/specs/request_factory_spec.rb index f883952..0a70f19 100644 --- a/rswag-specs/spec/rswag/specs/request_factory_spec.rb +++ b/rswag-specs/spec/rswag/specs/request_factory_spec.rb @@ -3,13 +3,13 @@ require 'rswag/specs/request_factory' module Rswag module Specs - describe RequestFactory do + RSpec.describe RequestFactory do subject { RequestFactory.new(config) } before do allow(config).to receive(:get_swagger_doc).and_return(swagger_doc) end - let(:config) { double('config') } + let(:config) { double('config') } let(:swagger_doc) { {} } let(:example) { double('example') } let(:metadata) do diff --git a/rswag-specs/spec/rswag/specs/response_validator_spec.rb b/rswag-specs/spec/rswag/specs/response_validator_spec.rb index 1d05427..9f52b5b 100644 --- a/rswag-specs/spec/rswag/specs/response_validator_spec.rb +++ b/rswag-specs/spec/rswag/specs/response_validator_spec.rb @@ -3,13 +3,13 @@ require 'rswag/specs/response_validator' module Rswag module Specs - describe ResponseValidator do + RSpec.describe ResponseValidator do subject { ResponseValidator.new(config) } before do allow(config).to receive(:get_swagger_doc).and_return(swagger_doc) end - let(:config) { double('config') } + let(:config) { double('config') } let(:swagger_doc) { {} } let(:example) { double('example') } let(:metadata) do diff --git a/rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb b/rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb index f904fa5..b5b7ad8 100644 --- a/rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb +++ b/rswag-specs/spec/rswag/specs/swagger_formatter_spec.rb @@ -3,8 +3,8 @@ require 'ostruct' module Rswag module Specs - - describe SwaggerFormatter do + + RSpec.describe SwaggerFormatter do subject { described_class.new(output, config) } # Mock out some infrastructure @@ -47,7 +47,7 @@ module Rswag end describe '#stop' do - before do + 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' } }, diff --git a/rswag-ui/package-lock.json b/rswag-ui/package-lock.json index 664d3fa..144573b 100644 --- a/rswag-ui/package-lock.json +++ b/rswag-ui/package-lock.json @@ -5,9 +5,9 @@ "requires": true, "dependencies": { "swagger-ui-dist": { - "version": "3.17.3", - "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.17.3.tgz", - "integrity": "sha1-37lkCMzEZ3UVX3NpGQxdSyAW/lw=" + "version": "3.18.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-3.18.2.tgz", + "integrity": "sha512-pWAEiKkgWUJvjmLW9AojudnutJ+NTn5g6OdNLj1iIJWwCkoy40K3Upwa24DqFbmIE4vLX4XplND61hp2L+s5vg==" } } } diff --git a/rswag-ui/package.json b/rswag-ui/package.json index 1fce627..27dbc6c 100644 --- a/rswag-ui/package.json +++ b/rswag-ui/package.json @@ -3,6 +3,6 @@ "version": "1.0.0", "private": true, "dependencies": { - "swagger-ui-dist": "3.17.3" + "swagger-ui-dist": "3.18.2" } } diff --git a/test-app/app/assets/config/manifest.js b/test-app/app/assets/config/manifest.js new file mode 100644 index 0000000..5918193 --- /dev/null +++ b/test-app/app/assets/config/manifest.js @@ -0,0 +1,2 @@ +//= link_tree ../images +//= link_directory ../stylesheets .css diff --git a/test-app/app/assets/images/.keep b/test-app/app/assets/images/.keep new file mode 100644 index 0000000..e69de29 diff --git a/test-app/db/schema.rb b/test-app/db/schema.rb index e01f8f3..8accba1 100644 --- a/test-app/db/schema.rb +++ b/test-app/db/schema.rb @@ -2,15 +2,15 @@ # of editing this file, please use the migrations feature of Active Record to # incrementally modify your database, and then regenerate this schema definition. # -# This file is the source Rails uses to define your schema when running `rails -# db:schema:load`. When creating a new database, `rails db:schema:load` tends to -# be faster and is potentially less error prone than running all of your -# migrations from scratch. Old migrations may fail to apply correctly if those -# migrations use external dependencies or application code. +# 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 that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2016_02_18_212104) do +ActiveRecord::Schema.define(version: 20160218212104) do create_table "blogs", force: :cascade do |t| t.string "title" diff --git a/test-app/spec/features/swagger_ui_spec.rb b/test-app/spec/features/swagger_ui_spec.rb index 7faa14e..24d5790 100644 --- a/test-app/spec/features/swagger_ui_spec.rb +++ b/test-app/spec/features/swagger_ui_spec.rb @@ -1,6 +1,6 @@ require 'rails_helper' -feature 'swagger-ui', js: true do +RSpec.feature 'swagger-ui', js: true do scenario 'browsing api-docs' do visit '/api-docs' diff --git a/test-app/spec/integration/auth_tests_spec.rb b/test-app/spec/integration/auth_tests_spec.rb index 8e47d2e..573219e 100644 --- a/test-app/spec/integration/auth_tests_spec.rb +++ b/test-app/spec/integration/auth_tests_spec.rb @@ -1,6 +1,6 @@ require 'swagger_helper' -describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do +RSpec.describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do path '/auth-tests/basic' do post 'Authenticates with basic auth' do diff --git a/test-app/spec/integration/blogs_spec.rb b/test-app/spec/integration/blogs_spec.rb index abca570..28ee892 100644 --- a/test-app/spec/integration/blogs_spec.rb +++ b/test-app/spec/integration/blogs_spec.rb @@ -1,6 +1,6 @@ require 'swagger_helper' -describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do +RSpec.describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do let(:api_key) { 'fake_key' } path '/blogs' do diff --git a/test-app/spec/rake/rswag_specs_swaggerize_spec.rb b/test-app/spec/rake/rswag_specs_swaggerize_spec.rb index 0a590ee..27fc195 100644 --- a/test-app/spec/rake/rswag_specs_swaggerize_spec.rb +++ b/test-app/spec/rake/rswag_specs_swaggerize_spec.rb @@ -1,9 +1,9 @@ require 'spec_helper' require 'rake' -describe 'rswag:specs:swaggerize' do +RSpec.describe 'rswag:specs:swaggerize' do let(:swagger_root) { Rails.root.to_s + '/swagger' } - before do + before do TestApp::Application.load_tasks FileUtils.rm_r(swagger_root) if File.exists?(swagger_root) end