rspec-rails-api 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5c81756e5b64eb7e5d7363231a498c947377879806b92dac3d8f11c0d28fd7bb
4
- data.tar.gz: 728634d884084a356b2f2d29ae410b6a54c70d47a8dfe9f0ac821bc75a80e160
3
+ metadata.gz: 06e9bc31ff4e310ca952011e76cc3fb6dcc39b15ea090229edb1385b51ed16f6
4
+ data.tar.gz: 8be9cba238ce37496e2e7eb2db221754cde5c673b1e404541ea84e71095e6ccb
5
5
  SHA512:
6
- metadata.gz: 6b50cb2972931d3a160bd2e3ab7ee73d73d4a762c7ee8e9264fae4903b3a98954d8d34aa6dc1aeecbd1fe40dea99cc6d02bd592e710a9a136b4665dd0285cd03
7
- data.tar.gz: 482ab0880d61cf4b709034903a7509b57750bf121dcdab9acba7bc519ef4fd8e9c079f2b263d3c7732db18e8fbb6f333743ccd700ebe286fcb548ca3c1b8cbde
6
+ metadata.gz: 5a9058a1121e702991d55696687b64cb8d2bb3872596b4d92f79aa33de8ef54a5b9ef33d2275f92087256cd6d7afcd1fc16472157d52529e19a97b5b66118b70
7
+ data.tar.gz: 85777059873dd5fba1842ce286c0c0c4c445aa2dad584a2992b2add593de51cb267e1b398dd6038fe71f5ec36bfa38bf8f03fa4ce0877d0438ae62f4f58fb82f
data/.rubocop.yml CHANGED
@@ -7,6 +7,7 @@ AllCops:
7
7
  Exclude:
8
8
  - dummy/**/*
9
9
  - vendor/bundle/**/*
10
+ NewCops: enable
10
11
 
11
12
  Layout/LineLength:
12
13
  Max: 120
data/CHANGELOG.md CHANGED
@@ -3,6 +3,60 @@ All notable changes to this project will be documented in this file.
3
3
 
4
4
  ## Not released
5
5
 
6
+ - All parameters attributes are considered required unless specified
7
+ - Fix object `attributes` key in spec and documentation.
8
+ When defining object attributes, documentation and tests used `properties` key
9
+ while the code was waiting for an `attributes` key. The later makes more sense
10
+ so the spec and documentation were fixed.
11
+ - Check for arrays of primitives is now a thing:
12
+ ```rb
13
+ # In entities, when attribute is an array of primitives
14
+ entity :user,
15
+ aliases: { type: :array, description: 'Pseudonyms', of: :type_string }
16
+
17
+ # In examples, when response is an array of primitives
18
+ for_code 200, expect_many: :type_string do |url|
19
+ #...
20
+ end
21
+ ```
22
+ - RSpec metadata is now stored in `rra` instead of `rrad` (the gem's first name
23
+ was RSpec Rails API Doc at the time). Update RSpec configuration accordingly.
24
+ - OpenApi:
25
+ - Responses now includes the schema
26
+ - `on_xxx` methods second parameter is now used as summary instead of description.
27
+ Description can be defined on the third parameter.
28
+ - DSL changes:
29
+ - `visit` is renamed to `test_response_of`
30
+ - Support for `doc_only` is removed
31
+ - Response expectations _should_ now be declared with `for_code`, and should be
32
+ removed from example bodies:
33
+ ```rb
34
+ # Before:
35
+ for_code 200 do |url|
36
+ visit url
37
+ expect(response).to have_many defined: :post
38
+ end
39
+
40
+ # Now:
41
+ for_code 200, expect_many: :post do |url|
42
+ test_response_of url
43
+ end
44
+ ```
45
+
46
+
47
+ ## 0.3.4 - 2021-10-20
48
+ - Add the "required" attribute in parameters
49
+
50
+ ## 0.3.3 - 2021-06-02
51
+ - Fix correct types on request parameters
52
+
53
+ ## 0.3.2 - 2021-03-09
54
+ - Fix YAML rendering (ruby objects were sometimes rendered in documentation)
55
+ - Render examples results as YAML/JSON objects instead of text blocks.
56
+
57
+ ## 0.3.1 - 2020-04-09
58
+ - Add support for "test only" examples, allowing to write examples without documentation.
59
+
6
60
  ## 0.3.0 - 2019-12-26
7
61
 
8
62
  ### Changed
data/README.md CHANGED
@@ -48,7 +48,7 @@ renderer.api_contact = { name: 'Admin', email: 'admin@example.com', url: 'http:/
48
48
  renderer.api_license = { name: 'Apache', url: 'https://opensource.org/licenses/Apache-2.0' }
49
49
 
50
50
  RSpec.configuration.after(:context, type: :acceptance) do |context|
51
- renderer.merge_context context.class.metadata[:rrad].to_h
51
+ renderer.merge_context context.class.metadata[:rra].to_h
52
52
  end
53
53
 
54
54
  RSpec.configuration.after(:suite) do
@@ -125,7 +125,7 @@ end
125
125
  #...
126
126
  for_code 200, 'Success' do |url|
127
127
  sing_in #...
128
- visit url
128
+ test_response_of url
129
129
 
130
130
  #...
131
131
  end
@@ -139,14 +139,6 @@ by Arne Hartherz (MIT license).
139
139
 
140
140
  Write some spec files and run RSpec as you would usually do.
141
141
 
142
- If you want to generate the documentation without testing the endpoints
143
- (and thus, without examples in generated files), use the `DOC_ONLY`
144
- environment variable:
145
-
146
- ```sh
147
- DOC_ONLY=true bundle exec rails spec
148
- ```
149
-
150
142
  ## Writing specs
151
143
 
152
144
  There is a [commented example](dummy/spec/acceptance/posts_spec.rb) available in
@@ -171,9 +163,8 @@ RSpec.describe 'Users', type: :acceptance do
171
163
  url: { type: :string, description: 'URL to this category' }
172
164
 
173
165
  on_get '/api/users/', 'Users list' do
174
- for_code 200, 'Success response' do |url|
175
- visit url
176
- expect(response).to have_many defined :user
166
+ for_code 200, 'Success response', expect_many: :user do |url|
167
+ test_response_of url
177
168
  end
178
169
  end
179
170
 
@@ -181,16 +172,15 @@ RSpec.describe 'Users', type: :acceptance do
181
172
  path_param id: { type: :integer, description: 'User Id' }
182
173
 
183
174
  request_params user: {
184
- type: :object, required: true, properties: {
175
+ type: :object, attributes: {
185
176
  name: { type: :string, required: false, description: 'New name' },
186
177
  email: { type: :string, required: false, description: 'New email' },
187
178
  role: { type: :string, required: false, description: 'New role' },
188
179
  }
189
180
  }
190
181
 
191
- for_code 200, 'Success response' do |url|
192
- visit url
193
- expect(response).to have_one defined :user
182
+ for_code 200, 'Success response', expect_one: :user do |url|
183
+ test_response_of url
194
184
  end
195
185
  end
196
186
  end
@@ -273,6 +263,16 @@ inline.
273
263
  Both `:of` and `attributes` may be a hash of fields or a symbol. If they
274
264
  are omitted, they will be documented, but responses won't be validated.
275
265
 
266
+ Arrays of primitives are supported; the type should be prefixed by `type_` to
267
+ avoid collisions with defined entities of the same name:
268
+
269
+ ```rb
270
+ entity :user,
271
+ surnames: { type: :array, of: :type_int32 }
272
+ ```
273
+
274
+ Check `lib/rspec_rails_api.rb` for the full list.
275
+
276
276
  ##### `parameters(type, fields)`
277
277
  Describe path or request parameters. The type is only a reference,
278
278
  use whatever makes sense. These parameters will be present in
@@ -282,16 +282,16 @@ documentation, only if they are referenced by a `request_params` or
282
282
  Fields have the structure of the hash you would give to `request_params`
283
283
  or `path_params` (see each method later in this documentation).
284
284
 
285
- ##### `on_<xxx>(url, description, &block)`
285
+ ##### `on_<xxx>(url, summary = nil, description = nil, &block)`
286
286
 
287
287
  Defines an URL.
288
288
 
289
289
  - `url` should be a relative URL to an existing endpoint (i.e.:
290
290
  `/api/users`)
291
- - `description` should be some valid
292
- [CommonMark](https://commonmark.org/)
291
+ - `summary` is a one line description of the endpoint
292
+ - `description` should be some valid [CommonMark](https://commonmark.org/)
293
293
 
294
- For now, only these methods are available:
294
+ These methods are available:
295
295
 
296
296
  - `on_get`
297
297
  - `on_post`
@@ -341,7 +341,7 @@ nested elements can be described:
341
341
  ```ruby
342
342
  on_post '/api/items' do
343
343
  request_params attributes: {
344
- item: { type: :object, required: true, properties: {
344
+ item: { type: :object, attributes: {
345
345
  name: { type: integer, description: 'The name of the new item', required: true },
346
346
  notes: { type: string, description: 'Additional notes' }
347
347
  } }
@@ -353,21 +353,21 @@ end
353
353
  An attribute should have the following form:
354
354
 
355
355
  ```
356
- <attr_name>: {type: <type>, desc: <description>, required: <required>, properties: <another_hash>, of: <another_hash> }
356
+ <attr_name>: {type: <type>, desc: <description>, required: <required>, attributes: <another_hash>, of: <another_hash> }
357
357
  ```
358
358
 
359
359
  - `attr_name` is the attribute name (sic)
360
360
  - `type` is the field type (check _entity definition_ for a list).
361
361
  `type` can be `:object` if the attribute contains other attributes.
362
362
  - `required` is optional an defaults to `false`.
363
- - `properties` is a hash of params and is only used if `type: :object`
363
+ - `attributes` is a hash of params and is only used if `type: :object`
364
364
  - `of` is a hash of params and is only used if `type: :array`
365
365
 
366
366
  Alternative with defined parameters:
367
367
 
368
368
  ```ruby
369
369
  parameters :item_form_params,
370
- item: { type: :object, required: true, properties: {
370
+ item: { type: :object, attributes: {
371
371
  name: { type: integer, description: 'The name of the new item', required: true },
372
372
  notes: { type: string, description: 'Additional notes' }
373
373
  }
@@ -380,12 +380,12 @@ on_post '/api/items' do
380
380
  end
381
381
  ```
382
382
 
383
- ##### `for_code(http_status, description = nil, doc_only: false, test_only: false &block)`
383
+ ##### `for_code(http_status, description = nil, test_only: false &block)`
384
384
 
385
385
  Describes the desired output for a precedently defined URL.
386
386
 
387
- Block takes one required argument, that should be passed to `visit`.
388
- This argument will contain the block context and allow `visit` to access
387
+ Block takes one required argument, that should be passed to `test_response_of`.
388
+ This argument will contain the block context and allow `test_response_of` to access
389
389
  the metadatas.
390
390
 
391
391
  You can have only one documented code per action/url, unless you use
@@ -396,11 +396,9 @@ You can have only one documented code per action/url, unless you use
396
396
  - `description` should be some valid
397
397
  [CommonMark](https://commonmark.org/). If not defined, a human readable
398
398
  translation of the `http_status` will be used.
399
- - `doc_only` can be set to true to temporarily disable block execution
400
- and only create the documentation (without examples).
401
399
  - `test_only` will omit the test from the documentation. Useful when you
402
400
  need to test things _around_ the call (response content, db,...)
403
- - `block` where additional tests can be performed. If `visit()` is
401
+ - `block` where additional tests can be performed. If `test_response_of` is
404
402
  called within the block, its output will be used in documentation
405
403
  examples, and the response type and code will actually be tested.
406
404
 
@@ -409,17 +407,17 @@ examples. This can be useful to document endpoints that are impossible
409
407
  to test.
410
408
 
411
409
  Once again, you have to pass an argument to the block if you use
412
- `visit`.
410
+ `test_response_of`.
413
411
 
414
412
  ```ruby
415
413
  # ...
416
414
  for_code 200, 'A successful response' do |url|
417
- visit url
415
+ test_response_of url
418
416
  # ...
419
417
  end
420
418
 
421
419
  for_code 200, 'Side test', test_only: true do |url|
422
- visit url
420
+ test_response_of url
423
421
  # ...
424
422
  end
425
423
  # ...
@@ -429,7 +427,7 @@ Once again, you have to pass an argument to the block if you use
429
427
 
430
428
  Example methods are available in `for_code` blocks
431
429
 
432
- ##### `visit(example, path_params: {}, payload: {}, headers: {})`
430
+ ##### `test_response_of(example, path_params: {}, payload: {}, headers: {})`
433
431
 
434
432
  Visits the described URL and:
435
433
 
@@ -446,12 +444,17 @@ Visits the described URL and:
446
444
 
447
445
  ```ruby
448
446
  for_code 200, 'Success' do |url|
449
- visit url
447
+ test_response_of url
450
448
  end
451
449
  ```
452
450
 
453
451
  #### Matchers
454
452
 
453
+ The matchers _should not_ be used in acceptance specs unless the default DSL is
454
+ not adapted for a particular use case (and I can't imagine one now).
455
+
456
+ For the sake of comprehension, the two custom matchers still described.
457
+
455
458
  ##### `have_one(type)`
456
459
 
457
460
  Expects the compared content to be a hash with the same keys as a
data/Rakefile CHANGED
@@ -2,7 +2,10 @@
2
2
 
3
3
  require 'bundler/gem_tasks'
4
4
  require 'rspec/core/rake_task'
5
+ require 'yard'
5
6
 
6
7
  RSpec::Core::RakeTask.new(:spec)
7
8
 
8
9
  task default: :spec
10
+
11
+ YARD::Rake::YardocTask.new
@@ -6,7 +6,16 @@ module RSpec
6
6
  module DSL
7
7
  # These methods will be available in examples (i.e.: 'for_code')
8
8
  module Example
9
- def visit(example, path_params: {}, payload: {}, headers: {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
9
+ ##
10
+ # Visits the current example and tests the response
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
16
+ #
17
+ # @return [void]
18
+ def test_response_of(example, path_params: {}, payload: {}, headers: {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
10
19
  raise 'Missing context. Call visit with for_code context.' unless example
11
20
 
12
21
  status_code = prepare_status_code example.class.description
@@ -23,39 +32,74 @@ module RSpec
23
32
 
24
33
  return if example.class.description.match?(/-> test (\d+)(.*)/)
25
34
 
26
- set_request_example example.class.metadata[:rrad], request_params, status_code, response.body
35
+ set_request_example example.class.metadata[:rra], request_params, status_code, response.body
27
36
  end
28
37
 
38
+ private
39
+
40
+ ##
41
+ # Searches for a defined entity in metadata
42
+ #
43
+ # @param entity [Symbol] Entity reference
44
+ #
45
+ # @return [RSpec::Rails::Api::EntityConfig, Hash] Defined entity
29
46
  def defined(entity)
30
- current_resource = self.class.metadata[:rrad].current_resource
47
+ return { type: entity.to_s.split('_').last.to_sym } if PRIMITIVES.include? entity
48
+
49
+ current_resource = rra_metadata.current_resource
31
50
  raise '@current_resource is unset' unless current_resource
32
51
 
33
- entities = self.class.metadata[:rrad].resources[current_resource][:entities]
52
+ entities = rra_metadata.resources[current_resource][:entities]
34
53
 
35
54
  out = entities[entity]
36
- raise "Unkown entity '#{entity}' in resource '#{current_resource}'" unless out
55
+ raise "Unknown entity '#{entity}' in resource '#{current_resource}'" unless out
37
56
 
38
57
  out.expand_with(entities)
39
58
  end
40
59
 
41
- private
42
-
43
- def check_response(response, expected_code)
60
+ ##
61
+ # Performs various tests on the response
62
+ #
63
+ # @param response [ActionDispatch::TestResponse] The response
64
+ # @param expected_code [Number] Code to test for
65
+ def check_response(response, expected_code) # rubocop:disable Metrics/AbcSize
44
66
  expect(response.status).to eq expected_code
45
67
  expect(response.headers['Content-Type']).to eq 'application/json; charset=utf-8' if expected_code != 204
68
+ expectations = rra_current_example[:expectations]
69
+ expect(response).to have_many defined(expectations[:many]) if expectations[:many]
70
+ expect(response).to have_one defined(expectations[:one]) if expectations[:one]
71
+ expect(response.body).to eq '' if expectations[:none]
46
72
  end
47
73
 
48
- def set_request_example(rrad_metadata, request_params, status_code = nil, response = nil)
49
- rrad_metadata.add_request_example(url: request_params[:example_url],
50
- action: request_params[:action],
51
- status_code: status_code,
52
- response: response,
53
- path_params: request_params[:path_params],
54
- params: request_params[:params])
74
+ ##
75
+ # Adds a request example information to metadata
76
+ #
77
+ # @param rra_metadata [RSpec::Rails::Api::Metadata]
78
+ # @param request_params [Hash]
79
+ # @param status_code [Integer, nil]
80
+ # @param response [String, nil]
81
+ #
82
+ # @return [void]
83
+ def set_request_example(rra_metadata, request_params, status_code = nil, response = nil)
84
+ rra_metadata.add_request_example(url: request_params[:example_url],
85
+ action: request_params[:action],
86
+ status_code: status_code,
87
+ response: response,
88
+ path_params: request_params[:path_params],
89
+ params: request_params[:params])
55
90
  end
56
91
 
92
+ ##
93
+ # Prepares the options for a request
94
+ #
95
+ # @param description [String] RSpec example description
96
+ # @param request_params [Hash] Request parameters
97
+ # @param payload [Hash] Request body
98
+ # @param request_headers [Hash] Custom headers
99
+ #
100
+ # @return [Hash] Options for the request
57
101
  def prepare_request_params(description, request_params = {}, payload = {}, request_headers = {})
58
- example_params = description.split ' '
102
+ example_params = description.split
59
103
 
60
104
  {
61
105
  action: example_params[0].downcase,
@@ -66,7 +110,13 @@ module RSpec
66
110
  }
67
111
  end
68
112
 
113
+ ##
69
114
  # Replace path params by values
115
+ #
116
+ # @param url [String] Url definition
117
+ # @param request_params [Hash] Request parameters
118
+ #
119
+ # @return [String] Actual path to visit
70
120
  def prepare_request_url(url, request_params)
71
121
  url.gsub(/(?::(\w*))/) do |e|
72
122
  symbol = e.sub(':', '').to_sym
@@ -80,6 +130,12 @@ module RSpec
80
130
  end
81
131
  end
82
132
 
133
+ ##
134
+ # Prepares request headers
135
+ #
136
+ # @param headers [Hash] Custom headers
137
+ #
138
+ # @return [Hash] Headers to use in request
83
139
  def prepare_request_headers(headers = {})
84
140
  {
85
141
  'Accept' => 'application/json',
@@ -87,6 +143,12 @@ module RSpec
87
143
  }.merge headers
88
144
  end
89
145
 
146
+ ##
147
+ # Extracts status code from RSpec example description
148
+ #
149
+ # @param description [String] RSpec example description
150
+ #
151
+ # @return [Integer] Status code
90
152
  def prepare_status_code(description)
91
153
  code_match = /->(?: test)? (\d+) - .*/.match description
92
154
 
@@ -94,6 +156,22 @@ module RSpec
94
156
 
95
157
  code_match[1].to_i
96
158
  end
159
+
160
+ ##
161
+ # Returns the current example configuration from metadata
162
+ #
163
+ # @return [Hash] Current example configuration
164
+ def rra_current_example
165
+ self.class.metadata[:rra_current_example]
166
+ end
167
+
168
+ ##
169
+ # Returns the whole Rspec-rails-API metadata object
170
+ #
171
+ # @return [RSpec::Rails::Api::Metadata] Rspec-rails-API metadata object
172
+ def rra_metadata
173
+ self.class.metadata[:rra]
174
+ end
97
175
  end
98
176
  end
99
177
  end