rspec-rails-api 0.5.0 → 0.6.1

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: 232e1ea8b43dd1e3b5258f0029d28a54d886156634e87d4a6854a3543752fb0f
4
- data.tar.gz: 71828ab75d08ff900cbe9f018af219bc79b98231118712d127e35c274690d7ad
3
+ metadata.gz: 4771b145e32ae7cbfd7f03d6252618d3a4cd8b557930a67a562e45046d4e302b
4
+ data.tar.gz: 6bc0e55b1c2e5d51ba5bdfc52ac532efbf5ad539e138557a99895ef637e0311a
5
5
  SHA512:
6
- metadata.gz: dbb37718158f52e0a2056bbe9b8ccde44782cafe915cc146f05333532484b10ae8260e7abbd3f571e657c778b8d8ac37c731adfa452d3e94af253dcf6849aede
7
- data.tar.gz: 731f623490f7e15b3f0acb8851955835083249b60e62f722ab9870c6658d3e690ed0b6aca8ac4edcdc3f6b1c892517511937d190459b8020fcb2b3c2ef99047a
6
+ metadata.gz: cafa1ebfc333e41474f85dfb9389f976df1ffa8000de62c8a412c6436088db9182a7f15ec162dff46848265e6cf86564e5a79c93e0814afcc454a242ad77c8b6
7
+ data.tar.gz: a24717ab5c36a863eaa6939c4ee3f5df3b5fbf058f45b17923d50dd0656680becca60f325d2d9c11b13db84dfaf243a4f24dd77450c33b28911086b986f9cb9e
data/CHANGELOG.md CHANGED
@@ -1,22 +1,82 @@
1
1
  # Change Log
2
2
  All notable changes to this project will be documented in this file.
3
3
 
4
- ## Not released
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
- ## 0.5.0 - 2023-01-02
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.1] - 2024-01-17
22
+
23
+ ### Fixed
24
+
25
+ - Renderer: handle arrays of object with inline attributes (not a reference)
26
+ - Renderer: Remove the `name` attribute from `basic` authentication declaration
27
+ - Renderer: Add missing `items` key on arrays of any type
28
+
29
+ ## [0.6.0] - 2023-07-17
30
+
31
+ ### Added
32
+
33
+ - Declaring the same resource multiple times is now possible
34
+ - Add a simple support for security schemes
35
+ - Add support for global entities declarations. Keep things DRYer :)
36
+ - `test_response_of`: add `ignore_content_type` flag to ignore response's content type
37
+ - `test_response_of`: add `ignore_response` flag to ignore tests on response
38
+ - Renderer: Add simple support for redactable content. It will replace content in responses by something else. For now,
39
+ sub-entities are not redacted.
40
+ - Support for `file` type. When declaring a field with the `file` type, it will change the request content-type to
41
+ `multipart/form-data` automatically.
42
+
43
+ ### Changed
44
+
45
+ - Generated JSON files will always be prettified to ease changes reviews when they are versioned
46
+ - When an unexpected 422 error happens, also display its content in error message
47
+ - [BREAKING] It is now impossible to declare the same entity twice. To remediate, rename your declared entities and/or
48
+ use global declarations. Check [README](./README.md) for example.
49
+ - Renderer: Use response data as-is when it's not valid JSON
50
+
51
+ ### Fixed
52
+
53
+ - Strip generated descriptions and summaries
54
+ - Primitives are no more referenced in schemas for "expect_one" types
55
+ - Compare type `:float` against `Numeric` class
56
+ - Entities: Raise error when using `:array` type with `attributes` property and `:object` type with `of` property
57
+ - Rendering of sub-references now use the correct reference
58
+ - Metadata: Add missing type on array parameters
59
+ - Don't transform parameters into JSON when making `get` requests
60
+ - Operation IDs don't use summaries for uniqueness
61
+ - Downcase response content type before comparison
62
+ - Type format is now added on request parameters if applicable
63
+
64
+ ## [0.5.0] - 2023-01-02
65
+
66
+ ### Changed
7
67
 
8
68
  - Improved error messages
9
69
  - Improved usage of primitive types: use `:string` instead of `:type_string`, and more generally, remove the `type_`
10
70
  prefix
71
+
72
+ ### Fixed
73
+ -
11
74
  - Fixed an error when an object is defined with an attribute named `type`.
12
75
 
13
- ## 0.4.0 - 2021-12-19
76
+ ## [0.4.0] - 2021-12-19
77
+
78
+ ### Added
14
79
 
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
80
  - Check for arrays of primitives is now a thing:
21
81
  ```rb
22
82
  # In entities, when attribute is an array of primitives
@@ -28,15 +88,19 @@ All notable changes to this project will be documented in this file.
28
88
  #...
29
89
  end
30
90
  ```
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
91
  - OpenApi:
34
92
  - Responses now includes the schema
35
93
  - `on_xxx` methods second parameter is now used as summary instead of description.
36
94
  Description can be defined on the third parameter.
95
+
96
+ ### Changed
97
+
98
+ - All parameters attributes are considered required unless specified
99
+ - RSpec metadata is now stored in `rra` instead of `rrad` (the gem's first name
100
+ was RSpec Rails API Doc at the time). Update RSpec configuration accordingly.
37
101
  - DSL changes:
38
102
  - `visit` is renamed to `test_response_of`
39
- - Support for `doc_only` is removed
103
+ - Support for `doc_only` is removed
40
104
  - Response expectations _should_ now be declared with `for_code`, and should be
41
105
  removed from example bodies:
42
106
  ```rb
@@ -52,44 +116,71 @@ All notable changes to this project will be documented in this file.
52
116
  end
53
117
  ```
54
118
 
119
+ ### Fixed
120
+
121
+ - Fix object `attributes` key in spec and documentation.
122
+ When defining object attributes, documentation and tests used `properties` key
123
+ while the code was waiting for an `attributes` key. The later makes more sense
124
+ so the spec and documentation were fixed.
125
+
126
+ ## [0.3.4] - 2021-10-20
127
+
128
+ ### Added
55
129
 
56
- ## 0.3.4 - 2021-10-20
57
130
  - Add the "required" attribute in parameters
58
131
 
59
- ## 0.3.3 - 2021-06-02
132
+ ## [0.3.3] - 2021-06-02
133
+
134
+ ### Fixed
135
+
60
136
  - Fix correct types on request parameters
61
137
 
62
- ## 0.3.2 - 2021-03-09
63
- - Fix YAML rendering (ruby objects were sometimes rendered in documentation)
138
+ ## [0.3.2] - 2021-03-09
139
+
140
+ ### Changed
141
+
64
142
  - Render examples results as YAML/JSON objects instead of text blocks.
65
143
 
66
- ## 0.3.1 - 2020-04-09
144
+ ### Fixed
145
+
146
+ - Fix YAML rendering (ruby objects were sometimes rendered in documentation)
147
+
148
+ ## [0.3.1] - 2020-04-09
149
+
150
+ ### Added
151
+
67
152
  - Add support for "test only" examples, allowing to write examples without documentation.
68
153
 
69
- ## 0.3.0 - 2019-12-26
154
+ ## [0.3.0] - 2019-12-26
70
155
 
71
156
  ### Changed
157
+
72
158
  - Rails 6 support, deprecated methods from Rails 5 are not supported. Use version `0.2.3`
73
159
  of this gem if your application is still on 5.
74
160
 
75
161
  ## 0.2.3 - 2019-12-04
76
162
 
77
- ### Improved
163
+ ### Added
78
164
 
79
165
  - Generated Swagger file now use the payloads of POST/PATCH/PUT requests.
80
- - Minimum Ruby version is now specified: Ruby 2.3.3.
81
166
 
82
- ## 0.2.1/0.2.2 - 2019-11-03
167
+ ### Changed
83
168
 
84
- _Version 0.2.1 was released and yanked by mistake. Version 0.2.2 is the exact
85
- same one, with a version bump_
169
+ - Minimum Ruby version is now specified: Ruby 2.3.3.
170
+
171
+ ## [0.2.2] - 2019-11-03
86
172
 
87
173
  ### Changed
88
174
 
89
175
  - `for_code` method now have its `description` optional. If none is provided,
90
176
  the description will be set from the status code.
91
177
 
92
- ## 0.2.0 - 2019-11-02
178
+ ## [0.2.1] - 2019-11-03 [YANKED]
179
+
180
+ _Version 0.2.1 was released and yanked by mistake. Version 0.2.2 is the exact
181
+ same one, with a version bump_
182
+
183
+ ## [0.2.0] - 2019-11-02
93
184
 
94
185
  ### Added
95
186
 
@@ -108,35 +199,35 @@ of the fixtures.
108
199
  the existing calls accordingly. To use params defined with `parameters`, use the
109
200
  `defined` option: `request_params defined: :common_form_params`
110
201
 
111
- ## 0.1.5 - 2019-10-31
202
+ ## [0.1.5] - 2019-10-31
112
203
 
113
204
  ### Fixed
114
205
 
115
206
  - Fixed issue with POST/PUT/DELETE requests with no `request_params`
116
207
  - Improved documentation (integration with Devise, typos and mistakes)
117
208
 
118
- ## 0.1.4 - 2019-10-24
209
+ ## [0.1.4] - 2019-10-24
119
210
 
120
211
  ### Added
121
212
 
122
213
  - Added support for arrays of objects in request parameters
123
214
 
124
- ## 0.1.3 - 2019-10-23
215
+ ## [0.1.3] - 2019-10-23
125
216
 
126
217
  ### Added
127
218
 
128
219
  - Added ability to document API descriptions, servers, etc... from the RSpec helper files
129
220
 
130
- ## 0.1.2 - 2019-10-22
221
+ ## [0.1.2] - 2019-10-22
131
222
 
132
223
  ### Added
133
224
 
134
225
  - Added `item` property for arrays descriptions
135
226
 
136
- ## 0.1.1 - 2019-10-22
227
+ ## [0.1.1] - 2019-10-22
137
228
 
138
229
  - Added support for custom headers in request examples (useful for `visit` method)
139
230
 
140
- ## 0.1.0 - 2019-10-21
231
+ ## [0.1.0] - 2019-10-21
141
232
 
142
233
  Initial release
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rspec-rails-api (0.5.0)
4
+ rspec-rails-api (0.6.1)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -26,53 +26,114 @@ bundle
26
26
 
27
27
  Configuration should be made manually for now:
28
28
 
29
- **spec/acceptance_helper.rb**
29
+ **spec/support/rspec_rails_api.rb**:
30
30
 
31
31
  ```ruby
32
- require 'rails_helper'
33
32
  require 'rspec_rails_api'
34
33
 
34
+ # Associate spec/acceptance/* to acceptance tests
35
+ RSpec::Rails::DIRECTORY_MAPPINGS[:acceptance] = %w[spec acceptance]
36
+
35
37
  RSpec.configure do |config|
36
38
  config.include RSpec::Rails::Api::DSL::Example
37
- end
38
-
39
- renderer = RSpec::Rails::Api::OpenApiRenderer.new
40
- # Options here should be customized
41
- renderer.api_title = 'YourProject API'
42
- renderer.api_version = '1'
43
- renderer.api_description = 'Manage data on YourProject'
44
- # Options below are optional
45
- renderer.api_servers = [{ url: 'https://example.com' }]
46
- renderer.api_tos = 'http://example.com/tos.html'
47
- renderer.api_contact = { name: 'Admin', email: 'admin@example.com', url: 'http://example.com/contact' }
48
- renderer.api_license = { name: 'Apache', url: 'https://opensource.org/licenses/Apache-2.0' }
39
+ config.include RSpec::Rails::RequestExampleGroup, type: :acceptance
49
40
 
50
- RSpec.configuration.after(:context, type: :acceptance) do |context|
51
- renderer.merge_context context.class.metadata[:rra].to_h
52
- end
41
+ # Define the renderer if you want to generate the OpenApi documentation
42
+ renderer = RSpec::Rails::Api::OpenApiRenderer.new
43
+ # Options here should be customized
44
+ renderer.api_title = 'YourProject API'
45
+ renderer.api_version = '1'
46
+ renderer.api_description = 'Manage data on YourProject'
47
+ # Options below are optional
48
+ renderer.api_servers = [{ url: 'https://example.com' }]
49
+ renderer.api_tos = 'http://example.com/tos.html'
50
+ renderer.api_contact = { name: 'Admin', email: 'admin@example.com', url: 'http://example.com/contact' }
51
+ renderer.api_license = { name: 'Apache', url: 'https://opensource.org/licenses/Apache-2.0' }
52
+ # ... Check the "Configuration" section for all the options
53
+
54
+ config.after(:context, type: :acceptance) do |context|
55
+ renderer.merge_context context.class.metadata[:rra].to_h
56
+ end
53
57
 
54
- RSpec.configuration.after(:suite) do
55
- # Default path is 'tmp/rspec_rails_api_output.json/yaml'
56
- renderer.write_files Rails.root.join('public', 'swagger_doc'), only: [:json]
58
+ config.after(:suite) do
59
+ # Default path is 'tmp/rspec_rails_api_output.json/yaml'
60
+ renderer.write_files Rails.root.join('public', 'swagger_doc'), only: [:json]
61
+ end
57
62
  end
58
63
  ```
59
64
 
60
- **spec/rails_helper.rb**
65
+ **spec/rails_helper.rb**:
61
66
 
62
67
  ```ruby
63
- # ...
64
-
65
- RSpec::Rails::DIRECTORY_MAPPINGS[:acceptance] = %w[spec acceptance]
66
-
67
- RSpec.configure do |config|
68
- # ...
69
- config.include RSpec::Rails::RequestExampleGroup, type: :acceptance
70
- end
68
+ #...
69
+ require 'support/rspec_rails_api'
70
+ #...
71
71
  ```
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
+
78
+ ```rb
79
+ # Server URL for quick reference
80
+ server_url = 'https://example.com'
81
+
82
+ # Options here should be present for a valid OpenAPI file
83
+ renderer.api_title = 'MyProject API'
84
+ renderer.api_version = '1'
85
+
86
+ # Options below are optional
87
+ #
88
+ # API description. Markdown supported
89
+ # renderer.api_description = 'Manage data on MyProject'
90
+ #
91
+ # List of servers, to live-test the documentation
92
+ # renderer.api_servers = [{ url: server_url }, { url: 'http://localhost:3000' }]
93
+ #
94
+ # Link to the API terms of service, if any
95
+ # renderer.api_tos = 'http://example.com/tos.html'
96
+ #
97
+ # Contact information
98
+ # renderer.api_contact = { name: 'Admin', email: 'admin@example.com', url: 'http://example.com/contact' }
99
+ #
100
+ # API license information
101
+ # renderer.api_license = { name: 'Apache', url: 'https://opensource.org/licenses/Apache-2.0' }
102
+ #
103
+ # Possible security schemes
104
+ # renderer.add_security_scheme :pkce_code_grant, 'PKCE code grant',
105
+ # type: 'oauth2',
106
+ # flows: {
107
+ # implicit: {
108
+ # authorizationUrl: "#{server_url}/oauth/authorize",
109
+ # scopes: { read: 'will read data on your behalf', write: 'will write data on your behalf' }
110
+ # }
111
+ # }
112
+ # renderer.add_security_scheme :bearer, 'Bearer token',
113
+ # type: 'http',
114
+ # scheme: 'bearer'
115
+ #
116
+ # Declare keys whose values should be filtered in responses.
117
+ # renderer.redact_responses entity_name: { key: 'REDACTED' },
118
+ # other_entity: { other_key: ['REDACTED'] }
119
+
120
+
121
+ # We need to merge each context metadata so we can reference to them to build the final file
122
+ RSpec.configuration.after(:context, type: :acceptance) do |context|
123
+ renderer.merge_context context.class.metadata[:rra].to_h
124
+ # During development of rspec_rails_api, you may want to dump raw metadata to a file
125
+ renderer.merge_context context.class.metadata[:rra].to_h, dump_metadata: true
126
+ end
127
+
128
+ # Skip this block if you don't need the OpenAPI documentation file and only have your responses tested
129
+ RSpec.configuration.after(:suite) do
130
+ renderer.write_files Rails.root.join('public/swagger') # Write both YAML and prettified JSON files
131
+ # or
132
+ renderer.write_files Rails.root.join('public/swagger'), only: [:json] # Prettified JSON only
133
+ # or
134
+ renderer.write_files Rails.root.join('public/swagger'), only: [:yaml] # YAML only
135
+ end
136
+ ```
76
137
 
77
138
  ### Integration with Devise
78
139
 
@@ -124,7 +185,7 @@ end
124
185
  # In examples
125
186
  #...
126
187
  for_code 200, 'Success' do |url|
127
- sing_in #...
188
+ sign_in #...
128
189
  test_response_of url
129
190
 
130
191
  #...
@@ -135,10 +196,6 @@ end
135
196
  This solution comes from [this article](https://makandracards.com/makandra/37161-rspec-devise-how-to-sign-in-users-in-request-specs)
136
197
  by Arne Hartherz (MIT license).
137
198
 
138
- ## Usage
139
-
140
- Write some spec files and run RSpec as you would usually do.
141
-
142
199
  ## Writing specs
143
200
 
144
201
  There is a [commented example](dummy/spec/acceptance/posts_spec.rb) available in
@@ -149,7 +206,7 @@ The idea is to have a simple DSL, and declare things like:
149
206
  **spec/acceptance/users_spec.rb**
150
207
 
151
208
  ```ruby
152
- require 'acceptance_helper'
209
+ require 'rails_helper'
153
210
 
154
211
  RSpec.describe 'Users', type: :acceptance do
155
212
  resource 'Users', 'Manage users'
@@ -186,6 +243,80 @@ RSpec.describe 'Users', type: :acceptance do
186
243
  end
187
244
  ```
188
245
 
246
+ ### Entity declarations
247
+
248
+ You can declare entities locally (in every spec files), but sometimes you will need to use/reference the same entity
249
+ in multiple spec files (e.g.: an error message). In that case, you can create _global_ entities in separate files, and they
250
+ will be picked-up when needed.
251
+
252
+ Example of a local entity:
253
+
254
+ ```rb
255
+ # spec/acceptance/api/users_acceptance_spec.rb
256
+ require 'rails_helper'
257
+
258
+ RSpec.describe 'Users', type: :acceptance do
259
+ resource 'Users', 'Manage users'
260
+
261
+ # This is a local entity
262
+ entity :user,
263
+ id: { type: :integer, description: 'The id' },
264
+ email: { type: :string, description: 'The name' },
265
+ role: { type: :string, description: 'The name' },
266
+ created_at: { type: :datetime, description: 'Creation date' },
267
+ updated_at: { type: :datetime, description: 'Modification date' },
268
+ url: { type: :string, description: 'URL to this category' }
269
+
270
+ on_get '/api/users/', 'Users list' do
271
+ for_code 200, 'Success response', expect_many: :user do |url|
272
+ test_response_of url
273
+ end
274
+ end
275
+
276
+ #...
277
+ end
278
+ ```
279
+
280
+ Defining global entities:
281
+
282
+ ```rb
283
+ # spec/support/entities/user.rb
284
+ # This file should be required at some point in the "rails_helper" or "acceptance_helper"
285
+
286
+ require 'rspec/rails/api/metadata'
287
+
288
+ RSpec::Rails::Api::Metadata.add_entity :user,
289
+ id: { type: :integer, description: 'The id' },
290
+ email: { type: :string, description: 'The name' },
291
+ role: { type: :string, description: 'The name' },
292
+ created_at: { type: :datetime, description: 'Creation date' },
293
+ updated_at: { type: :datetime, description: 'Modification date' },
294
+ url: { type: :string, description: 'URL to this category' }
295
+
296
+ ```
297
+
298
+ Organization of the global entities declaration is up to you.
299
+
300
+ For small projects, we usually put them all in one file:
301
+
302
+ ```rb
303
+ # spec/support/acceptance_entities.rb
304
+
305
+ require 'rspec/rails/api/metadata'
306
+
307
+ # This file contains common object definitions
308
+ {
309
+ error: {
310
+ error: { type: :string, description: "Error message" }
311
+ },
312
+ form_error: {
313
+ title: { type: :array, required: false, description: "Title errors", of: :string }
314
+ },
315
+ }.each do |name, attributes|
316
+ RSpec::Rails::Api::Metadata.add_entity name, attributes
317
+ end
318
+ ```
319
+
189
320
  ### DSL
190
321
 
191
322
  #### Example groups
@@ -197,6 +328,23 @@ Starts a resource description.
197
328
  - It must be called before any other documentation calls.
198
329
  - It should be in the first `describe block`
199
330
 
331
+ A resource may be completed across multiple spec files:
332
+
333
+ ```rb
334
+ # an_acceptance_spec.rb
335
+ RSpec.describe 'Something', type: :acceptance do
336
+ resource 'User', 'Manage users'
337
+ end
338
+
339
+ # another_acceptance_spec.rb
340
+ RSpec.describe 'Something else', type: :acceptance do
341
+ resource 'User', 'Another description'
342
+ end
343
+
344
+ ```
345
+
346
+ The first evaluated `resource` statement will be used as description; all the tests in both files will complete it.
347
+
200
348
  ##### `entity(type, fields)`
201
349
 
202
350
  Describes an entity for the documentation. The type is only a reference,
@@ -423,11 +571,30 @@ Once again, you have to pass an argument to the block if you use
423
571
  # ...
424
572
  ```
425
573
 
574
+ ##### `requires_security(scheme_references)`
575
+
576
+ Specifies the valid security schemes to use for this request. Security schemes are declared at the renderer level
577
+ (see [the configuration example](#Configuration)).
578
+
579
+ ```rb
580
+ # Given a previously :basic scheme
581
+
582
+ # ...
583
+ on_get '/some/path' do
584
+ require_security :basic, :implicit
585
+
586
+ for_code 200 do |url|
587
+ #...
588
+ end
589
+ end
590
+ # ...
591
+ ```
592
+
426
593
  #### Examples
427
594
 
428
595
  Example methods are available in `for_code` blocks
429
596
 
430
- ##### `test_response_of(example, path_params: {}, payload: {}, headers: {})`
597
+ ##### `test_response_of(example, path_params: {}, payload: {}, headers: {}, ignore_content_type: false)`
431
598
 
432
599
  Visits the described URL and:
433
600
 
@@ -441,6 +608,7 @@ Visits the described URL and:
441
608
  - `payload`: a hash of values to send. Ignored for GET and DELETE
442
609
  requests
443
610
  - `headers`: a hash of custom headers.
611
+ - `ignore_content_type`: whether to ignore response's content-type. By default, checks for a JSON response
444
612
 
445
613
  ```ruby
446
614
  for_code 200, 'Success' do |url|
@@ -543,6 +711,11 @@ MRs to improve this are welcome.
543
711
 
544
712
  There is no support for file fields yet.
545
713
 
714
+ ### Security
715
+
716
+ `Security` mechanisms are only declared for the OpenApi documentation as a reference, and is
717
+ not checked nor enforced in the tests.
718
+
546
719
  ## Development
547
720
 
548
721
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -9,14 +9,15 @@ module RSpec
9
9
  ##
10
10
  # Visits the current example and tests the response
11
11
  #
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
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 visit with for_code context.' unless example
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].to_json,
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
- entities = rra_metadata.resources[current_resource][:entities]
53
- raise "Unknown entity '#{entity}' in resource '#{current_resource}'" unless entities.key? entity.to_sym
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
- entities[entity.to_sym].expand_with(entities)
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
- def check_response(response, expected_code) # rubocop:disable Metrics/AbcSize
64
- expect(response.status).to eq expected_code
65
- expect(response.headers['Content-Type']).to eq 'application/json; charset=utf-8' if expected_code != 204
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: example_params[0].downcase,
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
- metadata[:rra].add_entity type, fields
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
  #
@@ -13,8 +13,8 @@ module RSpec
13
13
 
14
14
  def initialize(fields)
15
15
  @fields = {}
16
- fields.each_key do |name|
17
- @fields[name] = FieldConfig.new fields[name]
16
+ fields.each_pair do |name, definition|
17
+ @fields[name] = FieldConfig.new(**definition)
18
18
  end
19
19
  end
20
20
 
@@ -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
@@ -50,7 +53,7 @@ module RSpec
50
53
  def define_attributes(attributes)
51
54
  @attributes = case attributes
52
55
  when Hash
53
- @attributes = EntityConfig.new attributes
56
+ EntityConfig.new attributes
54
57
  when Symbol
55
58
  attributes
56
59
  end
@@ -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 :entities, :resources, :parameters, :current_resource, :current_url, :current_method, :current_code
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] = { description: description, paths: {}, entities: {} }
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 = { resources: {}, entities: {} }
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 unless reference == :basic
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
- File.write "#{path}.#{type}", prepare_metadata.send("to_#{type}")
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(JSON.pretty_generate(hash))
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
- extract_from_resources
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 # rubocop:disable Metrics/MethodLength
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,31 @@ 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] = PARAM_TYPES[field.type][:format] if PARAM_TYPES[field.type][:format]
204
- schema[:properties][name] = property
205
- # Primitives support
206
- property[:items] = { type: field.attributes } if PRIMITIVES.include? field.attributes
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
+ elsif field.type == :array
245
+ property = { type: :array, items: process_entity(field.attributes) }
246
+ end
207
247
 
