rspec-openapi 0.8.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f835fe8be895164639ec2bfdbb6414b922734cc9598b30b100ac7da15d31b98f
4
- data.tar.gz: 60ba1234561313d8e9fbe5c1f5667e6c759d477ad124867045e3e2e516174ece
3
+ metadata.gz: d55c7178770a58a980d06669adfab735ba84b51a283302dd8e14e91af4b7b4e9
4
+ data.tar.gz: 7b0030924262f8a1cd7027df612315b8d6e3fb89e91f9b4e57c3ec83f7e9bfd6
5
5
  SHA512:
6
- metadata.gz: 44c56d011d95c1350eef4ed83011ff00c749cf9e83ec3cb44f605a6e3ee50c05173117d618d3472ea44b20aeef8981c17ae35f8b91220ff8891382447c1a4679
7
- data.tar.gz: c264b04decebbcd4389633eed77cd98a0241980fa283c320e99c5bdaf5a5b5be36e242367da23abcefbf0f31802dd27f1d123324005c8234657d11328e9e1e60
6
+ metadata.gz: 4cf846ca12d2062ae17c0b5d1829a7887c206425330c015f5fdde310c59678cb802b4cadb60f63c1ca6d55063c3d4f1cc2ac7813db10c6d8611a0fa3af975192
7
+ data.tar.gz: 7501fac5fbfd8b0c4cec6cb196feb9ff09a640c4aff7165a93a6880eebea194d5c30674eefadb53987538ebed913d8d43ab625f1a7c8a3a12ad76ba15b459687
@@ -26,11 +26,19 @@ jobs:
26
26
  rails: 6.1.6
27
27
  - ruby: ruby:3.1
28
28
  rails: 7.0.3
29
+ coverage: coverage
29
30
  env:
30
31
  RAILS_VERSION: ${{ matrix.rails == '' && '6.1.6' || matrix.rails }}
32
+ COVERAGE: ${{ matrix.coverage || '' }}
31
33
  steps:
32
34
  - uses: actions/checkout@v2
33
35
  - name: bundle install
34
36
  run: bundle install -j$(nproc) --retry 3
35
37
  - run: bundle exec rspec
36
38
  timeout-minutes: 1
39
+ - name: Upload coverage reports
40
+ uses: codecov/codecov-action@v3
41
+ if: matrix.coverage == 'coverage'
42
+ with:
43
+ fail_ci_if_error: true
44
+ files: ./coverage/coverage.xml
data/.rubocop.yml CHANGED
@@ -17,6 +17,8 @@ Style/ClassAndModuleChildren:
17
17
  EnforcedStyle: compact
18
18
  Exclude:
19
19
  - 'lib/rspec/openapi/version.rb'
20
+ Layout/FirstArrayElementIndentation:
21
+ EnforcedStyle: consistent
20
22
  Metrics/BlockLength:
21
23
  Exclude:
22
24
  - 'spec/**/*'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ unless ENV['COVERAGE'] && ENV['COVERAGE'].empty?
