rspec-rails-api 0.5.0 → 0.6.0
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 +4 -4
- data/CHANGELOG.md +112 -29
- data/Gemfile.lock +1 -1
- data/README.md +207 -4
- data/lib/rspec/rails/api/dsl/example.rb +35 -16
- data/lib/rspec/rails/api/dsl/example_group.rb +11 -1
- data/lib/rspec/rails/api/entity_config.rb +2 -2
- data/lib/rspec/rails/api/field_config.rb +4 -1
- data/lib/rspec/rails/api/metadata.rb +46 -18
- data/lib/rspec/rails/api/open_api_renderer.rb +141 -36
- data/lib/rspec/rails/api/version.rb +1 -1
- data/lib/rspec_rails_api.rb +2 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: aa6db3329e09e4356d205cf49f100e11132a3e8a8fcb681ef8ede5e22ba470dd
|
4
|
+
data.tar.gz: e25b15faecc41214f92bb089f8fc9e28702f9c5ad2072daf4014251d30f23291
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2c947cf296de131ece545c7a21ed093d79de1e1fd5f57af53c58985eab815c781ce4dcbba7f7269c679dbf8155d4eabf0ae5035caa70bbd88090650630ed8e11
|
7
|
+
data.tar.gz: b0c4d8bef1c715b0ea369b5f7a659e8fa69344735af2a7735a51a1cb755c3fc3b642597c47a133be9568c0361d8565725ed803d2b0dd530050d1e286795ee911
|
data/CHANGELOG.md
CHANGED
@@ -1,22 +1,74 @@
|
|
1
1
|
# Change Log
|
2
2
|
All notable changes to this project will be documented in this file.
|
3
3
|
|
4
|
-
|
4
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
5
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
5
6
|
|
6
|
-
|
7
|
+
<!--
|
8
|
+
Quick remainder of the possible sections:
|
9
|
+
-----------------------------------------
|
10
|
+
Added, for new features.
|
11
|
+
Changed, for changes in existing functionality.
|
12
|
+
Deprecated, for soon-to-be removed features.
|
13
|
+
Removed, for now removed features.
|
14
|
+
Fixed, for any bug fixes.
|
15
|
+
Security, in case of vulnerabilities.
|
16
|
+
Maintenance, in case of rework, dependencies change
|
17
|
+
-->
|
18
|
+
|
19
|
+
## [Unreleased]
|
20
|
+
|
21
|
+
## [0.6.0] - 2023-07-17
|
22
|
+
|
23
|
+
### Added
|
24
|
+
|
25
|
+
- Declaring the same resource multiple times is now possible
|
26
|
+
- Add a simple support for security schemes
|
27
|
+
- Add support for global entities declarations. Keep things DRYer :)
|
28
|
+
- `test_response_of`: add `ignore_content_type` flag to ignore response's content type
|
29
|
+
- `test_response_of`: add `ignore_response` flag to ignore tests on response
|
30
|
+
- Renderer: Add simple support for redactable content. It will replace content in responses by something else. For now,
|
31
|
+
sub-entities are not redacted.
|
32
|
+
- Support for `file` type. When declaring a field with the `file` type, it will change the request content-type to
|
33
|
+
`multipart/form-data` automatically.
|
34
|
+
|
35
|
+
### Changed
|
36
|
+
|
37
|
+
- Generated JSON files will always be prettified to ease changes reviews when they are versioned
|
38
|
+
- When an unexpected 422 error happens, also display its content in error message
|
39
|
+
- [BREAKING] It is now impossible to declare the same entity twice. To remediate, rename your declared entities and/or
|
40
|
+
use global declarations. Check [README](./README.md) for example.
|
41
|
+
- Renderer: Use response data as-is when it's not valid JSON
|
42
|
+
|
43
|
+
### Fixed
|
44
|
+
|
45
|
+
- Strip generated descriptions and summaries
|
46
|
+
- Primitives are no more referenced in schemas for "expect_one" types
|
47
|
+
- Compare type `:float` against `Numeric` class
|
48
|
+
- Entities: Raise error when using `:array` type with `attributes` property and `:object` type with `of` property
|
49
|
+
- Rendering of sub-references now use the correct reference
|
50
|
+
- Metadata: Add missing type on array parameters
|
51
|
+
- Don't transform parameters into JSON when making `get` requests
|
52
|
+
- Operation IDs don't use summaries for uniqueness
|
53
|
+
- Downcase response content type before comparison
|
54
|
+
- Type format is now added on request parameters if applicable
|
55
|
+
|
56
|
+
## [0.5.0] - 2023-01-02
|
57
|
+
|
58
|
+
### Changed
|
7
59
|
|
8
60
|
- Improved error messages
|
9
61
|
- Improved usage of primitive types: use `:string` instead of `:type_string`, and more generally, remove the `type_`
|
10
62
|
prefix
|
63
|
+
|
64
|
+
### Fixed
|
65
|
+
-
|
11
66
|
- Fixed an error when an object is defined with an attribute named `type`.
|
12
67
|
|
13
|
-
## 0.4.0 - 2021-12-19
|
68
|
+
## [0.4.0] - 2021-12-19
|
69
|
+
|
70
|
+
### Added
|
14
71
|
|
15
|
-
- All parameters attributes are considered required unless specified
|
16
|
-
- Fix object `attributes` key in spec and documentation.
|
17
|
-
When defining object attributes, documentation and tests used `properties` key
|
18
|
-
while the code was waiting for an `attributes` key. The later makes more sense
|
19
|
-
so the spec and documentation were fixed.
|
20
72
|
- Check for arrays of primitives is now a thing:
|
21
73
|
```rb
|
22
74
|
# In entities, when attribute is an array of primitives
|
@@ -28,15 +80,19 @@ All notable changes to this project will be documented in this file.
|
|
28
80
|
#...
|
29
81
|
end
|
30
82
|
```
|
31
|
-
- RSpec metadata is now stored in `rra` instead of `rrad` (the gem's first name
|
32
|
-
was RSpec Rails API Doc at the time). Update RSpec configuration accordingly.
|
33
83
|
- OpenApi:
|
34
84
|
- Responses now includes the schema
|
35
85
|
- `on_xxx` methods second parameter is now used as summary instead of description.
|
36
86
|
Description can be defined on the third parameter.
|
87
|
+
|
88
|
+
### Changed
|
89
|
+
|
90
|
+
- All parameters attributes are considered required unless specified
|
91
|
+
- RSpec metadata is now stored in `rra` instead of `rrad` (the gem's first name
|
92
|
+
was RSpec Rails API Doc at the time). Update RSpec configuration accordingly.
|
37
93
|
- DSL changes:
|
38
94
|
- `visit` is renamed to `test_response_of`
|
39
|
-
- Support for `doc_only` is removed
|
95
|
+
- Support for `doc_only` is removed
|
40
96
|
- Response expectations _should_ now be declared with `for_code`, and should be
|
41
97
|
removed from example bodies:
|
42
98
|
```rb
|
@@ -52,44 +108,71 @@ All notable changes to this project will be documented in this file.
|
|
52
108
|
end
|
53
109
|
```
|
54
110
|
|
111
|
+
### Fixed
|
112
|
+
|
113
|
+
- Fix object `attributes` key in spec and documentation.
|
114
|
+
When defining object attributes, documentation and tests used `properties` key
|
115
|
+
while the code was waiting for an `attributes` key. The later makes more sense
|
116
|
+
so the spec and documentation were fixed.
|
117
|
+
|
118
|
+
## [0.3.4] - 2021-10-20
|
119
|
+
|
120
|
+
### Added
|
55
121
|
|
56
|
-
## 0.3.4 - 2021-10-20
|
57
122
|
- Add the "required" attribute in parameters
|
58
123
|
|
59
|
-
## 0.3.3 - 2021-06-02
|
124
|
+
## [0.3.3] - 2021-06-02
|
125
|
+
|
126
|
+
### Fixed
|
127
|
+
|
60
128
|
- Fix correct types on request parameters
|
61
129
|
|
62
|
-
## 0.3.2 - 2021-03-09
|
63
|
-
|
130
|
+
## [0.3.2] - 2021-03-09
|
131
|
+
|
132
|
+
### Changed
|
133
|
+
|
64
134
|
- Render examples results as YAML/JSON objects instead of text blocks.
|
65
135
|
|
66
|
-
|
136
|
+
### Fixed
|
137
|
+
|
138
|
+
- Fix YAML rendering (ruby objects were sometimes rendered in documentation)
|
139
|
+
|
140
|
+
## [0.3.1] - 2020-04-09
|
141
|
+
|
142
|
+
### Added
|
143
|
+
|
67
144
|
- Add support for "test only" examples, allowing to write examples without documentation.
|
68
145
|
|
69
|
-
## 0.3.0 - 2019-12-26
|
146
|
+
## [0.3.0] - 2019-12-26
|
70
147
|
|
71
148
|
### Changed
|
149
|
+
|
72
150
|
- Rails 6 support, deprecated methods from Rails 5 are not supported. Use version `0.2.3`
|
73
151
|
of this gem if your application is still on 5.
|
74
152
|
|
75
153
|
## 0.2.3 - 2019-12-04
|
76
154
|
|
77
|
-
###
|
155
|
+
### Added
|
78
156
|
|
79
157
|
- Generated Swagger file now use the payloads of POST/PATCH/PUT requests.
|
80
|
-
- Minimum Ruby version is now specified: Ruby 2.3.3.
|
81
158
|
|
82
|
-
|
159
|
+
### Changed
|
160
|
+
|
161
|
+
- Minimum Ruby version is now specified: Ruby 2.3.3.
|
83
162
|
|
84
|
-
|
85
|
-
same one, with a version bump_
|
163
|
+
## [0.2.2] - 2019-11-03
|
86
164
|
|
87
165
|
### Changed
|
88
166
|
|
89
167
|
- `for_code` method now have its `description` optional. If none is provided,
|
90
168
|
the description will be set from the status code.
|
91
169
|
|
92
|
-
## 0.2.
|
170
|
+
## [0.2.1] - 2019-11-03 [YANKED]
|
171
|
+
|
172
|
+
_Version 0.2.1 was released and yanked by mistake. Version 0.2.2 is the exact
|
173
|
+
same one, with a version bump_
|
174
|
+
|
175
|
+
## [0.2.0] - 2019-11-02
|
93
176
|
|
94
177
|
### Added
|
95
178
|
|
@@ -108,35 +191,35 @@ of the fixtures.
|
|
108
191
|
the existing calls accordingly. To use params defined with `parameters`, use the
|
109
192
|
`defined` option: `request_params defined: :common_form_params`
|
110
193
|
|
111
|
-
## 0.1.5 - 2019-10-31
|
194
|
+
## [0.1.5] - 2019-10-31
|
112
195
|
|
113
196
|
### Fixed
|
114
197
|
|
115
198
|
- Fixed issue with POST/PUT/DELETE requests with no `request_params`
|
116
199
|
- Improved documentation (integration with Devise, typos and mistakes)
|
117
200
|
|
118
|
-
## 0.1.4 - 2019-10-24
|
201
|
+
## [0.1.4] - 2019-10-24
|
119
202
|
|
120
203
|
### Added
|
121
204
|
|
122
205
|
- Added support for arrays of objects in request parameters
|
123
206
|
|
124
|
-
## 0.1.3 - 2019-10-23
|
207
|
+
## [0.1.3] - 2019-10-23
|
125
208
|
|
126
209
|
### Added
|
127
210
|
|
128
211
|
- Added ability to document API descriptions, servers, etc... from the RSpec helper files
|
129
212
|
|
130
|
-
## 0.1.2 - 2019-10-22
|
213
|
+
## [0.1.2] - 2019-10-22
|
131
214
|
|
132
215
|
### Added
|
133
216
|
|
134
217
|
- Added `item` property for arrays descriptions
|
135
218
|
|
136
|
-
## 0.1.1 - 2019-10-22
|
219
|
+
## [0.1.1] - 2019-10-22
|
137
220
|
|
138
221
|
- Added support for custom headers in request examples (useful for `visit` method)
|
139
222
|
|
140
|
-
## 0.1.0 - 2019-10-21
|
223
|
+
## [0.1.0] - 2019-10-21
|
141
224
|
|
142
225
|
Initial release
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -72,7 +72,101 @@ end
|
|
72
72
|
|
73
73
|
## Configuration
|
74
74
|
|
75
|
-
**TODO: This section is incomplete and the gem has no generator yet
|
75
|
+
**TODO: This section is incomplete and the gem has no generator yet.**
|
76
|
+
|
77
|
+
Here is an example configuration:
|
78
|
+
|
79
|
+
```rb
|
80
|
+
# spec/rails_helper.rb
|
81
|
+
require 'spec/support/rspec_rails_api'
|
82
|
+
```
|
83
|
+
|
84
|
+
```rb
|
85
|
+
# spec/support/acceptance_entities.rb
|
86
|
+
|
87
|
+
# This file contains common object definitions
|
88
|
+
{
|
89
|
+
error: {
|
90
|
+
error: { type: :string, description: "Error message" }
|
91
|
+
},
|
92
|
+
form_error: {
|
93
|
+
title: { type: :array, required: false, description: "Title errors", of: :string }
|
94
|
+
},
|
95
|
+
}.each do |name, attributes|
|
96
|
+
RSpec::Rails::Api::Metadata.add_entity name, attributes
|
97
|
+
end
|
98
|
+
```
|
99
|
+
|
100
|
+
```rb
|
101
|
+
# spec/support/rspec_rails_api.rb
|
102
|
+
require 'rspec_rails_api'
|
103
|
+
require 'support/acceptance_entities'
|
104
|
+
|
105
|
+
# Include DSL in RSpec
|
106
|
+
RSpec.configure do |config|
|
107
|
+
config.include RSpec::Rails::Api::DSL::Example
|
108
|
+
end
|
109
|
+
|
110
|
+
# Initialize and configure the renderer
|
111
|
+
renderer = RSpec::Rails::Api::OpenApiRenderer.new
|
112
|
+
# Server URL for quick reference
|
113
|
+
server_url = 'https://example.com'
|
114
|
+
|
115
|
+
# Options here should be present for a valid OpenAPI file
|
116
|
+
renderer.api_title = 'MyProject API'
|
117
|
+
renderer.api_version = '1'
|
118
|
+
|
119
|
+
# Options below are optional
|
120
|
+
#
|
121
|
+
# API description. Markdown supported
|
122
|
+
# renderer.api_description = 'Manage data on MyProject'
|
123
|
+
#
|
124
|
+
# List of servers, to live-test the documentation
|
125
|
+
# renderer.api_servers = [{ url: server_url }, { url: 'http://localhost:3000' }]
|
126
|
+
#
|
127
|
+
# Link to the API terms of service, if any
|
128
|
+
# renderer.api_tos = 'http://example.com/tos.html'
|
129
|
+
#
|
130
|
+
# Contact information
|
131
|
+
# renderer.api_contact = { name: 'Admin', email: 'admin@example.com', url: 'http://example.com/contact' }
|
132
|
+
#
|
133
|
+
# API license information
|
134
|
+
# renderer.api_license = { name: 'Apache', url: 'https://opensource.org/licenses/Apache-2.0' }
|
135
|
+
#
|
136
|
+
# Possible security schemes
|
137
|
+
# renderer.add_security_scheme :pkce_code_grant, 'PKCE code grant',
|
138
|
+
# type: 'oauth2',
|
139
|
+
# flows: {
|
140
|
+
# implicit: {
|
141
|
+
# authorizationUrl: "#{server_url}/oauth/authorize",
|
142
|
+
# scopes: { read: 'will read data on your behalf', write: 'will write data on your behalf' }
|
143
|
+
# }
|
144
|
+
# }
|
145
|
+
# renderer.add_security_scheme :bearer, 'Bearer token',
|
146
|
+
# type: 'http',
|
147
|
+
# scheme: 'bearer'
|
148
|
+
#
|
149
|
+
# Declare keys whose values should be filtered in responses.
|
150
|
+
# renderer.redact_responses entity_name: { key: 'REDACTED' },
|
151
|
+
# other_entity: { other_key: ['REDACTED'] }
|
152
|
+
|
153
|
+
|
154
|
+
# We need to merge each context metadata so we can reference to them to build the final file
|
155
|
+
RSpec.configuration.after(:context, type: :acceptance) do |context|
|
156
|
+
renderer.merge_context context.class.metadata[:rra].to_h
|
157
|
+
# During development of rspec_rails_api, you may want to dump raw metadata to a file
|
158
|
+
renderer.merge_context context.class.metadata[:rra].to_h, dump_metadata: true
|
159
|
+
end
|
160
|
+
|
161
|
+
# Skip this block if you don't need the OpenAPI documentation file and only have your responses tested
|
162
|
+
RSpec.configuration.after(:suite) do
|
163
|
+
renderer.write_files Rails.root.join('public/swagger') # Write both YAML and prettified JSON files
|
164
|
+
# or
|
165
|
+
renderer.write_files Rails.root.join('public/swagger'), only: [:json] # Prettified JSON only
|
166
|
+
# or
|
167
|
+
renderer.write_files Rails.root.join('public/swagger'), only: [:yaml] # YAML only
|
168
|
+
end
|
169
|
+
```
|
76
170
|
|
77
171
|
### Integration with Devise
|
78
172
|
|
@@ -124,7 +218,7 @@ end
|
|
124
218
|
# In examples
|
125
219
|
#...
|
126
220
|
for_code 200, 'Success' do |url|
|
127
|
-
|
221
|
+
sign_in #...
|
128
222
|
test_response_of url
|
129
223
|
|
130
224
|
#...
|
@@ -149,7 +243,7 @@ The idea is to have a simple DSL, and declare things like:
|
|
149
243
|
**spec/acceptance/users_spec.rb**
|
150
244
|
|
151
245
|
```ruby
|
152
|
-
require '
|
246
|
+
require 'rails_helper'
|
153
247
|
|
154
248
|
RSpec.describe 'Users', type: :acceptance do
|
155
249
|
resource 'Users', 'Manage users'
|
@@ -186,6 +280,78 @@ RSpec.describe 'Users', type: :acceptance do
|
|
186
280
|
end
|
187
281
|
```
|
188
282
|
|
283
|
+
### Entity declarations
|
284
|
+
|
285
|
+
You can declare entities locally (in every spec files), but sometimes you will need to use/reference the same entity
|
286
|
+
in multiple spec files (e.g.: an error message). In that case, you can create _global_ entities in separate files, and they
|
287
|
+
will be picked-up when needed.
|
288
|
+
|
289
|
+
Example of a local entity:
|
290
|
+
|
291
|
+
```rb
|
292
|
+
# spec/acceptance/api/users_acceptance_spec.rb
|
293
|
+
require 'rails_helper'
|
294
|
+
|
295
|
+
RSpec.describe 'Users', type: :acceptance do
|
296
|
+
resource 'Users', 'Manage users'
|
297
|
+
|
298
|
+
# This is a local entity
|
299
|
+
entity :user,
|
300
|
+
id: { type: :integer, description: 'The id' },
|
301
|
+
email: { type: :string, description: 'The name' },
|
302
|
+
role: { type: :string, description: 'The name' },
|
303
|
+
created_at: { type: :datetime, description: 'Creation date' },
|
304
|
+
updated_at: { type: :datetime, description: 'Modification date' },
|
305
|
+
url: { type: :string, description: 'URL to this category' }
|
306
|
+
|
307
|
+
on_get '/api/users/', 'Users list' do
|
308
|
+
for_code 200, 'Success response', expect_many: :user do |url|
|
309
|
+
test_response_of url
|
310
|
+
end
|
311
|
+
end
|
312
|
+
|
313
|
+
#...
|
314
|
+
end
|
315
|
+
```
|
316
|
+
|
317
|
+
Defining global entities:
|
318
|
+
|
319
|
+
```rb
|
320
|
+
# spec/support/entities/user.rb
|
321
|
+
# This file should be required at some point in the "rails_helper" or "acceptance_helper"
|
322
|
+
|
323
|
+
require 'rspec/rails/api/metadata'
|
324
|
+
|
325
|
+
RSpec::Rails::Api::Metadata.add_entity :user,
|
326
|
+
id: { type: :integer, description: 'The id' },
|
327
|
+
email: { type: :string, description: 'The name' },
|
328
|
+
role: { type: :string, description: 'The name' },
|
329
|
+
created_at: { type: :datetime, description: 'Creation date' },
|
330
|
+
updated_at: { type: :datetime, description: 'Modification date' },
|
331
|
+
url: { type: :string, description: 'URL to this category' }
|
332
|
+
|
333
|
+
```
|
334
|
+
|
335
|
+
Organization of the global entities declaration is up to you.
|
336
|
+
|
337
|
+
```rb
|
338
|
+
# spec/acceptance/api/users_acceptance_spec.rb
|
339
|
+
require 'rails_helper'
|
340
|
+
|
341
|
+
RSpec.describe 'Users', type: :acceptance do
|
342
|
+
resource 'Users', 'Manage users'
|
343
|
+
|
344
|
+
on_get '/api/users/', 'Users list' do
|
345
|
+
# "user" will use the global user entity
|
346
|
+
for_code 200, 'Success response', expect_many: :user do |url|
|
347
|
+
test_response_of url
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
#...
|
352
|
+
end
|
353
|
+
```
|
354
|
+
|
189
355
|
### DSL
|
190
356
|
|
191
357
|
#### Example groups
|
@@ -197,6 +363,23 @@ Starts a resource description.
|
|
197
363
|
- It must be called before any other documentation calls.
|
198
364
|
- It should be in the first `describe block`
|
199
365
|
|
366
|
+
A resource may be completed across multiple spec files:
|
367
|
+
|
368
|
+
```rb
|
369
|
+
# an_acceptance_spec.rb
|
370
|
+
RSpec.describe 'Something', type: :acceptance do
|
371
|
+
resource 'User', 'Manage users'
|
372
|
+
end
|
373
|
+
|
374
|
+
# another_acceptance_spec.rb
|
375
|
+
RSpec.describe 'Something else', type: :acceptance do
|
376
|
+
resource 'User', 'Another description'
|
377
|
+
end
|
378
|
+
|
379
|
+
```
|
380
|
+
|
381
|
+
The first evaluated `resource` statement will be used as description; all the tests in both files will complete it.
|
382
|
+
|
200
383
|
##### `entity(type, fields)`
|
201
384
|
|
202
385
|
Describes an entity for the documentation. The type is only a reference,
|
@@ -423,11 +606,30 @@ Once again, you have to pass an argument to the block if you use
|
|
423
606
|
# ...
|
424
607
|
```
|
425
608
|
|
609
|
+
##### `requires_security(scheme_references)`
|
610
|
+
|
611
|
+
Specifies the valid security schemes to use for this request. Security schemes are declared at the renderer level
|
612
|
+
(see [the configuration example](#Configuration)).
|
613
|
+
|
614
|
+
```rb
|
615
|
+
# Given a previously :basic scheme
|
616
|
+
|
617
|
+
# ...
|
618
|
+
on_get '/some/path' do
|
619
|
+
require_security :basic, :implicit
|
620
|
+
|
621
|
+
for_code 200 do |url|
|
622
|
+
#...
|
623
|
+
end
|
624
|
+
end
|
625
|
+
# ...
|
626
|
+
```
|
627
|
+
|
426
628
|
#### Examples
|
427
629
|
|
428
630
|
Example methods are available in `for_code` blocks
|
429
631
|
|
430
|
-
##### `test_response_of(example, path_params: {}, payload: {}, headers: {})`
|
632
|
+
##### `test_response_of(example, path_params: {}, payload: {}, headers: {}, ignore_content_type: false)`
|
431
633
|
|
432
634
|
Visits the described URL and:
|
433
635
|
|
@@ -441,6 +643,7 @@ Visits the described URL and:
|
|
441
643
|
- `payload`: a hash of values to send. Ignored for GET and DELETE
|
442
644
|
requests
|
443
645
|
- `headers`: a hash of custom headers.
|
646
|
+
- `ignore_content_type`: whether to ignore response's content-type. By default, checks for a JSON response
|
444
647
|
|
445
648
|
```ruby
|
446
649
|
for_code 200, 'Success' do |url|
|
@@ -9,14 +9,15 @@ module RSpec
|
|
9
9
|
##
|
10
10
|
# Visits the current example and tests the response
|
11
11
|
#
|
12
|
-
# @param example
|
13
|
-
# @param path_params
|
14
|
-
# @param payload
|
15
|
-
# @param headers
|
12
|
+
# @param example [Hash] Current example
|
13
|
+
# @param path_params [Hash] Path parameters definition
|
14
|
+
# @param payload [Hash] Request body
|
15
|
+
# @param headers [Hash] Custom headers
|
16
|
+
# @param ignore_content_type [Boolean] Whether to ignore the response's content-type for this response only
|
16
17
|
#
|
17
18
|
# @return [void]
|
18
|
-
def test_response_of(example, path_params: {}, payload: {}, headers: {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
19
|
-
raise 'Missing context. Call
|
19
|
+
def test_response_of(example, path_params: {}, payload: {}, headers: {}, ignore_content_type: false, ignore_response: false) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/ParameterLists, Layout/LineLength
|
20
|
+
raise 'Missing context. Call "test_response_of" within a "for_code" block.' unless example
|
20
21
|
|
21
22
|
status_code = prepare_status_code example.class.description
|
22
23
|
|
@@ -25,10 +26,10 @@ module RSpec
|
|
25
26
|
|
26
27
|
send(request_params[:action],
|
27
28
|
request_params[:url],
|
28
|
-
params: request_params[:params]
|
29
|
+
params: request_params[:params],
|
29
30
|
headers: request_params[:headers])
|
30
31
|
|
31
|
-
check_response(response, status_code)
|
32
|
+
check_response(response, status_code, ignore_content_type: ignore_content_type) unless ignore_response
|
32
33
|
|
33
34
|
return if example.class.description.match?(/-> test (\d+)(.*)/)
|
34
35
|
|
@@ -38,7 +39,10 @@ module RSpec
|
|
38
39
|
private
|
39
40
|
|
40
41
|
##
|
41
|
-
# Searches for a defined entity in metadata
|
42
|
+
# Searches for a defined entity in example metadata or global entities
|
43
|
+
#
|
44
|
+
# If an entity needs expansion (e.g.: with an attribute like "type: :array, of: :something"), it will use the
|
45
|
+
# scope where the entity was found: global entities or example metadata.
|
42
46
|
#
|
43
47
|
# @param entity [Symbol] Entity reference
|
44
48
|
#
|
@@ -49,10 +53,10 @@ module RSpec
|
|
49
53
|
current_resource = rra_metadata.current_resource
|
50
54
|
raise '@current_resource is unset' unless current_resource
|
51
55
|
|
52
|
-
|
53
|
-
raise "
|
56
|
+
definition = RSpec::Rails::Api::Metadata.entities[entity.to_sym]
|
57
|
+
raise "Entity '#{entity}' was never defined (globally or in '#{current_resource}')" unless definition
|
54
58
|
|
55
|
-
|
59
|
+
definition.expand_with(RSpec::Rails::Api::Metadata.entities)
|
56
60
|
end
|
57
61
|
|
58
62
|
##
|
@@ -60,9 +64,21 @@ module RSpec
|
|
60
64
|
#
|
61
65
|
# @param response [ActionDispatch::TestResponse] The response
|
62
66
|
# @param expected_code [Number] Code to test for
|
63
|
-
|
64
|
-
|
65
|
-
|
67
|
+
# @param ignore_content_type [Boolean] Whether to ignore the response's content-type for
|
68
|
+
# this response only
|
69
|
+
def check_response(response, expected_code, ignore_content_type: false) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity
|
70
|
+
code_error_message = if response.status != expected_code && response.status == 422
|
71
|
+
<<~TXT
|
72
|
+
expected: #{expected_code}
|
73
|
+
got: #{response.status}
|
74
|
+
response: #{response.body}
|
75
|
+
TXT
|
76
|
+
end
|
77
|
+
|
78
|
+
expect(response.status).to eq(expected_code), code_error_message
|
79
|
+
if expected_code != 204 && !ignore_content_type
|
80
|
+
expect(response.headers['Content-Type'].downcase).to eq 'application/json; charset=utf-8'
|
81
|
+
end
|
66
82
|
expectations = rra_current_example[:expectations]
|
67
83
|
expect(response).to have_many defined(expectations[:many]) if expectations[:many]
|
68
84
|
expect(response).to have_one defined(expectations[:one]) if expectations[:one]
|
@@ -98,9 +114,12 @@ module RSpec
|
|
98
114
|
# @return [Hash] Options for the request
|
99
115
|
def prepare_request_params(description, request_params = {}, payload = {}, request_headers = {})
|
100
116
|
example_params = description.split
|
117
|
+
verb = example_params[0].downcase
|
118
|
+
|
119
|
+
payload = payload.to_json if verb != 'get'
|
101
120
|
|
102
121
|
{
|
103
|
-
action:
|
122
|
+
action: verb,
|
104
123
|
url: prepare_request_url(example_params[1], request_params),
|
105
124
|
example_url: example_params[1],
|
106
125
|
params: payload,
|
@@ -28,7 +28,7 @@ module RSpec
|
|
28
28
|
#
|
29
29
|
# @return [void]
|
30
30
|
def entity(type, fields)
|
31
|
-
|
31
|
+
RSpec::Rails::Api::Metadata.add_entity type, fields
|
32
32
|
end
|
33
33
|
|
34
34
|
##
|
@@ -78,6 +78,16 @@ module RSpec
|
|
78
78
|
metadata[:rra].add_request_params attributes
|
79
79
|
end
|
80
80
|
|
81
|
+
##
|
82
|
+
# Declares security schemes valid for this path. It won't be enforced during testing but will complete the
|
83
|
+
# documentation. When the reference does not exist, an exception will be thrown _during_ render, not before.
|
84
|
+
#
|
85
|
+
# @param scheme_references [Array<Symbol>] References to a security scheme defined with the renderer's
|
86
|
+
# `add_security_scheme`.
|
87
|
+
def requires_security(*scheme_references)
|
88
|
+
metadata[:rra].add_security_references(*scheme_references)
|
89
|
+
end
|
90
|
+
|
81
91
|
##
|
82
92
|
# Defines a GET action
|
83
93
|
#
|
@@ -11,10 +11,13 @@ module RSpec
|
|
11
11
|
class FieldConfig
|
12
12
|
attr_accessor :required, :type, :attributes, :description
|
13
13
|
|
14
|
-
def initialize(type:, description:, required: true, attributes: nil, of: nil)
|
14
|
+
def initialize(type:, description:, required: true, attributes: nil, of: nil) # rubocop:disable Metrics/CyclomaticComplexity
|
15
15
|
@required = required
|
16
16
|
@description = description
|
17
|
+
|
17
18
|
raise "Field type not allowed: '#{type}'" unless Validator.valid_type?(type)
|
19
|
+
raise "Don't use 'of' on non-arrays" if of && type != :array
|
20
|
+
raise "Don't use 'attributes' on non-objects" if attributes && type != :object
|
18
21
|
|
19
22
|
define_attributes attributes if type == :object
|
20
23
|
define_attributes of if type == :array
|
@@ -10,11 +10,10 @@ module RSpec
|
|
10
10
|
module Api
|
11
11
|
# Handles contexts and examples metadata.
|
12
12
|
class Metadata # rubocop:disable Metrics/ClassLength
|
13
|
-
attr_reader :
|
13
|
+
attr_reader :resources, :parameters, :current_resource, :current_url, :current_method, :current_code
|
14
14
|
|
15
15
|
def initialize
|
16
16
|
@resources = {}
|
17
|
-
@entities = {}
|
18
17
|
@parameters = {}
|
19
18
|
# Only used when building metadata during RSpec boot
|
20
19
|
@current_resource = nil
|
@@ -23,6 +22,33 @@ module RSpec
|
|
23
22
|
@current_code = nil
|
24
23
|
end
|
25
24
|
|
25
|
+
class << self
|
26
|
+
##
|
27
|
+
# Define an entity globally.
|
28
|
+
#
|
29
|
+
# Global entities will be available within the specs, but if they are re-declared locally, the local variant
|
30
|
+
# will be used.
|
31
|
+
#
|
32
|
+
# @param name [Symbol] Entity name
|
33
|
+
# @param fields [Hash] Fields definitions
|
34
|
+
#
|
35
|
+
# @return [void]
|
36
|
+
def add_entity(name, fields)
|
37
|
+
@entities ||= {}
|
38
|
+
raise "#{name} is already declared" if @entities.key? name
|
39
|
+
|
40
|
+
@entities[name] = EntityConfig.new fields
|
41
|
+
end
|
42
|
+
|
43
|
+
def entities
|
44
|
+
@entities || {}
|
45
|
+
end
|
46
|
+
|
47
|
+
def reset
|
48
|
+
@entities = {}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
26
52
|
##
|
27
53
|
# Adds a resource to metadata
|
28
54
|
#
|
@@ -31,24 +57,10 @@ module RSpec
|
|
31
57
|
#
|
32
58
|
# @return [void]
|
33
59
|
def add_resource(name, description)
|
34
|
-
@resources[name.to_sym]
|
60
|
+
@resources[name.to_sym] ||= { description: description, paths: {} }
|
35
61
|
@current_resource = name.to_sym
|
36
62
|
end
|
37
63
|
|
38
|
-
##
|
39
|
-
# Adds an entity definition
|
40
|
-
#
|
41
|
-
# @param name [Symbol] Entity name
|
42
|
-
# @param fields [Hash] Fields definitions
|
43
|
-
#
|
44
|
-
#
|
45
|
-
# @return [void]
|
46
|
-
def add_entity(name, fields)
|
47
|
-
Utils.deep_set(@resources,
|
48
|
-
[@current_resource, 'entities', name],
|
49
|
-
EntityConfig.new(fields))
|
50
|
-
end
|
51
|
-
|
52
64
|
##
|
53
65
|
# Adds a parameter definition
|
54
66
|
#
|
@@ -61,6 +73,7 @@ module RSpec
|
|
61
73
|
|
62
74
|
fields.each_value do |field|
|
63
75
|
field[:required] = true unless field[:required] == false
|
76
|
+
field[:schema] = { type: field[:of] } if field[:type] == :array && PRIMITIVES.include?(field[:of])
|
64
77
|
end
|
65
78
|
@parameters[name] = fields
|
66
79
|
end
|
@@ -114,6 +127,20 @@ module RSpec
|
|
114
127
|
params)
|
115
128
|
end
|
116
129
|
|
130
|
+
# Associate a defined security scheme to this request
|
131
|
+
#
|
132
|
+
# @param references [Array<Symbol>] Security scheme reference
|
133
|
+
def add_security_references(*references)
|
134
|
+
check_current_context :resource, :url, :method
|
135
|
+
|
136
|
+
refs = @resources.dig @current_resource, 'paths', @current_url, 'actions', @current_method, 'security'
|
137
|
+
refs ||= []
|
138
|
+
refs += references
|
139
|
+
Utils.deep_set(@resources,
|
140
|
+
[@current_resource, 'paths', @current_url, 'actions', @current_method, 'security'],
|
141
|
+
refs)
|
142
|
+
end
|
143
|
+
|
117
144
|
##
|
118
145
|
# Adds an action and sets `@current_url` and `@current_method`
|
119
146
|
#
|
@@ -294,7 +321,7 @@ module RSpec
|
|
294
321
|
# @param field [Hash] Parameter definition
|
295
322
|
#
|
296
323
|
# @return [Hash] Completed parameter
|
297
|
-
def fill_request_param(field)
|
324
|
+
def fill_request_param(field) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
298
325
|
if field[:type] == :object && field[:attributes]
|
299
326
|
organize_params field[:attributes]
|
300
327
|
else
|
@@ -302,6 +329,7 @@ module RSpec
|
|
302
329
|
type: PARAM_TYPES[field[:type]][:type],
|
303
330
|
description: field[:description] || nil,
|
304
331
|
}
|
332
|
+
properties[:format] = PARAM_TYPES[field[:type]][:format] if PARAM_TYPES[field[:type]][:format]
|
305
333
|
|
306
334
|
properties[:items] = organize_params field[:of] if field[:type] == :array && field[:of]
|
307
335
|
properties
|
@@ -15,16 +15,36 @@ module RSpec
|
|
15
15
|
# ```
|
16
16
|
class OpenApiRenderer # rubocop:disable Metrics/ClassLength
|
17
17
|
attr_writer :api_servers, :api_title, :api_version, :api_description, :api_tos
|
18
|
+
attr_reader :redactables
|
18
19
|
|
19
20
|
def initialize
|
20
|
-
@metadata
|
21
|
-
@api_infos
|
22
|
-
@api_servers
|
23
|
-
@api_paths
|
21
|
+
@metadata = { resources: {}, entities: {} }
|
22
|
+
@api_infos = {}
|
23
|
+
@api_servers = []
|
24
|
+
@api_paths = {}
|
24
25
|
@api_components = {}
|
25
26
|
@api_tags = []
|
26
27
|
@api_contact = {}
|
27
28
|
@api_license = {}
|
29
|
+
@api_security = {}
|
30
|
+
@redactables = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def redact_responses(pairs)
|
34
|
+
@redactables = pairs
|
35
|
+
end
|
36
|
+
|
37
|
+
##
|
38
|
+
# Adds a security scheme definition to the API documentation
|
39
|
+
#
|
40
|
+
# @param reference [Symbol] Reference to use in the tests
|
41
|
+
# @param name [String] Human friendly name
|
42
|
+
# @param definition [Hash] Security scheme definition as per https://swagger.io/specification/#security-scheme-object
|
43
|
+
def add_security_scheme(reference, name, definition)
|
44
|
+
raise "Security scheme #{reference} is already defined" if @api_security.key? reference
|
45
|
+
|
46
|
+
definition[:name] = name
|
47
|
+
@api_security[reference] = definition
|
28
48
|
end
|
29
49
|
|
30
50
|
##
|
@@ -36,7 +56,6 @@ module RSpec
|
|
36
56
|
# @return [void
|
37
57
|
def merge_context(context, dump_metadata: false)
|
38
58
|
@metadata[:resources].deep_merge! context.respond_to?(:resources) ? context.resources : context[:resources]
|
39
|
-
@metadata[:entities].deep_merge! context.respond_to?(:entities) ? context.entities : context[:entities]
|
40
59
|
|
41
60
|
# Save context for debug and fixtures
|
42
61
|
File.write ::Rails.root.join('tmp', 'rra_metadata.yaml'), @metadata.to_yaml if dump_metadata
|
@@ -54,11 +73,16 @@ module RSpec
|
|
54
73
|
|
55
74
|
path ||= ::Rails.root.join('tmp', 'rspec_api_rails')
|
56
75
|
|
76
|
+
metadata = prepare_metadata
|
77
|
+
|
57
78
|
file_types = %i[yaml json]
|
58
79
|
only.each do |type|
|
59
80
|
next unless file_types.include? type
|
60
81
|
|
61
|
-
|
82
|
+
data = metadata.to_yaml if type == :yaml
|
83
|
+
data = JSON.pretty_generate(metadata) if type == :json
|
84
|
+
|
85
|
+
File.write "#{path}.#{type}", data
|
62
86
|
end
|
63
87
|
end
|
64
88
|
|
@@ -77,7 +101,7 @@ module RSpec
|
|
77
101
|
components: @api_components,
|
78
102
|
tags: @api_tags,
|
79
103
|
}
|
80
|
-
JSON.parse(
|
104
|
+
JSON.parse(hash.to_json)
|
81
105
|
end
|
82
106
|
|
83
107
|
##
|
@@ -125,24 +149,33 @@ module RSpec
|
|
125
149
|
#
|
126
150
|
# @return [void]
|
127
151
|
def extract_metadata
|
128
|
-
|
152
|
+
extract_security
|
129
153
|
api_infos
|
130
154
|
api_servers
|
155
|
+
global_entities
|
156
|
+
extract_from_resources
|
157
|
+
end
|
158
|
+
|
159
|
+
##
|
160
|
+
# Extracts metadata from security schemes for rendering
|
161
|
+
#
|
162
|
+
# @return [void]
|
163
|
+
def extract_security
|
164
|
+
return unless @api_security.keys.count.positive?
|
165
|
+
|
166
|
+
@api_components['securitySchemes'] = @api_security
|
131
167
|
end
|
132
168
|
|
133
169
|
##
|
134
170
|
# Extracts metadata from resources for rendering
|
135
171
|
#
|
136
172
|
# @return [void]
|
137
|
-
def extract_from_resources
|
173
|
+
def extract_from_resources
|
138
174
|
@api_components[:schemas] ||= {}
|
139
175
|
@metadata[:resources].each do |resource_key, resource|
|
140
|
-
resource[:entities].each do |name, entity|
|
141
|
-
@api_components[:schemas][name] = process_entity(entity)
|
142
|
-
end
|
143
176
|
@api_tags.push(
|
144
177
|
name: resource_key.to_s,
|
145
|
-
description: resource[:description]
|
178
|
+
description: resource[:description].presence&.strip || ''
|
146
179
|
)
|
147
180
|
process_resource resource: resource_key, resource_config: resource
|
148
181
|
end
|
@@ -190,22 +223,29 @@ module RSpec
|
|
190
223
|
parameters
|
191
224
|
end
|
192
225
|
|
193
|
-
def process_entity(entity) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
226
|
+
def process_entity(entity) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
194
227
|
schema = {
|
195
228
|
properties: {},
|
196
229
|
}
|
197
230
|
required = []
|
198
231
|
entity.fields.each do |name, field|
|
199
232
|
property = {
|
200
|
-
description: field.description,
|
233
|
+
description: field.description.presence&.strip || '',
|
201
234
|
type: PARAM_TYPES[field.type][:type],
|
202
235
|
}
|
203
|
-
property[:format]
|
204
|
-
|
205
|
-
|
206
|
-
|
236
|
+
property[:format] = PARAM_TYPES[field.type][:format] if PARAM_TYPES[field.type][:format]
|
237
|
+
|
238
|
+
if PRIMITIVES.include? field.attributes
|
239
|
+
property[:items] = { type: field.attributes }
|
240
|
+
elsif field.type == :object && field.attributes.is_a?(Symbol)
|
241
|
+
property = { '$ref' => "#/components/schemas/#{field.attributes}" }
|
242
|
+
elsif field.type == :array && field.attributes.is_a?(Symbol)
|
243
|
+
property = { type: :array, items: { '$ref' => "#/components/schemas/#{field.attributes}" } }
|
244
|
+
end
|
207
245
|
|
208
246
|
required.push name unless field.required == false
|
247
|
+
|
248
|
+
schema[:properties][name] = property
|
209
249
|
end
|
210
250
|
|
211
251
|
schema[:required] = required unless required.size.zero?
|
@@ -221,10 +261,10 @@ module RSpec
|
|
221
261
|
#
|
222
262
|
#
|
223
263
|
# @return [void]
|
224
|
-
def process_path_param(name, param) # rubocop:disable Metrics/MethodLength
|
264
|
+
def process_path_param(name, param) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
225
265
|
parameter = {
|
226
266
|
name: name.to_s,
|
227
|
-
description: param[:description],
|
267
|
+
description: param[:description].presence&.strip || '',
|
228
268
|
required: param[:required] || true,
|
229
269
|
in: param[:scope].to_s,
|
230
270
|
schema: {
|
@@ -250,7 +290,7 @@ module RSpec
|
|
250
290
|
#
|
251
291
|
# FIXME: Rename "action_config" to "action"
|
252
292
|
# FIXME: Rename "parameters" to "path_parameters"
|
253
|
-
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
293
|
+
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
254
294
|
def process_action(resource: nil, path: nil, path_config: nil, action_config: nil, parameters: nil)
|
255
295
|
responses = {}
|
256
296
|
request_body = nil
|
@@ -268,21 +308,31 @@ module RSpec
|
|
268
308
|
responses[status_key] = process_response status: status_key, status_config: status, content: content
|
269
309
|
end
|
270
310
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
operationId: "#{resource} #{summary}".downcase.gsub(/[^\w]/, '_'),
|
311
|
+
action = {
|
312
|
+
summary: path_config[:actions][action_config][:summary]&.strip || '',
|
313
|
+
description: path_config[:actions][action_config][:description].presence&.strip || '',
|
314
|
+
operationId: "#{resource} #{action_config} #{path}".downcase.gsub(/[^\w]/, '_'),
|
276
315
|
parameters: parameters,
|
277
316
|
responses: responses,
|
278
317
|
tags: [resource.to_s],
|
279
318
|
}
|
280
319
|
|
320
|
+
if path_config[:actions][action_config].key? :security
|
321
|
+
references = path_config[:actions][action_config][:security]
|
322
|
+
|
323
|
+
action[:security] = []
|
324
|
+
references.each do |reference|
|
325
|
+
raise "No security scheme defined with reference #{reference}" unless @api_security.key? reference
|
326
|
+
|
327
|
+
action[:security].push({ reference => [] })
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
281
331
|
action[:requestBody] = request_body if request_body
|
282
332
|
|
283
333
|
action
|
284
334
|
end
|
285
|
-
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
|
335
|
+
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
286
336
|
|
287
337
|
##
|
288
338
|
# Processes a request body from metadata
|
@@ -294,11 +344,12 @@ module RSpec
|
|
294
344
|
# @return [void]
|
295
345
|
def process_request_body(schema: nil, ref: nil, examples: {})
|
296
346
|
Utils.deep_set @api_components, ['schemas', ref], schema
|
347
|
+
|
297
348
|
{
|
298
349
|
# description: '',
|
299
350
|
required: true,
|
300
351
|
content: {
|
301
|
-
|
352
|
+
content_type_from_schema(schema) => {
|
302
353
|
schema: { '$ref' => "#/components/schemas/#{ref}" },
|
303
354
|
examples: examples,
|
304
355
|
},
|
@@ -306,6 +357,23 @@ module RSpec
|
|
306
357
|
}
|
307
358
|
end
|
308
359
|
|
360
|
+
def content_type_from_schema(schema)
|
361
|
+
schema_includes_file?(schema) ? 'multipart/form-data' : 'application/json'
|
362
|
+
end
|
363
|
+
|
364
|
+
def schema_includes_file?(schema)
|
365
|
+
return true if schema[:type] == 'string' && schema[:format] == 'binary'
|
366
|
+
return false unless schema[:properties].is_a?(Hash) && schema[:required].is_a?(Array)
|
367
|
+
|
368
|
+
schema[:properties].each_value do |definition|
|
369
|
+
next unless schema_includes_file?(definition)
|
370
|
+
|
371
|
+
return true
|
372
|
+
end
|
373
|
+
|
374
|
+
false
|
375
|
+
end
|
376
|
+
|
309
377
|
##
|
310
378
|
# Process a response from metadata
|
311
379
|
#
|
@@ -314,22 +382,45 @@ module RSpec
|
|
314
382
|
# @param content [String] Response content
|
315
383
|
#
|
316
384
|
# @return [void]
|
317
|
-
def process_response(status: nil, status_config: nil, content: nil)
|
318
|
-
response = { description: status_config[:description] }
|
385
|
+
def process_response(status: nil, status_config: nil, content: nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
386
|
+
response = { description: status_config[:description].presence&.strip || '' }
|
319
387
|
|
320
388
|
return response if status.to_s == '204' && content # No content
|
321
389
|
|
390
|
+
data = begin
|
391
|
+
JSON.parse(content)
|
392
|
+
rescue JSON::ParserError, TypeError
|
393
|
+
content
|
394
|
+
end
|
395
|
+
|
396
|
+
entity = status_config[:expectations][:one] || status_config[:expectations][:many]
|
397
|
+
|
398
|
+
# TODO: handle sub-entities
|
399
|
+
if @redactables.key?(entity) && data.is_a?(Hash)
|
400
|
+
if status_config[:expectations][:one]
|
401
|
+
@redactables[entity].each_pair do |attribute, replacement|
|
402
|
+
data[attribute.to_s] = replacement
|
403
|
+
end
|
404
|
+
else
|
405
|
+
data.each_index do |index|
|
406
|
+
@redactables[entity].each_pair do |attribute, replacement|
|
407
|
+
data[index][attribute.to_s] = replacement
|
408
|
+
end
|
409
|
+
end
|
410
|
+
end
|
411
|
+
end
|
412
|
+
|
322
413
|
response[:content] = {
|
323
414
|
'application/json': {
|
324
415
|
schema: response_schema(status_config[:expectations]),
|
325
|
-
examples: { default: { value:
|
416
|
+
examples: { default: { value: data } },
|
326
417
|
},
|
327
418
|
}
|
328
419
|
|
329
420
|
response
|
330
421
|
end
|
331
422
|
|
332
|
-
def response_schema(expectations)
|
423
|
+
def response_schema(expectations) # rubocop:disable Metrics/MethodLength
|
333
424
|
if expectations[:many]
|
334
425
|
items = if PRIMITIVES.include?(expectations[:many])
|
335
426
|
{ type: expectations[:many] }
|
@@ -338,7 +429,11 @@ module RSpec
|
|
338
429
|
end
|
339
430
|
{ type: 'array', items: items }
|
340
431
|
elsif expectations[:one]
|
341
|
-
|
432
|
+
if PRIMITIVES.include?(expectations[:one])
|
433
|
+
{ type: expectations[:one] }
|
434
|
+
else
|
435
|
+
{ '$ref' => "#/components/schemas/#{expectations[:one]}" }
|
436
|
+
end
|
342
437
|
end
|
343
438
|
end
|
344
439
|
|
@@ -361,6 +456,16 @@ module RSpec
|
|
361
456
|
request_examples
|
362
457
|
end
|
363
458
|
|
459
|
+
def global_entities
|
460
|
+
return if RSpec::Rails::Api::Metadata.entities.keys.count.zero?
|
461
|
+
|
462
|
+
@api_components[:schemas] = {}
|
463
|
+
|
464
|
+
RSpec::Rails::Api::Metadata.entities.each_pair do |name, entity|
|
465
|
+
@api_components[:schemas][name] = process_entity(entity)
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
364
469
|
##
|
365
470
|
# Converts path with params like ":id" to their OpenAPI representation
|
366
471
|
#
|
@@ -387,12 +492,12 @@ module RSpec
|
|
387
492
|
# Fills the API general information sections
|
388
493
|
#
|
389
494
|
# @return [void]
|
390
|
-
def api_infos
|
495
|
+
def api_infos # rubocop:disable Metrics/CyclomaticComplexity
|
391
496
|
@api_infos = {
|
392
497
|
title: @api_title || 'Some sample app',
|
393
498
|
version: @api_version || '1.0',
|
394
499
|
}
|
395
|
-
@api_infos[:description] = @api_description if @api_description
|
500
|
+
@api_infos[:description] = @api_description.strip || '' if @api_description.present?
|
396
501
|
@api_infos[:termsOfService] = @api_tos if @api_tos
|
397
502
|
@api_infos[:contact] = @api_contact if @api_contact[:name]
|
398
503
|
@api_infos[:license] = @api_license if @api_license[:name]
|
data/lib/rspec_rails_api.rb
CHANGED
@@ -30,9 +30,10 @@ module RSpec
|
|
30
30
|
boolean: { type: 'boolean', format: nil },
|
31
31
|
string: { type: 'string', format: nil, class: String },
|
32
32
|
integer: { type: 'integer', format: nil, class: Integer },
|
33
|
-
number: { type: 'number', format: nil, class:
|
33
|
+
number: { type: 'number', format: nil, class: Numeric },
|
34
34
|
array: { type: 'array', format: nil, class: Array },
|
35
35
|
object: { type: 'object', format: nil, class: Hash },
|
36
|
+
file: { type: 'string', format: 'binary' },
|
36
37
|
}.freeze
|
37
38
|
|
38
39
|
PRIMITIVES = PARAM_TYPES.keys
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rspec-rails-api
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manuel Tancoigne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-
|
11
|
+
date: 2023-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|