diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b579c412..a89032c4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,11 +26,19 @@ jobs: rails: 6.1.6 - ruby: ruby:3.1 rails: 7.0.3 + coverage: coverage env: RAILS_VERSION: ${{ matrix.rails == '' && '6.1.6' || matrix.rails }} + COVERAGE: ${{ matrix.coverage || '' }} steps: - uses: actions/checkout@v2 - name: bundle install run: bundle install -j$(nproc) --retry 3 - run: bundle exec rspec timeout-minutes: 1 + - name: Upload coverage reports + uses: codecov/codecov-action@v3 + if: matrix.coverage == 'coverage' + with: + fail_ci_if_error: true + files: ./coverage/coverage.xml diff --git a/.rubocop.yml b/.rubocop.yml index 82f64f13..193faee4 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -17,6 +17,8 @@ Style/ClassAndModuleChildren: EnforcedStyle: compact Exclude: - 'lib/rspec/openapi/version.rb' +Layout/FirstArrayElementIndentation: + EnforcedStyle: consistent Metrics/BlockLength: Exclude: - 'spec/**/*' diff --git a/.simplecov_spawn.rb b/.simplecov_spawn.rb new file mode 100644 index 00000000..040e7669 --- /dev/null +++ b/.simplecov_spawn.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +unless ENV['COVERAGE'] && ENV['COVERAGE'].empty? + require 'simplecov' + require 'simplecov-cobertura' + + SimpleCov.at_fork.call(Process.pid) + SimpleCov.formatter SimpleCov::Formatter::MultiFormatter.new([ + SimpleCov::Formatter::CoberturaFormatter, + ]) + SimpleCov.start do + add_filter '/spec/' + add_filter '/scripts/' + end +end diff --git a/Gemfile b/Gemfile index 09f8e80a..897b4735 100644 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,8 @@ gem 'roda' gem 'rspec-rails' group :test do + gem 'simplecov' + gem 'simplecov-cobertura' gem 'super_diff' end diff --git a/README.md b/README.md index 0999ebbb..860f5722 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# rspec-openapi ![test](https://github.com/k0kubun/rspec-openapi/workflows/test/badge.svg) +# rspec-openapi ![test](https://github.com/exoego/rspec-openapi/workflows/test/badge.svg) [![codecov](https://codecov.io/gh/exoego/rspec-openapi/branch/master/graph/badge.svg?token=egYm6AlxkD)](https://codecov.io/gh/exoego/rspec-openapi) Generate OpenAPI schema from RSpec request specs. @@ -308,6 +308,7 @@ Some examples' attributes can be overwritten via RSpec metadata options. Example summary: 'list all posts', description: 'list all posts ordered by pub_date', tags: %w[v1 posts], + required_request_params: %w[limit], security: [{"MyToken" => []}], } do # ... @@ -344,6 +345,12 @@ It should work with both classes inheriting from `ActionDispatch::IntegrationTes Please note that not all features present in the rspec integration work with minitest (yet). For example, custom per test case metadata is not supported. A custom `description_builder` will not work either. +Run minitest with OPENAPI=1 to generate `doc/openapi.yaml` for your request specs. + +```bash +$ OPENAPI=1 bundle exec rails t +``` + ## Links Existing RSpec plugins which have OpenAPI integration: diff --git a/lib/rspec/openapi.rb b/lib/rspec/openapi.rb index a6c4a9c6..c91b054e 100644 --- a/lib/rspec/openapi.rb +++ b/lib/rspec/openapi.rb @@ -10,10 +10,8 @@ require 'rspec/openapi/schema_merger' require 'rspec/openapi/schema_cleaner' -if ENV['OPENAPI'] - require 'rspec/openapi/minitest_hooks' - require 'rspec/openapi/rspec_hooks' -end +require 'rspec/openapi/minitest_hooks' if Object.const_defined?('Minitest') +require 'rspec/openapi/rspec_hooks' if ENV['OPENAPI'] && Object.const_defined?('RSpec') module RSpec::OpenAPI @path = 'doc/openapi.yaml' diff --git a/lib/rspec/openapi/minitest_hooks.rb b/lib/rspec/openapi/minitest_hooks.rb index 30ce3aa0..73dda985 100644 --- a/lib/rspec/openapi/minitest_hooks.rb +++ b/lib/rspec/openapi/minitest_hooks.rb @@ -5,11 +5,7 @@ module RSpec::OpenAPI::Minitest Example = Struct.new(:context, :description, :metadata, :file_path) - module TestPatch - def self.prepended(base) - base.extend(ClassMethods) - end - + module RunPatch def run(*args) result = super if ENV['OPENAPI'] && self.class.openapi? @@ -22,6 +18,12 @@ def run(*args) end result end + end + + module ActivateOpenApiClassMethods + def self.prepended(base) + base.extend(ClassMethods) + end module ClassMethods def openapi? @@ -35,10 +37,12 @@ def openapi! end end -Minitest::Test.prepend RSpec::OpenAPI::Minitest::TestPatch +Minitest::Test.prepend RSpec::OpenAPI::Minitest::ActivateOpenApiClassMethods + +if ENV['OPENAPI'] + Minitest::Test.prepend RSpec::OpenAPI::Minitest::RunPatch -Minitest.after_run do - if ENV['OPENAPI'] + Minitest.after_run do result_recorder = RSpec::OpenAPI::ResultRecorder.new(RSpec::OpenAPI.path_records) result_recorder.record_results! puts result_record.error_message if result_recorder.errors? diff --git a/lib/rspec/openapi/record.rb b/lib/rspec/openapi/record.rb index f86ea4dd..cbba7be3 100644 --- a/lib/rspec/openapi/record.rb +++ b/lib/rspec/openapi/record.rb @@ -6,6 +6,7 @@ :path_params, # @param [Hash] - {:controller=>"v1/statuses", :action=>"create", :id=>"1"} :query_params, # @param [Hash] - {:query=>"string"} :request_params, # @param [Hash] - {:request=>"body"} + :required_request_params, # @param [Array] - ["param1", "param2"] :request_content_type, # @param [String] - "application/json" :request_headers, # @param [Array] - [["header_key1", "header_value1"], ["header_key2", "header_value2"]] :summary, # @param [String] - "v1/statuses #show" diff --git a/lib/rspec/openapi/record_builder.rb b/lib/rspec/openapi/record_builder.rb index 013b3043..3541367b 100644 --- a/lib/rspec/openapi/record_builder.rb +++ b/lib/rspec/openapi/record_builder.rb @@ -11,7 +11,8 @@ def build(context, example:) request, response = extract_request_response(context) return if request.nil? - path, summary, tags, raw_path_params, description, security = extract_request_attributes(request, example) + path, summary, tags, required_request_params, raw_path_params, description, security = + extract_request_attributes(request, example) request_headers, response_headers = extract_headers(request, response) @@ -21,6 +22,7 @@ def build(context, example:) path_params: raw_path_params, query_params: request.query_parameters, request_params: raw_request_params(request), + required_request_params: required_request_params, request_content_type: request.media_type, request_headers: request_headers, summary: summary, @@ -61,6 +63,7 @@ def extract_request_attributes(request, example) metadata = example.metadata[:openapi] || {} summary = metadata[:summary] tags = metadata[:tags] + required_request_params = metadata[:required_request_params] || [] security = metadata[:security] description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example) raw_path_params = request.path_parameters @@ -83,7 +86,7 @@ def extract_request_attributes(request, example) raw_path_params = raw_path_params.slice(*(raw_path_params.keys - %i[controller action format])) end summary ||= "#{request.method} #{path}" - [path, summary, tags, raw_path_params, description, security] + [path, summary, tags, required_request_params, raw_path_params, description, security] end def extract_request_response(context) diff --git a/lib/rspec/openapi/result_recorder.rb b/lib/rspec/openapi/result_recorder.rb index 9a2ec890..1ff6b560 100644 --- a/lib/rspec/openapi/result_recorder.rb +++ b/lib/rspec/openapi/result_recorder.rb @@ -15,7 +15,6 @@ def record_results! RSpec::OpenAPI::SchemaMerger.merge!(spec, schema) new_from_zero = {} records.each do |record| - File.open('/tmp/records', 'a') { |f| f.puts record.to_yaml } begin record_schema = RSpec::OpenAPI::SchemaBuilder.build(record) RSpec::OpenAPI::SchemaMerger.merge!(spec, record_schema) diff --git a/lib/rspec/openapi/schema_builder.rb b/lib/rspec/openapi/schema_builder.rb index 5a65cb0f..641c27b4 100644 --- a/lib/rspec/openapi/schema_builder.rb +++ b/lib/rspec/openapi/schema_builder.rb @@ -73,6 +73,7 @@ def build_parameters(record) parameters << { name: build_parameter_name(key, value), in: 'query', + required: record.required_request_params.include?(key), schema: build_property(try_cast(value)), example: (try_cast(value) if example_enabled?), }.compact @@ -191,10 +192,23 @@ def build_example(value) def adjust_params(value) value.each do |key, v| - if v.is_a?(ActionDispatch::Http::UploadedFile) + case v + when ActionDispatch::Http::UploadedFile value[key] = v.original_filename - elsif v.is_a?(Hash) + when Hash adjust_params(v) + when Array + result = v.map do |item| + case item + when ActionDispatch::Http::UploadedFile + item.original_filename + when Hash + adjust_params(item) + else + item + end + end + value[key] = result end end end diff --git a/lib/rspec/openapi/schema_cleaner.rb b/lib/rspec/openapi/schema_cleaner.rb index 89e287ed..f6cf5b2e 100644 --- a/lib/rspec/openapi/schema_cleaner.rb +++ b/lib/rspec/openapi/schema_cleaner.rb @@ -47,7 +47,7 @@ def cleanup_empty_required_array!(base) paths_to_objects.each do |path| parent = base.dig(*path.take(path.length - 1)) # "required" array must not be present if empty - parent.delete('required') if parent['required'].empty? + parent.delete('required') if parent['required'] && parent['required'].empty? end end diff --git a/scripts/rspec b/scripts/rspec new file mode 100755 index 00000000..9adf4c3c --- /dev/null +++ b/scripts/rspec @@ -0,0 +1,11 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# (The MIT License) +# Copyright (c) 2012 Chad Humphries, David Chelimsky, Myron Marston +# Copyright (c) 2009 Chad Humphries, David Chelimsky +# Copyright (c) 2006 David Chelimsky, The RSpec Development Team +# Copyright (c) 2005 Steven Baker + +require 'rspec/core' +RSpec::Core::Runner.invoke diff --git a/scripts/rspec_with_simplecov b/scripts/rspec_with_simplecov new file mode 100755 index 00000000..b6fa6544 --- /dev/null +++ b/scripts/rspec_with_simplecov @@ -0,0 +1,49 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +# (The MIT License) +# Copyright (c) 2012 Chad Humphries, David Chelimsky, Myron Marston +# Copyright (c) 2009 Chad Humphries, David Chelimsky +# Copyright (c) 2006 David Chelimsky, The RSpec Development Team +# Copyright (c) 2005 Steven Baker + +# Turn on verbose to make sure we not generating any ruby warning +$VERBOSE = true + +# So our "did they run the rspec command?" detection logic thinks +# that we run `rspec`. +$0 = 'rspec' + +# This is necessary for when `--standalone` is being used. +$LOAD_PATH.unshift File.expand_path '../bundle', __dir__ + +# For the travis build we put the bundle directory up a directory +# so it can be shared among the repos for faster bundle installs. +$LOAD_PATH.unshift File.expand_path '../../bundle', __dir__ + +require 'bundler/setup' + +# To use simplecov while running rspec-core's test suite, we must +# load simplecov _before_ loading any of rspec-core's files. +# So, this executable exists purely as a wrapper script that +# first loads simplecov, and then loads rspec. +begin + # Simplecov emits some ruby warnings when loaded, so silence them. + old_verbose = $VERBOSE + $VERBOSE = false + + unless (ENV.fetch('COVERAGE', nil) && ENV['COVERAGE'].empty?) || RUBY_VERSION < '1.9.3' + require 'simplecov' + + SimpleCov.start do + # Flaky :( + # enable_coverage :branch + end + end +rescue LoadError + # simplecov is not available +ensure + $VERBOSE = old_verbose +end + +load File.expand_path('rspec', __dir__) diff --git a/spec/integration_tests/rails_test.rb b/spec/integration_tests/rails_test.rb index cc1dce30..043b0a80 100644 --- a/spec/integration_tests/rails_test.rb +++ b/spec/integration_tests/rails_test.rb @@ -4,9 +4,8 @@ ENV['RAILS_ENV'] ||= 'test' ENV['OPENAPI_OUTPUT'] ||= 'yaml' -require File.expand_path('../rails/config/environment', __dir__) - require 'minitest/autorun' +require File.expand_path('../rails/config/environment', __dir__) RSpec::OpenAPI.request_headers = %w[X-Authorization-Token] RSpec::OpenAPI.response_headers = %w[X-Cursor] @@ -104,13 +103,7 @@ class TablesUpdateTest < ActionDispatch::IntegrationTest openapi! test 'returns a table' do - png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP - QBoAAAAASUVORK5CYII='.unpack1('m') - File.binwrite('test.png', png) - image = Rack::Test::UploadedFile.new('test.png', 'image/png') - patch '/tables/1', headers: { authorization: 'k0kubun' }, params: { - nested: { image: image, caption: 'Some caption' }, - } + patch '/tables/1', headers: { authorization: 'k0kubun' }, params: { name: 'test' } assert_response 200 end end @@ -141,6 +134,42 @@ class ImageTest < ActionDispatch::IntegrationTest get '/images' assert_response 200 end + + test 'returns a image payload with upload' do + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack1('m') + File.binwrite('test.png', png) + image = Rack::Test::UploadedFile.new('test.png', 'image/png') + post '/images/upload', params: { image: image } + assert_response 200 + end + + test 'returns a image payload with upload nested' do + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack1('m') + File.binwrite('test.png', png) + image = Rack::Test::UploadedFile.new('test.png', 'image/png') + post '/images/upload_nested', params: { nested_image: { image: image, caption: 'Some caption' } } + assert_response 200 + end + + test 'returns a image payload with upload multiple' do + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack1('m') + File.binwrite('test.png', png) + image = Rack::Test::UploadedFile.new('test.png', 'image/png') + post '/images/upload_multiple', params: { images: [image, image] } + assert_response 200 + end + + test 'returns a image payload with upload multiple nested' do + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack1('m') + File.binwrite('test.png', png) + image = Rack::Test::UploadedFile.new('test.png', 'image/png') + post '/images/upload_multiple_nested', params: { images: [{ image: image }, { image: image }] } + assert_response 200 + end end class ExtraRoutesTest < ActionDispatch::IntegrationTest diff --git a/spec/integration_tests/roda_test.rb b/spec/integration_tests/roda_test.rb index 5bcc6b98..366133e4 100644 --- a/spec/integration_tests/roda_test.rb +++ b/spec/integration_tests/roda_test.rb @@ -3,8 +3,8 @@ require_relative '../roda/roda_app' require 'json' require 'rack/test' -require 'rspec/openapi' require 'minitest/autorun' +require 'rspec/openapi' ENV['OPENAPI_OUTPUT'] ||= 'yaml' diff --git a/spec/minitest/rack_test_spec.rb b/spec/minitest/rack_test_spec.rb index 49d8b190..20c8d424 100644 --- a/spec/minitest/rack_test_spec.rb +++ b/spec/minitest/rack_test_spec.rb @@ -32,4 +32,10 @@ expect(new_yaml).to eq org_yaml end end + + describe 'with disabled OpenAPI generation' do + it 'can run tests' do + minitest 'spec/integration_tests/roda_test.rb' + end + end end diff --git a/spec/minitest/rails_spec.rb b/spec/minitest/rails_spec.rb index a5d71c5e..d67a3cab 100644 --- a/spec/minitest/rails_spec.rb +++ b/spec/minitest/rails_spec.rb @@ -33,4 +33,10 @@ expect(new_yaml).to eq org_yaml end end + + describe 'with disabled OpenAPI generation' do + it 'can run tests' do + minitest 'spec/integration_tests/rails_test.rb' + end + end end diff --git a/spec/rails/app/controllers/images_controller.rb b/spec/rails/app/controllers/images_controller.rb index a750581c..b2aab36e 100644 --- a/spec/rails/app/controllers/images_controller.rb +++ b/spec/rails/app/controllers/images_controller.rb @@ -1,8 +1,6 @@ class ImagesController < ApplicationController def show - png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP - QBoAAAAASUVORK5CYII='.unpack('m').first - send_data png, type: 'image/png', disposition: 'inline' + send_image end def index @@ -14,4 +12,28 @@ def index ] render json: list end + + def upload + send_image + end + + def upload_nested + send_image + end + + def upload_multiple + send_image + end + + def upload_multiple_nested + send_image + end + + private + + def send_image + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack('m').first + send_data png, type: 'image/png', disposition: 'inline' + end end diff --git a/spec/rails/app/controllers/users_controller.rb b/spec/rails/app/controllers/users_controller.rb index e15eead5..8acafe6b 100644 --- a/spec/rails/app/controllers/users_controller.rb +++ b/spec/rails/app/controllers/users_controller.rb @@ -16,6 +16,10 @@ def show render json: find_user(params[:id]) end + def active + render json: find_user(params[:id]).present? + end + private def find_user(id = nil) diff --git a/spec/rails/config/routes.rb b/spec/rails/config/routes.rb index 7f129d99..24e09746 100644 --- a/spec/rails/config/routes.rb +++ b/spec/rails/config/routes.rb @@ -5,8 +5,17 @@ defaults format: 'json' do resources :tables, only: [:index, :show, :create, :update, :destroy] - resources :images, only: [:index, :show] - resources :users, only: [:show, :create] + resources :images, only: [:index, :show] do + collection do + post 'upload' + post 'upload_nested' + post 'upload_multiple' + post 'upload_multiple_nested' + end + end + resources :users, only: [:show, :create] do + get 'active' + end get '/test_block' => ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['A TEST']] } end diff --git a/spec/rails/doc/openapi.json b/spec/rails/doc/openapi.json index 4fa23f26..65b27841 100644 --- a/spec/rails/doc/openapi.json +++ b/spec/rails/doc/openapi.json @@ -141,6 +141,7 @@ { "name": "filter[name]", "in": "query", + "required": false, "schema": { "type": "object", "properties": { @@ -159,6 +160,7 @@ { "name": "filter[price]", "in": "query", + "required": false, "schema": { "type": "object", "properties": { @@ -177,6 +179,7 @@ { "name": "page", "in": "query", + "required": false, "schema": { "type": "integer" }, @@ -185,6 +188,7 @@ { "name": "per", "in": "query", + "required": false, "schema": { "type": "integer" }, @@ -455,36 +459,20 @@ ], "requestBody": { "content": { - "multipart/form-data": { + "application/x-www-form-urlencoded": { "schema": { "type": "object", "properties": { - "nested": { - "type": "object", - "properties": { - "image": { - "type": "string", - "format": "binary" - }, - "caption": { - "type": "string" - } - }, - "required": [ - "image", - "caption" - ] + "name": { + "type": "string" } }, "required": [ - "nested" + "name" ] }, "example": { - "nested": { - "image": "test.png", - "caption": "Some caption" - } + "name": "test" } } } @@ -818,6 +806,213 @@ } } } + }, + "/images/upload_nested": { + "post": { + "summary": "upload_nested", + "tags": [ + "Image" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "nested_image": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary" + }, + "caption": { + "type": "string" + } + }, + "required": [ + "image", + "caption" + ] + } + }, + "required": [ + "nested_image" + ] + }, + "example": { + "nested_image": { + "image": "test.png", + "caption": "Some caption" + } + } + } + } + }, + "responses": { + "200": { + "description": "returns a image payload with upload nested", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/images/upload": { + "post": { + "summary": "upload", + "tags": [ + "Image" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "image" + ] + }, + "example": { + "image": "test.png" + } + } + } + }, + "responses": { + "200": { + "description": "returns a image payload with upload", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/images/upload_multiple_nested": { + "post": { + "summary": "upload_multiple_nested", + "tags": [ + "Image" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "type": "object", + "properties": { + "image": { + "type": "string", + "format": "binary" + } + }, + "required": [ + "image" + ] + } + } + }, + "required": [ + "images" + ] + }, + "example": { + "images": [ + { + "image": "test.png" + }, + { + "image": "test.png" + } + ] + } + } + } + }, + "responses": { + "200": { + "description": "returns a image payload with upload multiple nested", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } + }, + "/images/upload_multiple": { + "post": { + "summary": "upload_multiple", + "tags": [ + "Image" + ], + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "images": { + "type": "array", + "items": { + "type": "string", + "format": "binary" + } + } + }, + "required": [ + "images" + ] + }, + "example": { + "images": [ + "test.png", + "test.png" + ] + } + } + } + }, + "responses": { + "200": { + "description": "returns a image payload with upload multiple", + "content": { + "image/png": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + } + } + } } } } diff --git a/spec/rails/doc/openapi.yaml b/spec/rails/doc/openapi.yaml index 5218adb5..3ea218c6 100644 --- a/spec/rails/doc/openapi.yaml +++ b/spec/rails/doc/openapi.yaml @@ -28,6 +28,7 @@ paths: example: token - name: filter[name] in: query + required: false schema: type: object properties: @@ -39,6 +40,7 @@ paths: name: Example Table - name: filter[price] in: query + required: false schema: type: object properties: @@ -50,11 +52,13 @@ paths: price: '0' - name: page in: query + required: false schema: type: integer example: 1 - name: per in: query + required: false schema: type: integer example: 10 @@ -311,27 +315,16 @@ paths: example: 1 requestBody: content: - multipart/form-data: + application/x-www-form-urlencoded: schema: type: object properties: - nested: - type: object - properties: - image: - type: string - format: binary - caption: - type: string - required: - - image - - caption + name: + type: string required: - - nested + - name example: - nested: - image: test.png - caption: Some caption + name: test responses: '200': description: returns a table @@ -545,3 +538,130 @@ paths: schema: type: string example: ANOTHER TEST + "/images/upload": + post: + summary: upload + tags: + - Image + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + image: + type: string + format: binary + required: + - image + example: + image: test.png + responses: + '200': + description: returns a image payload with upload + content: + image/png: + schema: + type: string + format: binary + "/images/upload_nested": + post: + summary: upload_nested + tags: + - Image + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + nested_image: + type: object + properties: + image: + type: string + format: binary + caption: + type: string + required: + - image + - caption + required: + - nested_image + example: + nested_image: + image: test.png + caption: Some caption + responses: + '200': + description: returns a image payload with upload nested + content: + image/png: + schema: + type: string + format: binary + "/images/upload_multiple_nested": + post: + summary: upload_multiple_nested + tags: + - Image + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + images: + type: array + items: + type: object + properties: + image: + type: string + format: binary + required: + - image + required: + - images + example: + images: + - image: test.png + - image: test.png + responses: + '200': + description: returns a image payload with upload multiple nested + content: + image/png: + schema: + type: string + format: binary + "/images/upload_multiple": + post: + summary: upload_multiple + tags: + - Image + requestBody: + content: + multipart/form-data: + schema: + type: object + properties: + images: + type: array + items: + type: string + format: binary + required: + - images + example: + images: + - test.png + - test.png + responses: + '200': + description: returns a image payload with upload multiple + content: + image/png: + schema: + type: string + format: binary diff --git a/spec/rails/doc/smart/expected.yaml b/spec/rails/doc/smart/expected.yaml index e32f02fb..aebfa2cf 100644 --- a/spec/rails/doc/smart/expected.yaml +++ b/spec/rails/doc/smart/expected.yaml @@ -28,16 +28,19 @@ paths: example: token - name: page in: query + required: false schema: type: integer example: 42 - name: per in: query + required: false schema: type: integer example: 10 - name: show_columns in: query + required: true schema: type: string example: "true" @@ -204,6 +207,26 @@ paths: schema: type: integer example: 2 + "/users/{user_id}/active": + get: + responses: + "200": + description: "returns a boolean" + content: + "application/json": + schema: + type: boolean + example: true + summary: active + tags: + - User + parameters: + - name: user_id + in: path + required: true + schema: + type: integer + example: 1 components: securitySchemes: Scheme1: diff --git a/spec/requests/rails_smart_merge_spec.rb b/spec/requests/rails_smart_merge_spec.rb index 847ce028..eaa5c96a 100644 --- a/spec/requests/rails_smart_merge_spec.rb +++ b/spec/requests/rails_smart_merge_spec.rb @@ -36,7 +36,7 @@ # Small subset of `rails_spec.rb` with slight changes RSpec.describe 'Tables', type: :request do - describe '#index' do + describe '#index', openapi: { required_request_params: 'show_columns' } do context it 'returns a list of tables' do it 'with flat query parameters' do # These new params replace them in old spec @@ -102,4 +102,11 @@ expect(response.status).to eq(200) end end + + describe '#active' do + it 'returns a boolean' do + get '/users/1/active' + expect(response.status).to eq(200) + end + end end diff --git a/spec/requests/rails_spec.rb b/spec/requests/rails_spec.rb index 5d56bb78..c6b61558 100644 --- a/spec/requests/rails_spec.rb +++ b/spec/requests/rails_spec.rb @@ -70,6 +70,11 @@ get '/tables/2', headers: { authorization: 'k0kubun' } expect(response.status).to eq(404) end + + it 'does not return a table if not found (openapi: false)', openapi: false do + get '/tables/3', headers: { authorization: 'k0kubun' } + expect(response.status).to eq(404) + end end describe '#create' do @@ -84,16 +89,8 @@ end describe '#update' do - before do - png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP - QBoAAAAASUVORK5CYII='.unpack1('m') - File.binwrite('test.png', png) - end - let(:image) { Rack::Test::UploadedFile.new('test.png', 'image/png') } it 'returns a table' do - patch '/tables/1', headers: { authorization: 'k0kubun' }, params: { - nested: { image: image, caption: 'Some caption' }, - } + patch '/tables/1', headers: { authorization: 'k0kubun' }, params: { name: 'test' } expect(response.status).to eq(200) end end @@ -125,6 +122,62 @@ expect(response.status).to eq(200) end end + + describe '#upload' do + before do + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack1('m') + File.binwrite('test.png', png) + end + let(:image) { Rack::Test::UploadedFile.new('test.png', 'image/png') } + + it 'returns a image payload with upload' do + post '/images/upload', params: { image: image } + expect(response.status).to eq(200) + end + end + + describe '#upload_nested' do + before do + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack1('m') + File.binwrite('test.png', png) + end + let(:image) { Rack::Test::UploadedFile.new('test.png', 'image/png') } + + it 'returns a image payload with upload nested' do + post '/images/upload_nested', params: { nested_image: { image: image, caption: 'Some caption' } } + expect(response.status).to eq(200) + end + end + + describe '#upload_multiple' do + before do + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack1('m') + File.binwrite('test.png', png) + end + let(:image) { Rack::Test::UploadedFile.new('test.png', 'image/png') } + + it 'returns a image payload with upload multiple' do + post '/images/upload_multiple', params: { images: [image, image] } + expect(response.status).to eq(200) + end + end + + describe '#upload_multiple_nested' do + before do + png = 'iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAAAAADhZOFXAAAADklEQVQIW2P4DwUMlDEA98A/wTjP + QBoAAAAASUVORK5CYII='.unpack1('m') + File.binwrite('test.png', png) + end + let(:image) { Rack::Test::UploadedFile.new('test.png', 'image/png') } + + it 'returns a image payload with upload multiple nested' do + post '/images/upload_multiple_nested', params: { images: [{ image: image }, { image: image }] } + expect(response.status).to eq(200) + end + end end RSpec.describe 'Extra routes', type: :request do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f2f3dd1b..c6423e9a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -14,16 +14,19 @@ def assert_run(*args) end def run_tests(*args, command:, openapi: false, output: :yaml) - env = { 'OPENAPI' => ('1' if openapi), 'OPENAPI_OUTPUT' => output.to_s }.compact + env = { + 'OPENAPI' => ('1' if openapi), + 'OPENAPI_OUTPUT' => output.to_s, + }.compact Bundler.public_send(Bundler.respond_to?(:with_unbundled_env) ? :with_unbundled_env : :with_clean_env) do Dir.chdir(repo_root) do - assert_run env, 'bundle', 'exec', command, *args + assert_run env, 'bundle', 'exec', command, '-r./.simplecov_spawn', *args end end end def rspec(*args, openapi: false, output: :yaml) - run_tests(*args, command: 'rspec', openapi: openapi, output: output) + run_tests(*args, command: 'scripts/rspec_with_simplecov', openapi: openapi, output: output) end def minitest(*args, openapi: false, output: :yaml)