4
+ require 'simplecov'
5
+ require 'simplecov-cobertura'
6
+
7
+ SimpleCov.at_fork.call(Process.pid)
8
+ SimpleCov.formatter SimpleCov::Formatter::MultiFormatter.new([
9
+ SimpleCov::Formatter::CoberturaFormatter,
10
+ ])
11
+ SimpleCov.start do
12
+ add_filter '/spec/'
13
+ add_filter '/scripts/'
14
+ end
15
+ end
data/CHANGELOG.md CHANGED
@@ -1,3 +1,21 @@
1
+ ## v0.9.0
2
+ - bugfix: Fix engine path resolution
3
+ [#113](https://github.com/exoego/rspec-openapi/pull/113)
4
+ - bugfix: fix multiple uploaded files
5
+ [#117](https://github.com/exoego/rspec-openapi/pull/117), [#126](https://github.com/exoego/rspec-openapi/pull/126)
6
+ - feat: Add required_request_params to metadata
7
+ [#114](https://github.com/exoego/rspec-openapi/pull/114)
8
+ - bugfix(minitest):
9
+ [#128](https://github.com/exoego/rspec-openapi/pull/128)
10
+ - doc(minitest): Add instructions for minitest triggered yaml generation
11
+ [#116](https://github.com/exoego/rspec-openapi/pull/116)
12
+ - chore: Don't dump records into temporary file
13
+ [#127](https://github.com/exoego/rspec-openapi/pull/127)
14
+
15
+ ## v0.8.1
16
+ - bugfix: Empty `required` array should not be present.
17
+ [#111](https://github.com/exoego/rspec-openapi/pull/111)
18
+
1
19
  ## v0.8.0
2
20
  - Set `required` in request body and response body
3
21
  [#95](https://github.com/exoego/rspec-openapi/pull/95), [#98](https://github.com/exoego/rspec-openapi/pull/98)
data/Gemfile CHANGED
@@ -10,6 +10,8 @@ gem 'roda'
10
10
  gem 'rspec-rails'
11
11
 
12
12
  group :test do
13
+ gem 'simplecov'
14
+ gem 'simplecov-cobertura'
13
15
  gem 'super_diff'
14
16
  end
15
17
 
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # rspec-openapi ![test](https://github.com/k0kubun/rspec-openapi/workflows/test/badge.svg)
1
+ # 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)
2
2
 
3
3
  Generate OpenAPI schema from RSpec request specs.
4
4
 
@@ -308,6 +308,7 @@ Some examples' attributes can be overwritten via RSpec metadata options. Example
308
308
  summary: 'list all posts',
309
309
  description: 'list all posts ordered by pub_date',
310
310
  tags: %w[v1 posts],
311
+ required_request_params: %w[limit],
311
312
  security: [{"MyToken" => []}],
312
313
  } do
313
314
  # ...
@@ -344,6 +345,12 @@ It should work with both classes inheriting from `ActionDispatch::IntegrationTes
344
345
 
345
346
  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.
346
347
 
348
+ Run minitest with OPENAPI=1 to generate `doc/openapi.yaml` for your request specs.
349
+
350
+ ```bash
351
+ $ OPENAPI=1 bundle exec rails t
352
+ ```
353
+
347
354
  ## Links
348
355
 
349
356
  Existing RSpec plugins which have OpenAPI integration:
@@ -5,11 +5,7 @@ require 'minitest'
5
5
  module RSpec::OpenAPI::Minitest
6
6
  Example = Struct.new(:context, :description, :metadata, :file_path)
7
7
 
8
- module TestPatch
9
- def self.prepended(base)
10
- base.extend(ClassMethods)
11
- end
12
-
8
+ module RunPatch
13
9
  def run(*args)
14
10
  result = super
15
11
  if ENV['OPENAPI'] && self.class.openapi?
@@ -22,6 +18,12 @@ module RSpec::OpenAPI::Minitest
22
18
  end
23
19
  result
24
20
  end
21
+ end
22
+
23
+ module ActivateOpenApiClassMethods
24
+ def self.prepended(base)
25
+ base.extend(ClassMethods)
26
+ end
25
27
 
26
28
  module ClassMethods
27
29
  def openapi?
@@ -35,10 +37,12 @@ module RSpec::OpenAPI::Minitest
35
37
  end
36
38
  end
37
39
 
38
- Minitest::Test.prepend RSpec::OpenAPI::Minitest::TestPatch
40
+ Minitest::Test.prepend RSpec::OpenAPI::Minitest::ActivateOpenApiClassMethods
41
+
42
+ if ENV['OPENAPI']
43
+ Minitest::Test.prepend RSpec::OpenAPI::Minitest::RunPatch
39
44
 
40
- Minitest.after_run do
41
- if ENV['OPENAPI']
45
+ Minitest.after_run do
42
46
  result_recorder = RSpec::OpenAPI::ResultRecorder.new(RSpec::OpenAPI.path_records)
43
47
  result_recorder.record_results!
44
48
  puts result_record.error_message if result_recorder.errors?
@@ -6,10 +6,12 @@ RSpec::OpenAPI::Record = Struct.new(
6
6
  :path_params, # @param [Hash] - {:controller=>"v1/statuses", :action=>"create", :id=>"1"}
7
7
  :query_params, # @param [Hash] - {:query=>"string"}
8
8
  :request_params, # @param [Hash] - {:request=>"body"}
9
+ :required_request_params, # @param [Array] - ["param1", "param2"]
9
10
  :request_content_type, # @param [String] - "application/json"
10
11
  :request_headers, # @param [Array] - [["header_key1", "header_value1"], ["header_key2", "header_value2"]]
11
12
  :summary, # @param [String] - "v1/statuses #show"
12
13
  :tags, # @param [Array] - ["Status"]
14
+ :operation_id, # @param [String] - "request-1234"
13
15
  :description, # @param [String] - "returns a status"
14
16
  :security, # @param [Array] - [{securityScheme1: []}]
15
17
  :status, # @param [Integer] - 200
@@ -11,7 +11,8 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
11
11
  request, response = extract_request_response(context)
12
12
  return if request.nil?
13
13
 
14
- path, summary, tags, raw_path_params, description, security = extract_request_attributes(request, example)
14
+ path, summary, tags, operation_id, required_request_params, raw_path_params, description, security =
15
+ extract_request_attributes(request, example)
15
16
 
16
17
  request_headers, response_headers = extract_headers(request, response)
17
18
 
@@ -21,10 +22,12 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
21
22
  path_params: raw_path_params,
22
23
  query_params: request.query_parameters,
23
24
  request_params: raw_request_params(request),
25
+ required_request_params: required_request_params,
24
26
  request_content_type: request.media_type,
25
27
  request_headers: request_headers,
26
28
  summary: summary,
27
29
  tags: tags,
30
+ operation_id: operation_id,
28
31
  description: description,
29
32
  security: security,
30
33
  status: response.status,
@@ -61,13 +64,21 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
61
64
  metadata = example.metadata[:openapi] || {}
62
65
  summary = metadata[:summary]
63
66
  tags = metadata[:tags]
67
+ operation_id = metadata[:operation_id]
68
+ required_request_params = metadata[:required_request_params] || []
64
69
  security = metadata[:security]
65
70
  description = metadata[:description] || RSpec::OpenAPI.description_builder.call(example)
66
71
  raw_path_params = request.path_parameters
67
72
  path = request.path
68
73
  if rails?
69
- route = find_rails_route(request)
70
- path = route.path.spec.to_s.delete_suffix('(.:format)')
74
+ # Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
75
+ fixed_request = request.dup
76
+ fixed_request.path_info = File.join(request.script_name, request.path_info) if request.script_name.present?
77
+
78
+ route, path = find_rails_route(fixed_request)
79
+ raise "No route matched for #{fixed_request.request_method} #{fixed_request.path_info}" if route.nil?
80
+
81
+ path = path.delete_suffix('(.:format)')
71
82
  summary ||= route.requirements[:action]
72
83
  tags ||= [route.requirements[:controller]&.classify].compact
73
84
  # :controller and :action always exist. :format is added when routes is configured as such.
@@ -75,7 +86,7 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
75
86
  raw_path_params = raw_path_params.slice(*(raw_path_params.keys - %i[controller action format]))
76
87
  end
77
88
  summary ||= "#{request.method} #{path}"
78
- [path, summary, tags, raw_path_params, description, security]
89
+ [path, summary, tags, operation_id, required_request_params, raw_path_params, description, security]
79
90
  end
80
91
 
81
92
  def extract_request_response(context)
@@ -99,21 +110,18 @@ class << RSpec::OpenAPI::RecordBuilder = Object.new
99
110
  end
100
111
 
101
112
  # @param [ActionDispatch::Request] request
102
- def find_rails_route(request, app: Rails.application, fix_path: true)
103
- # Reverse the destructive modification by Rails https://github.com/rails/rails/blob/v6.0.3.4/actionpack/lib/action_dispatch/journey/router.rb#L33-L41
104
- if fix_path && !request.script_name.empty?
105
- request = request.dup
106
- request.path_info = File.join(request.script_name, request.path_info)
107
- end
108
-
113
+ def find_rails_route(request, app: Rails.application, path_prefix: '')
109
114
  app.routes.router.recognize(request) do |route|
115
+ path = route.path.spec.to_s
110
116
  if route.app.matches?(request)
111
- return find_rails_route(request, app: route.app.app, fix_path: false) if route.app.engine?
112
-
113
- return route
117
+ if route.app.engine?
118
+ route, path = find_rails_route(request, app: route.app.app, path_prefix: path)
119
+ next if route.nil?
120
+ end
121
+ return [route, path_prefix + path]
114
122
  end
115
123
  end
116
- raise "No route matched for #{request.request_method} #{request.path_info}"
124
+ nil
117
125
  end
118
126
 
119
127
  # workaround to get real request parameters
@@ -15,7 +15,6 @@ class RSpec::OpenAPI::ResultRecorder
15
15
  RSpec::OpenAPI::SchemaMerger.merge!(spec, schema)
16
16
  new_from_zero = {}
17
17
  records.each do |record|
18
- File.open('/tmp/records', 'a') { |f| f.puts record.to_yaml }
19
18
  begin
20
19
  record_schema = RSpec::OpenAPI::SchemaBuilder.build(record)
21
20
  RSpec::OpenAPI::SchemaMerger.merge!(spec, record_schema)
@@ -26,6 +25,7 @@ class RSpec::OpenAPI::ResultRecorder
26
25
  end
27
26
  RSpec::OpenAPI::SchemaCleaner.cleanup!(spec, new_from_zero)
28
27
  RSpec::OpenAPI::ComponentsUpdater.update!(spec, new_from_zero)
28
+ RSpec::OpenAPI::SchemaCleaner.cleanup_empty_required_array!(spec)
29
29
  end
30
30
  end
31
31
  end
@@ -27,6 +27,7 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
27
27
  record.http_method.downcase => {
28
28
  summary: record.summary,
29
29
  tags: record.tags,
30
+ operationId: record.operation_id,
30
31
  security: record.security,
31
32
  parameters: build_parameters(record),
32
33
  requestBody: build_request_body(record),
@@ -73,6 +74,7 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
73
74
  parameters << {
74
75
  name: build_parameter_name(key, value),
75
76
  in: 'query',
77
+ required: record.required_request_params.include?(key),
76
78
  schema: build_property(try_cast(value)),
77
79
  example: (try_cast(value) if example_enabled?),
78
80
  }.compact
@@ -191,10 +193,23 @@ class << RSpec::OpenAPI::SchemaBuilder = Object.new
191
193
 
192
194
  def adjust_params(value)
193
195
  value.each do |key, v|
194
- if v.is_a?(ActionDispatch::Http::UploadedFile)
196
+ case v
197
+ when ActionDispatch::Http::UploadedFile
195
198
  value[key] = v.original_filename
196
- elsif v.is_a?(Hash)
199
+ when Hash
197
200
  adjust_params(v)
201
+ when Array
202
+ result = v.map do |item|
203
+ case item
204
+ when ActionDispatch::Http::UploadedFile
205
+ item.original_filename
206
+ when Hash
207
+ adjust_params(item)
208
+ else
209
+ item
210
+ end
211
+ end
212
+ value[key] = result
198
213
  end
199
214
  end
200
215
  end
@@ -39,6 +39,18 @@ class << RSpec::OpenAPI::SchemaCleaner = Object.new
39
39
  base
40
40
  end
41
41
 
42
+ def cleanup_empty_required_array!(base)
43
+ paths_to_objects = [
44
+ *RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties'),
45
+ *RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'paths', 'properties'),
46
+ ]
47
+ paths_to_objects.each do |path|
48
+ parent = base.dig(*path.take(path.length - 1))
49
+ # "required" array must not be present if empty
50
+ parent.delete('required') if parent['required'] && parent['required'].empty?
51
+ end
52
+ end
53
+
42
54
  private
43
55
 
44
56
  def cleanup_array!(base, spec, selector, fields_for_identity = [])
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RSpec
4
4
  module OpenAPI
5
- VERSION = '0.8.0'
5
+ VERSION = '0.9.0'
6
6
  end
7
7
  end
data/lib/rspec/openapi.rb CHANGED
@@ -10,10 +10,8 @@ require 'rspec/openapi/schema_file'
10
10
  require 'rspec/openapi/schema_merger'
11
11
  require 'rspec/openapi/schema_cleaner'
12
12
 
13
- if ENV['OPENAPI']
14
- require 'rspec/openapi/minitest_hooks'
15
- require 'rspec/openapi/rspec_hooks'
16
- end
13
+ require 'rspec/openapi/minitest_hooks' if Object.const_defined?('Minitest')
14
+ require 'rspec/openapi/rspec_hooks' if ENV['OPENAPI'] && Object.const_defined?('RSpec')
17
15
 
18
16
  module RSpec::OpenAPI
19
17
  @path = 'doc/openapi.yaml'
data/scripts/rspec ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # (The MIT License)
5
+ # Copyright (c) 2012 Chad Humphries, David Chelimsky, Myron Marston
6
+ # Copyright (c) 2009 Chad Humphries, David Chelimsky
7
+ # Copyright (c) 2006 David Chelimsky, The RSpec Development Team
8
+ # Copyright (c) 2005 Steven Baker
9
+
10
+ require 'rspec/core'
11
+ RSpec::Core::Runner.invoke
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # (The MIT License)
5
+ # Copyright (c) 2012 Chad Humphries, David Chelimsky, Myron Marston
6
+ # Copyright (c) 2009 Chad Humphries, David Chelimsky
7
+ # Copyright (c) 2006 David Chelimsky, The RSpec Development Team
8
+ # Copyright (c) 2005 Steven Baker
9
+
10
+ # Turn on verbose to make sure we not generating any ruby warning
11
+ $VERBOSE = true
12
+
13
+ # So our "did they run the rspec command?" detection logic thinks
14
+ # that we run `rspec`.
15
+ $0 = 'rspec'
16
+
17
+ # This is necessary for when `--standalone` is being used.
18
+ $LOAD_PATH.unshift File.expand_path '../bundle', __dir__
19
+
20
+ # For the travis build we put the bundle directory up a directory
21
+ # so it can be shared among the repos for faster bundle installs.
22
+ $LOAD_PATH.unshift File.expand_path '../../bundle', __dir__
23
+
24
+ require 'bundler/setup'
25
+
26
+ # To use simplecov while running rspec-core's test suite, we must
27
+ # load simplecov _before_ loading any of rspec-core's files.
28
+ # So, this executable exists purely as a wrapper script that
29
+ # first loads simplecov, and then loads rspec.
30
+ begin
31
+ # Simplecov emits some ruby warnings when loaded, so silence them.
32
+ old_verbose = $VERBOSE
33
+ $VERBOSE = false
34
+
35
+ unless (ENV.fetch('COVERAGE', nil) && ENV['COVERAGE'].empty?) || RUBY_VERSION < '1.9.3'
36
+ require 'simplecov'
37
+
38
+ SimpleCov.start do
39
+ # Flaky :(
40
+ # enable_coverage :branch
41
+ end
42
+ end
43
+ rescue LoadError
44
+ # simplecov is not available
45
+ ensure
46
+ $VERBOSE = old_verbose
47
+ end
48
+
49
+ load File.expand_path('rspec', __dir__)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-openapi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Takashi Kokubun
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2023-04-20 00:00:00.000000000 Z
12
+ date: 2023-10-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: actionpack
@@ -54,6 +54,7 @@ files:
54
54
  - ".rspec"
55
55
  - ".rubocop.yml"
56
56
  - ".rubocop_todo.yml"
57
+ - ".simplecov_spawn.rb"
57
58
  - CHANGELOG.md
58
59
  - Gemfile
59
60
  - LICENSE.txt
@@ -76,6 +77,8 @@ files:
76
77
  - lib/rspec/openapi/schema_merger.rb
77
78
  - lib/rspec/openapi/version.rb
78
79
  - rspec-openapi.gemspec
80
+ - scripts/rspec
81
+ - scripts/rspec_with_simplecov
79
82
  - test.png
80
83
  homepage: https://github.com/exoego/rspec-openapi
81
84
  licenses: