rspec-openapi 0.8.1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +8 -0
- data/.rubocop.yml +2 -0
- data/.simplecov_spawn.rb +15 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +2 -0
- data/README.md +8 -1
- data/lib/rspec/openapi/minitest_hooks.rb +12 -8
- data/lib/rspec/openapi/record.rb +2 -0
- data/lib/rspec/openapi/record_builder.rb +23 -15
- data/lib/rspec/openapi/result_recorder.rb +0 -1
- data/lib/rspec/openapi/schema_builder.rb +17 -2
- data/lib/rspec/openapi/schema_cleaner.rb +1 -1
- data/lib/rspec/openapi/version.rb +1 -1
- data/lib/rspec/openapi.rb +2 -4
- data/scripts/rspec +11 -0
- data/scripts/rspec_with_simplecov +49 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d55c7178770a58a980d06669adfab735ba84b51a283302dd8e14e91af4b7b4e9
|
4
|
+
data.tar.gz: 7b0030924262f8a1cd7027df612315b8d6e3fb89e91f9b4e57c3ec83f7e9bfd6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4cf846ca12d2062ae17c0b5d1829a7887c206425330c015f5fdde310c59678cb802b4cadb60f63c1ca6d55063c3d4f1cc2ac7813db10c6d8611a0fa3af975192
|
7
|
+
data.tar.gz: 7501fac5fbfd8b0c4cec6cb196feb9ff09a640c4aff7165a93a6880eebea194d5c30674eefadb53987538ebed913d8d43ab625f1a7c8a3a12ad76ba15b459687
|
data/.github/workflows/test.yml
CHANGED
@@ -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
data/.simplecov_spawn.rb
ADDED
@@ -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,17 @@
|
|
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
|
+
|
1
15
|
## v0.8.1
|
2
16
|
- bugfix: Empty `required` array should not be present.
|
3
17
|
[#111](https://github.com/exoego/rspec-openapi/pull/111)
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# rspec-openapi ![test](https://github.com/
|
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
|
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::
|
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?
|
data/lib/rspec/openapi/record.rb
CHANGED
@@ -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 =
|
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
|
-
|
70
|
-
|
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,
|
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
|
-
|
112
|
-
|
113
|
-
|
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
|
-
|
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)
|
@@ -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
|
-
|
196
|
+
case v
|
197
|
+
when ActionDispatch::Http::UploadedFile
|
195
198
|
value[key] = v.original_filename
|
196
|
-
|
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
|
@@ -47,7 +47,7 @@ class << RSpec::OpenAPI::SchemaCleaner = Object.new
|
|
47
47
|
paths_to_objects.each do |path|
|
48
48
|
parent = base.dig(*path.take(path.length - 1))
|
49
49
|
# "required" array must not be present if empty
|
50
|
-
parent.delete('required') if parent['required'].empty?
|
50
|
+
parent.delete('required') if parent['required'] && parent['required'].empty?
|
51
51
|
end
|
52
52
|
end
|
53
53
|
|
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
|
14
|
-
|
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.
|
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-
|
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:
|