208
248
  required.push name unless field.required == false
249
+
250
+ schema[:properties][name] = property
209
251
  end
210
252
 
211
253
  schema[:required] = required unless required.size.zero?
@@ -221,10 +263,10 @@ module RSpec
221
263
  #
222
264
  #
223
265
  # @return [void]
224
- def process_path_param(name, param) # rubocop:disable Metrics/MethodLength
266
+ def process_path_param(name, param) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
225
267
  parameter = {
226
268
  name: name.to_s,
227
- description: param[:description],
269
+ description: param[:description].presence&.strip || '',
228
270
  required: param[:required] || true,
229
271
  in: param[:scope].to_s,
230
272
  schema: {
@@ -250,7 +292,7 @@ module RSpec
250
292
  #
251
293
  # FIXME: Rename "action_config" to "action"
252
294
  # FIXME: Rename "parameters" to "path_parameters"
253
- # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
295
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
254
296
  def process_action(resource: nil, path: nil, path_config: nil, action_config: nil, parameters: nil)
255
297
  responses = {}
256
298
  request_body = nil
@@ -268,21 +310,31 @@ module RSpec
268
310
  responses[status_key] = process_response status: status_key, status_config: status, content: content
269
311
  end
270
312
 
271
- summary = path_config[:actions][action_config][:summary]
272
- action = {
273
- summary: summary,
274
- description: path_config[:actions][action_config][:description],
275
- operationId: "#{resource} #{summary}".downcase.gsub(/[^\w]/, '_'),
313
+ action = {
314
+ summary: path_config[:actions][action_config][:summary]&.strip || '',
315
+ description: path_config[:actions][action_config][:description].presence&.strip || '',
316
+ operationId: "#{resource} #{action_config} #{path}".downcase.gsub(/[^\w]/, '_'),
276
317
  parameters: parameters,
277
318
  responses: responses,
278
319
  tags: [resource.to_s],
279
320
  }
280
321
 
322
+ if path_config[:actions][action_config].key? :security
323
+ references = path_config[:actions][action_config][:security]
324
+
325
+ action[:security] = []
326
+ references.each do |reference|
327
+ raise "No security scheme defined with reference #{reference}" unless @api_security.key? reference
328
+
329
+ action[:security].push({ reference => [] })
330
+ end
331
+ end
332
+
281
333
  action[:requestBody] = request_body if request_body
282
334
 
283
335
  action
284
336
  end
285
- # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
337
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
286
338
 
287
339
  ##
288
340
  # Processes a request body from metadata
@@ -294,11 +346,12 @@ module RSpec
294
346
  # @return [void]
295
347
  def process_request_body(schema: nil, ref: nil, examples: {})
296
348
  Utils.deep_set @api_components, ['schemas', ref], schema
349
+
297
350
  {
298
351
  # description: '',
299
352
  required: true,
300
353
  content: {
301
- 'application/json' => {
354
+ content_type_from_schema(schema) => {
302
355
  schema: { '$ref' => "#/components/schemas/#{ref}" },
303
356
  examples: examples,
304
357
  },
@@ -306,6 +359,23 @@ module RSpec
306
359
  }
307
360
  end
308
361
 
362
+ def content_type_from_schema(schema)
363
+ schema_includes_file?(schema) ? 'multipart/form-data' : 'application/json'
364
+ end
365
+
366
+ def schema_includes_file?(schema)
367
+ return true if schema[:type] == 'string' && schema[:format] == 'binary'
368
+ return false unless schema[:properties].is_a?(Hash) && schema[:required].is_a?(Array)
369
+
370
+ schema[:properties].each_value do |definition|
371
+ next unless schema_includes_file?(definition)
372
+
373
+ return true
374
+ end
375
+
376
+ false
377
+ end
378
+
309
379
  ##
310
380
  # Process a response from metadata
311
381
  #
@@ -314,22 +384,45 @@ module RSpec
314
384
  # @param content [String] Response content
315
385
  #
316
386
  # @return [void]
317
- def process_response(status: nil, status_config: nil, content: nil)
318
- response = { description: status_config[:description] }
387
+ def process_response(status: nil, status_config: nil, content: nil) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
388
+ response = { description: status_config[:description].presence&.strip || '' }
319
389
 
320
390
  return response if status.to_s == '204' && content # No content
321
391
 
392
+ data = begin
393
+ JSON.parse(content)
394
+ rescue JSON::ParserError, TypeError
395
+ content
396
+ end
397
+
398
+ entity = status_config[:expectations][:one] || status_config[:expectations][:many]
399
+
400
+ # TODO: handle sub-entities
401
+ if @redactables.key?(entity) && data.is_a?(Hash)
402
+ if status_config[:expectations][:one]
403
+ @redactables[entity].each_pair do |attribute, replacement|
404
+ data[attribute.to_s] = replacement
405
+ end
406
+ else
407
+ data.each_index do |index|
408
+ @redactables[entity].each_pair do |attribute, replacement|
409
+ data[index][attribute.to_s] = replacement
410
+ end
411
+ end
412
+ end
413
+ end
414
+
322
415
  response[:content] = {
323
416
  'application/json': {
324
417
  schema: response_schema(status_config[:expectations]),
325
- examples: { default: { value: JSON.parse(content) } },
418
+ examples: { default: { value: data } },
326
419
  },
327
420
  }
328
421
 
329
422
  response
330
423
  end
331
424
 
332
- def response_schema(expectations)
425
+ def response_schema(expectations) # rubocop:disable Metrics/MethodLength, Metrics/PerceivedComplexity
333
426
  if expectations[:many]
334
427
  items = if PRIMITIVES.include?(expectations[:many])
335
428
  { type: expectations[:many] }
@@ -338,7 +431,15 @@ module RSpec
338
431
  end
339
432
  { type: 'array', items: items }
340
433
  elsif expectations[:one]
341
- { '$ref' => "#/components/schemas/#{expectations[:one]}" }
434
+ # Unspecified arrays
435
+ if expectations[:one] == :array
436
+ { type: :array, items: {} }
437
+ # Array of specified type
438
+ elsif PRIMITIVES.include?(expectations[:one])
439
+ { type: expectations[:one] }
440
+ else
441
+ { '$ref' => "#/components/schemas/#{expectations[:one]}" }
442
+ end
342
443
  end
343
444
  end
344
445
 
@@ -361,6 +462,16 @@ module RSpec
361
462
  request_examples
362
463
  end
363
464
 
465
+ def global_entities
466
+ return if RSpec::Rails::Api::Metadata.entities.keys.count.zero?
467
+
468
+ @api_components[:schemas] = {}
469
+
470
+ RSpec::Rails::Api::Metadata.entities.each_pair do |name, entity|
471
+ @api_components[:schemas][name] = process_entity(entity)
472
+ end
473
+ end
474
+
364
475
  ##
365
476
  # Converts path with params like ":id" to their OpenAPI representation
366
477
  #
@@ -387,12 +498,12 @@ module RSpec
387
498
  # Fills the API general information sections
388
499
  #
389
500
  # @return [void]
390
- def api_infos
501
+ def api_infos # rubocop:disable Metrics/CyclomaticComplexity
391
502
  @api_infos = {
392
503
  title: @api_title || 'Some sample app',
393
504
  version: @api_version || '1.0',
394
505
  }
395
- @api_infos[:description] = @api_description if @api_description
506
+ @api_infos[:description] = @api_description.strip || '' if @api_description.present?
396
507
  @api_infos[:termsOfService] = @api_tos if @api_tos
397
508
  @api_infos[:contact] = @api_contact if @api_contact[:name]
398
509
  @api_infos[:license] = @api_license if @api_license[:name]
@@ -3,7 +3,7 @@
3
3
  module RSpec
4
4
  module Rails
5
5
  module Api
6
- VERSION = '0.5.0'
6
+ VERSION = '0.6.1'
7
7
  end
8
8
  end
9
9
  end
@@ -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: Float },
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.5.0
4
+ version: 0.6.1
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-01-02 00:00:00.000000000 Z
11
+ date: 2024-01-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport