rspec-rails-api 0.3.4 → 0.5.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.
@@ -1,15 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec/rails/api/utils'
4
+ require 'rspec/rails/api/validator'
4
5
  require 'rspec/rails/api/open_api_renderer'
5
6
  require 'rspec/rails/api/entity_config'
6
7
 
7
8
  module RSpec
8
9
  module Rails
9
10
  module Api
10
- # Handles contexts and examples metadatas.
11
+ # Handles contexts and examples metadata.
11
12
  class Metadata # rubocop:disable Metrics/ClassLength
12
- attr_reader :entities, :resources, :current_resource, :parameters
13
+ attr_reader :entities, :resources, :parameters, :current_resource, :current_url, :current_method, :current_code
13
14
 
14
15
  def initialize
15
16
  @resources = {}
@@ -22,37 +23,65 @@ module RSpec
22
23
  @current_code = nil
23
24
  end
24
25
 
26
+ ##
27
+ # Adds a resource to metadata
28
+ #
29
+ # @param name [String] Resource name
30
+ # @param description [String] Resource description
31
+ #
32
+ # @return [void]
25
33
  def add_resource(name, description)
26
- @resources[name.to_sym] = { description: description, paths: {} }
34
+ @resources[name.to_sym] = { description: description, paths: {}, entities: {} }
27
35
  @current_resource = name.to_sym
28
36
  end
29
37
 
30
- def add_entity(type, fields)
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)
31
47
  Utils.deep_set(@resources,
32
- "#{@current_resource}.entities.#{type}",
48
+ [@current_resource, 'entities', name],
33
49
  EntityConfig.new(fields))
34
50
  end
35
51
 
36
- def add_parameter(type, fields)
37
- raise "Parameter #{type} is already defined" if @parameters[type]
52
+ ##
53
+ # Adds a parameter definition
54
+ #
55
+ # @param name [Symbol] Parameter definition name
56
+ # @param fields [Hash] Fields definitions
57
+ #
58
+ # @return [void]
59
+ def add_parameter(name, fields)
60
+ raise "Parameter #{name} is already defined" if @parameters[name]
38
61
 
39
62
  fields.each_value do |field|
40
63
  field[:required] = true unless field[:required] == false
41
64
  end
42
- @parameters[type] = fields
65
+ @parameters[name] = fields
43
66
  end
44
67
 
68
+ ##
69
+ # Adds path parameters definition
70
+ #
71
+ # @param fields [Hash] Parameters definitions
72
+ #
73
+ # @return [void]
45
74
  def add_path_params(fields) # rubocop:disable Metrics/MethodLength
46
75
  check_current_context :resource, :url
47
76
 
48
77
  chunks = @current_url.split('?')
49
78
 
50
79
  fields.each do |name, field|
51
- valid_attribute = Utils.check_attribute_type(field[:type], except: %i[array object])
80
+ valid_attribute = Validator.valid_type?(field[:type], except: %i[array object])
52
81
  raise "Field type not allowed: #{field[:type]}" unless valid_attribute
53
82
 
54
83
  scope = path_param_scope(chunks, name)
55
- Utils.deep_set(@resources, "#{@current_resource}.paths.#{@current_url}.path_params.#{name}",
84
+ Utils.deep_set(@resources, [@current_resource, 'paths', @current_url, 'path_params', name],
56
85
  description: field[:description] || nil,
57
86
  type: field[:type] || nil,
58
87
  required: field[:required] || true,
@@ -60,6 +89,9 @@ module RSpec
60
89
  end
61
90
  end
62
91
 
92
+ ##
93
+ # Add request parameters (_body_)
94
+ #
63
95
  # Fields should be something like:
64
96
  # id: {type: :number, description: 'Something'},
65
97
  # name: {type: string, description: 'Something'}
@@ -69,20 +101,34 @@ module RSpec
69
101
  # property: {type: :string, description: 'Something'},
70
102
  # ...
71
103
  # }}
104
+ #
105
+ # @param fields [Hash] Parameters definitions
106
+ #
107
+ # @return [void]
72
108
  def add_request_params(fields)
73
109
  check_current_context :resource, :url, :method
74
110
 
75
111
  params = organize_params fields
76
112
  Utils.deep_set(@resources,
77
- "#{@current_resource}.paths.#{@current_url}.actions.#{@current_method}.params",
113
+ [@current_resource, 'paths', @current_url, 'actions', @current_method, 'params'],
78
114
  params)
79
115
  end
80
116
 
81
- def add_action(method, url, description)
117
+ ##
118
+ # Adds an action and sets `@current_url` and `@current_method`
119
+ #
120
+ # @param method [:get, :post, :put, :patch, delete] Method name
121
+ # @param url [String] Associated URL
122
+ # @param summary [String] What the route does for given method
123
+ # @param description [String] Longer description of this action
124
+ #
125
+ # @return [void]
126
+ def add_action(method, url, summary, description = '')
82
127
  check_current_context :resource
83
128
 
84
- Utils.deep_set(@resources, "#{@current_resource}.paths.#{url}.actions.#{method}",
85
- description: description,
129
+ Utils.deep_set(@resources, [@current_resource, 'paths', url, 'actions', method],
130
+ description: description || '',
131
+ summary: summary,
86
132
  statuses: {},
87
133
  params: {})
88
134
 
@@ -90,35 +136,91 @@ module RSpec
90
136
  @current_method = method
91
137
  end
92
138
 
139
+ ##
140
+ # Adds a status code to metadata and sets `@current_code`
141
+ #
142
+ # @param status_code [Integer] The status code
143
+ # @param description [String] Code description
144
+ #
145
+ # @return [void]
146
+ #
93
147
  # rubocop:disable Layout/LineLength
94
148
  def add_status_code(status_code, description)
95
149
  check_current_context :resource, :url, :method
96
150
 
97
151
  Utils.deep_set(@resources,
98
- "#{@current_resource}.paths.#{@current_url}.actions.#{@current_method}.statuses.#{status_code}",
152
+ [@current_resource, 'paths', @current_url, 'actions', @current_method, 'statuses', status_code],
99
153
  description: description,
100
154
  example: { response: nil })
101
155
  @current_code = status_code
102
156
  end
103
157
  # rubocop:enable Layout/LineLength
104
158
 
159
+ ##
160
+ # Gets the current example
161
+ #
162
+ # @return [Hash] Current example metadata
163
+ def current_example
164
+ @resources.dig @current_resource,
165
+ :paths,
166
+ @current_url.to_sym,
167
+ :actions,
168
+ @current_method.to_sym,
169
+ :statuses,
170
+ @current_code.to_s.to_sym
171
+ end
172
+
173
+ ##
174
+ # Adds expectations for current example
175
+ #
176
+ # @param one [Hash, nil] Entity definition
177
+ # @param many [Hash, nil] Entity definition
178
+ #
179
+ # @return [void]
180
+ def add_expectations(one, many)
181
+ check_current_context :resource, :url, :method, :code
182
+ none = !many && !one
183
+
184
+ # rubocop:disable Layout/LineLength
185
+ Utils.deep_set(@resources,
186
+ [@current_resource, 'paths', @current_url, 'actions', @current_method, 'statuses', @current_code, 'expectations'],
187
+ {
188
+ one: one,
189
+ many: many,
190
+ none: none,
191
+ })
192
+ # rubocop:enable Layout/LineLength
193
+ end
194
+
195
+ ##
196
+ # Adds a request example
197
+ #
198
+ # @param url [String, nil] Visited URL
199
+ # @param action [String, nil] HTTP verb
200
+ # @param status_code [Integer, nil] Status code
201
+ # @param response [String, nil] Response body
202
+ # @param path_params [Hash, nil] Used path parameters
203
+ # @param params [Hash, nil] Used body parameters
204
+ #
105
205
  # rubocop:disable Metrics/ParameterLists
106
206
  def add_request_example(url: nil, action: nil, status_code: nil, response: nil, path_params: nil, params: nil)
107
207
  resource = nil
108
208
  @resources.each do |key, res|
109
- resource = key if Utils.deep_get(res, "paths.#{url}.actions.#{action}.statuses.#{status_code}")
209
+ resource = key if res.dig :paths, url.to_sym, :actions, action.to_sym, :statuses, status_code.to_s.to_sym
110
210
  end
111
211
 
112
212
  raise "Resource not found for #{action.upcase} #{url}" unless resource
113
213
 
114
214
  Utils.deep_set(@resources,
115
- "#{resource}.paths.#{url}.actions.#{action}.statuses.#{status_code}.example",
215
+ [resource, 'paths', url, 'actions', action, 'statuses', status_code, 'example'],
116
216
  path_params: path_params,
117
217
  params: params,
118
218
  response: response)
119
219
  end
120
220
  # rubocop:enable Metrics/ParameterLists
121
221
 
222
+ ##
223
+ # @return [Hash] Hash representation of the metadata
122
224
  def to_h
123
225
  {
124
226
  resources: @resources,
@@ -128,6 +230,13 @@ module RSpec
128
230
 
129
231
  private
130
232
 
233
+ ##
234
+ # Checks for the definition of given scopes.
235
+ # This is useful to verify if all metadata is set for the current example
236
+ #
237
+ # @param scope [Symbol[]] List of scope to check for
238
+ #
239
+ # @return [Boolean]
131
240
  def check_current_context(*scope) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
132
241
  scope ||= []
133
242
  raise 'No resource declared' if scope.include?(:resource) && !@current_resource
@@ -136,6 +245,13 @@ module RSpec
136
245
  raise 'No status code declared' if scope.include?(:code) && !@current_code
137
246
  end
138
247
 
248
+ ##
249
+ # Checks if a given parameter is used in the URL (_path_) or querystring (query)
250
+ #
251
+ # @param url_chunks [String[]] Chunks of an url splitted on the query separator (`?`)
252
+ # @param name [Symbol] Name of the parameter
253
+ #
254
+ # @return [:path, :query]
139
255
  def path_param_scope(url_chunks, name)
140
256
  if /:#{name}/.match?(url_chunks[0])
141
257
  :path
@@ -146,15 +262,25 @@ module RSpec
146
262
  end
147
263
  end
148
264
 
149
- def organize_params(fields) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
265
+ ##
266
+ # Checks and complete a field definition
267
+ #
268
+ # @param fields [Hash,Symbol] Fields definitions
269
+ #
270
+ # @return [Hash,Symbol] Completed field definition
271
+ def organize_params(fields) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
272
+ return fields if fields.is_a?(Symbol) && PRIMITIVES.include?(fields)
273
+ raise "Unsupported type \"#{fields}\"" unless fields.is_a? Hash
274
+
150
275
  out = { properties: {} }
151
276
  required = []
277
+
152
278
  allowed_types = %i[array object]
153
279
  fields.each do |name, field|
154
280
  allowed_type = allowed_types.include?(field[:type]) || PARAM_TYPES.key?(field[:type])
155
281
  raise "Field type not allowed: #{field[:type]}" unless allowed_type
156
282
 
157
- required.push name.to_s if field[:required]
283
+ required.push name.to_s if field[:required] != false
158
284
 
159
285
  out[:properties][name] = fill_request_param field
160
286
  end
@@ -162,9 +288,15 @@ module RSpec
162
288
  out
163
289
  end
164
290
 
291
+ ##
292
+ # Checks and completes a request parameter definition
293
+ #
294
+ # @param field [Hash] Parameter definition
295
+ #
296
+ # @return [Hash] Completed parameter
165
297
  def fill_request_param(field)
166
- if field[:type] == :object && field[:properties]
167
- organize_params field[:properties]
298
+ if field[:type] == :object && field[:attributes]
299
+ organize_params field[:attributes]
168
300
  else
169
301
  properties = {
170
302
  type: PARAM_TYPES[field[:type]][:type],
@@ -6,7 +6,7 @@ require 'active_support'
6
6
  module RSpec
7
7
  module Rails
8
8
  module Api
9
- # Class to render metadatas.
9
+ # Class to render metadata.
10
10
  # Example:
11
11
  # ```rb
12
12
  # renderer = RSpec::Rails::Api::OpenApiRenderer.new
@@ -27,46 +27,80 @@ module RSpec
27
27
  @api_license = {}
28
28
  end
29
29
 
30
+ ##
31
+ # Merges example context definition with the renderer data
32
+ #
33
+ # @param context [Hash] Metadata hash
34
+ # @param dump_metadata [Boolean] Saves the raw metadata in `tmp/rra_metadata.yaml` for debugging
35
+ #
36
+ # @return [void
30
37
  def merge_context(context, dump_metadata: false)
31
- @metadata[:resources].deep_merge! context[:resources]
32
- @metadata[:entities].deep_merge! context[:entities]
38
+ @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]
33
40
 
34
41
  # Save context for debug and fixtures
35
- File.write ::Rails.root.join('tmp', 'rra_metadata.yaml'), context.to_yaml if dump_metadata
42
+ File.write ::Rails.root.join('tmp', 'rra_metadata.yaml'), @metadata.to_yaml if dump_metadata
36
43
  end
37
44
 
45
+ ##
46
+ # Write OpenAPI files
47
+ #
48
+ # @param path [String, nil] Where to save the files. Defaults to `/tmp/rspec_api_rails.*` when unset
49
+ # @param only [[Symbol]] Formats to save the file to. Allowed values are `:yaml` and `:json`
50
+ #
51
+ # @return [void]
38
52
  def write_files(path = nil, only: %i[yaml json])
39
- content = prepare_metadata
53
+ return unless write_file? RSpec.world.filtered_examples
54
+
40
55
  path ||= ::Rails.root.join('tmp', 'rspec_api_rails')
41
56
 
42
57
  file_types = %i[yaml json]
43
-
44
58
  only.each do |type|
45
59
  next unless file_types.include? type
46
60
 
47
- File.write "#{path}.#{type}", JSON.parse(JSON.pretty_generate(content)).send("to_#{type}")
61
+ File.write "#{path}.#{type}", prepare_metadata.send("to_#{type}")
48
62
  end
49
63
  end
50
64
 
65
+ ##
66
+ # Extracts metadata from context to generate an OpenAPI structure
67
+ #
68
+ # @return [Hash] The OpenAPI structure
51
69
  def prepare_metadata
70
+ extract_metadata
52
71
  # Example: https://github.com/OAI/OpenAPI-Specification/blob/master/examples/v3.0/petstore-expanded.yaml
53
- extract_metadatas
54
- {
72
+ hash = {
55
73
  openapi: '3.0.0',
56
74
  info: @api_infos,
57
75
  servers: @api_servers,
58
76
  paths: @api_paths,
59
77
  components: @api_components,
60
78
  tags: @api_tags,
61
- }.deep_stringify_keys
79
+ }
80
+ JSON.parse(JSON.pretty_generate(hash))
62
81
  end
63
82
 
83
+ ##
84
+ # Sets the contact field
85
+ #
86
+ # @param name [String, nil] Contact name
87
+ # @param email [String, nil] Contact Email
88
+ # @param url [String, nil] Contact URL
89
+ #
90
+ # @return [void]
64
91
  def api_contact=(name: nil, email: nil, url: nil)
65
92
  @api_contact[:name] = name if name
66
93
  @api_contact[:email] = email if email
67
94
  @api_contact[:url] = url if url
68
95
  end
69
96
 
97
+ ##
98
+ # Sets the license field
99
+ #
100
+ # @param name [String, nil] License name
101
+ # @param url [String, nil] License URL
102
+ #
103
+ # @return [void]
70
104
  def api_license=(name: nil, url: nil)
71
105
  @api_license[:name] = name if name
72
106
  @api_license[:url] = url if url
@@ -74,14 +108,38 @@ module RSpec
74
108
 
75
109
  private
76
110
 
77
- def extract_metadatas
111
+ def write_file?(examples)
112
+ acceptance_examples = examples.values.flatten.filter do |e|
113
+ e.metadata[:type] == :acceptance
114
+ end
115
+ unless acceptance_examples.none?(&:exception)
116
+ puts "\n\e[00;31mSome acceptance tests failed. OpenApi specification file was not updated.\n\e[00m"
117
+ return false
118
+ end
119
+
120
+ true
121
+ end
122
+
123
+ ##
124
+ # Extracts metadata for rendering
125
+ #
126
+ # @return [void]
127
+ def extract_metadata
78
128
  extract_from_resources
79
129
  api_infos
80
130
  api_servers
81
131
  end
82
132
 
83
- def extract_from_resources
133
+ ##
134
+ # Extracts metadata from resources for rendering
135
+ #
136
+ # @return [void]
137
+ def extract_from_resources # rubocop:disable Metrics/MethodLength
138
+ @api_components[:schemas] ||= {}
84
139
  @metadata[:resources].each do |resource_key, resource|
140
+ resource[:entities].each do |name, entity|
141
+ @api_components[:schemas][name] = process_entity(entity)
142
+ end
85
143
  @api_tags.push(
86
144
  name: resource_key.to_s,
87
145
  description: resource[:description]
@@ -90,6 +148,14 @@ module RSpec
90
148
  end
91
149
  end
92
150
 
151
+ ##
152
+ # Processes a resource from metadata
153
+ #
154
+ # @param resource [Symbol, nil] Resource name
155
+ # @param resource_config [Hash, nil] Resource declaration
156
+ #
157
+ #
158
+ # @return [void]
93
159
  def process_resource(resource: nil, resource_config: nil) # rubocop:disable Metrics/MethodLength
94
160
  http_verbs = %i[get post put patch delete]
95
161
  resource_config[:paths].each do |path_key, path|
@@ -111,6 +177,10 @@ module RSpec
111
177
  end
112
178
  end
113
179
 
180
+ ##
181
+ # Processes path parameters for rendering
182
+ #
183
+ # @param params [Hash] Path parameters
114
184
  def process_path_params(params)
115
185
  parameters = []
116
186
  params.each do |name, param|
@@ -120,6 +190,37 @@ module RSpec
120
190
  parameters
121
191
  end
122
192
 
193
+ def process_entity(entity) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
194
+ schema = {
195
+ properties: {},
196
+ }
197
+ required = []
198
+ entity.fields.each do |name, field|
199
+ property = {
200
+ description: field.description,
201
+ type: PARAM_TYPES[field.type][:type],
202
+ }
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
207
+
208
+ required.push name unless field.required == false
209
+ end
210
+
211
+ schema[:required] = required unless required.size.zero?
212
+
213
+ schema
214
+ end
215
+
216
+ ##
217
+ # Processes a path parameter from metadata
218
+ #
219
+ # @param name [Symbol, nil] Parameter name
220
+ # @param param [Hash, nil] Parameter declaration
221
+ #
222
+ #
223
+ # @return [void]
123
224
  def process_path_param(name, param) # rubocop:disable Metrics/MethodLength
124
225
  parameter = {
125
226
  name: name.to_s,
@@ -136,6 +237,19 @@ module RSpec
136
237
  parameter
137
238
  end
138
239
 
240
+ ##
241
+ # Processes an action from metadata
242
+ #
243
+ # @param resource [Symbol, nil] Target resource
244
+ # @param path [Symbol, nil] Target path
245
+ # @param path_config [Hash, nil] Path configuraton
246
+ # @param action_config [Symbol, nil] Target action
247
+ # @param parameters [Array, nil] Path parameters
248
+ #
249
+ # @return [void]
250
+ #
251
+ # FIXME: Rename "action_config" to "action"
252
+ # FIXME: Rename "parameters" to "path_parameters"
139
253
  # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
140
254
  def process_action(resource: nil, path: nil, path_config: nil, action_config: nil, parameters: nil)
141
255
  responses = {}
@@ -154,10 +268,11 @@ module RSpec
154
268
  responses[status_key] = process_response status: status_key, status_config: status, content: content
155
269
  end
156
270
 
157
- description = path_config[:actions][action_config][:description]
158
- action = {
159
- description: description,
160
- operationId: "#{resource} #{description}".downcase.gsub(/[^\w]/, '_'),
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]/, '_'),
161
276
  parameters: parameters,
162
277
  responses: responses,
163
278
  tags: [resource.to_s],
@@ -169,8 +284,16 @@ module RSpec
169
284
  end
170
285
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
171
286
 
287
+ ##
288
+ # Processes a request body from metadata
289
+ #
290
+ # @param schema [Hash] Schema
291
+ # @param ref [String] Reference
292
+ # @param examples [Hash] Example
293
+ #
294
+ # @return [void]
172
295
  def process_request_body(schema: nil, ref: nil, examples: {})
173
- Utils.deep_set @api_components, "schemas.#{ref}", schema
296
+ Utils.deep_set @api_components, ['schemas', ref], schema
174
297
  {
175
298
  # description: '',
176
299
  required: true,
@@ -183,15 +306,22 @@ module RSpec
183
306
  }
184
307
  end
185
308
 
309
+ ##
310
+ # Process a response from metadata
311
+ #
312
+ # @param status [Symbol] Status code
313
+ # @param status_config [Hash] Configuration for status code
314
+ # @param content [String] Response content
315
+ #
316
+ # @return [void]
186
317
  def process_response(status: nil, status_config: nil, content: nil)
187
- response = {
188
- description: status_config[:description],
189
- }
318
+ response = { description: status_config[:description] }
190
319
 
191
320
  return response if status.to_s == '204' && content # No content
192
321
 
193
322
  response[:content] = {
194
323
  'application/json': {
324
+ schema: response_schema(status_config[:expectations]),
195
325
  examples: { default: { value: JSON.parse(content) } },
196
326
  },
197
327
  }
@@ -199,6 +329,25 @@ module RSpec
199
329
  response
200
330
  end
201
331
 
332
+ def response_schema(expectations)
333
+ if expectations[:many]
334
+ items = if PRIMITIVES.include?(expectations[:many])
335
+ { type: expectations[:many] }
336
+ else
337
+ { '$ref' => "#/components/schemas/#{expectations[:many]}" }
338
+ end
339
+ { type: 'array', items: items }
340
+ elsif expectations[:one]
341
+ { '$ref' => "#/components/schemas/#{expectations[:one]}" }
342
+ end
343
+ end
344
+
345
+ ##
346
+ # Processes examples from statuses
347
+ #
348
+ # @param statuses [Hash]
349
+ #
350
+ # @return [Hash] Request examples
202
351
  def process_examples(statuses)
203
352
  request_examples = {}
204
353
 
@@ -212,16 +361,32 @@ module RSpec
212
361
  request_examples
213
362
  end
214
363
 
364
+ ##
365
+ # Converts path with params like ":id" to their OpenAPI representation
366
+ #
367
+ # @param string [String] The original path
368
+ #
369
+ # @return [String] OpenAPI path representation
215
370
  def path_with_params(string)
216
371
  string.gsub(/(?::(\w*))/) do |e|
217
372
  "{#{e.sub(':', '')}}"
218
373
  end
219
374
  end
220
375
 
376
+ ##
377
+ # Converts a string to a snake_cased string to use as operationId
378
+ #
379
+ # @param string [String] Original string
380
+ #
381
+ # @return [String] Snake_cased string
221
382
  def escape_operation_id(string)
222
383
  string.downcase.gsub(/[^\w]+/, '_')
223
384
  end
224
385
 
386
+ ##
387
+ # Fills the API general information sections
388
+ #
389
+ # @return [void]
225
390
  def api_infos
226
391
  @api_infos = {
227
392
  title: @api_title || 'Some sample app',
@@ -235,6 +400,10 @@ module RSpec
235
400
  @api_infos
236
401
  end
237
402
 
403
+ ##
404
+ # Fills the API servers section
405
+ #
406
+ # @return [void]
238
407
  def api_servers
239
408
  @api_servers || [
240
409
  { url: 'http://api.example.com' },