rspec-rails-api 0.2.0 → 0.3.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '0861008371cafa6cab0cda5c165acda54b5a3047f152713bb684ae0c7c7be0ff'
4
- data.tar.gz: d3c8877c5ef1e43c902010b68b9e483feefa6e57251d67aba325394a9682aee7
3
+ metadata.gz: d574f6e06c94fc87d2db77079f80a341da7455b5776c3513a7e84b5b80165090
4
+ data.tar.gz: 23ddbfe59c098baa1ffcec4dce21d5d6ba25f8fe0143d965d11a8bee25771aad
5
5
  SHA512:
6
- metadata.gz: e1009f40d78187888e546c74e62e88d7557f45a4a3cfed78a27785d63b401f673ed1796928f1d32823a665694b86761653bb87edc68522418d6117c4b576e362
7
- data.tar.gz: 10ffebb4902456c4671b39b7dd707ea8ca870b9a27054c3139cae94bb3dcebfc3f88abd41a298ed870f98d7953c1195171cbf8f077db2dcd6530fe761e354a5a
6
+ metadata.gz: 3430d1d5da42f6c7bf9d28f6c6e6a74ff332fb3cd31cf92d170d9c87f76527dd0d64d6ae510ae19873d7ca078c4405a05154d867f100f804e806c6e6fc342049
7
+ data.tar.gz: f251c30d40efaeef76bfd3ea0f313834dc7323ab56dc07ddf1ecc845b033160dd29afd053d3de3b0a2d7afee7092e858a1844795296e6593d9598f0d17f4df58
data/.gitlab-ci.yml CHANGED
@@ -1,5 +1,5 @@
1
1
  ---
2
- image: ruby:2.4
2
+ image: ruby:2.5
3
3
 
4
4
  stages:
5
5
  - prepare
@@ -35,9 +35,10 @@ rspec:
35
35
  dependencies:
36
36
  - bundle
37
37
 
38
- rspec-dummy:
38
+ dummy:
39
39
  stage: test
40
40
  script:
41
41
  - cd dummy
42
42
  - bundle install --path='vendor/bundle'
43
+ - bundle exec rubocop
43
44
  - bundle exec rspec
data/.rubocop.yml CHANGED
@@ -3,25 +3,27 @@ require:
3
3
  - rubocop-performance
4
4
 
5
5
  AllCops:
6
+ TargetRubyVersion: 2.5
6
7
  Exclude:
7
8
  - dummy/**/*
8
9
  - vendor/bundle/**/*
10
+ NewCops: enable
9
11
 
10
- Metrics/BlockLength:
12
+ Layout/LineLength:
13
+ Max: 120
11
14
  Exclude:
12
- - rspec-rails-api.gemspec
13
15
  - spec/**/*_spec.rb
14
16
 
15
- Metrics/LineLength:
16
- Max: 120
17
+ Metrics/BlockLength:
17
18
  Exclude:
19
+ - rspec-rails-api.gemspec
18
20
  - spec/**/*_spec.rb
19
21
 
20
- Naming/UncommunicativeMethodParamName:
22
+ Naming/MethodParameterName:
21
23
  AllowedNames:
22
24
  - of
23
25
 
24
- Layout/AlignHash:
26
+ Layout/HashAlignment:
25
27
  EnforcedColonStyle: table
26
28
  EnforcedHashRocketStyle: table
27
29
 
data/CHANGELOG.md CHANGED
@@ -3,6 +3,36 @@ All notable changes to this project will be documented in this file.
3
3
 
4
4
  ## Not released
5
5
 
6
+ ## 0.3.2 - 2021-03-09
7
+ - Fix YAML rendering (ruby objects were sometimes rendered in documentation)
8
+ - Render examples results as YAML/JSON objects instead of text blocks.
9
+
10
+ ## 0.3.1 - 2020-04-09
11
+ - Add support for "test only" examples, allowing to write examples without documentation.
12
+
13
+ ## 0.3.0 - 2019-12-26
14
+
15
+ ### Changed
16
+ - Rails 6 support, deprecated methods from Rails 5 are not supported. Use version `0.2.3`
17
+ of this gem if your application is still on 5.
18
+
19
+ ## 0.2.3 - 2019-12-04
20
+
21
+ ### Improved
22
+
23
+ - Generated Swagger file now use the payloads of POST/PATCH/PUT requests.
24
+ - Minimum Ruby version is now specified: Ruby 2.3.3.
25
+
26
+ ## 0.2.1/0.2.2 - 2019-11-03
27
+
28
+ _Version 0.2.1 was released and yanked by mistake. Version 0.2.2 is the exact
29
+ same one, with a version bump_
30
+
31
+ ### Changed
32
+
33
+ - `for_code` method now have its `description` optional. If none is provided,
34
+ the description will be set from the status code.
35
+
6
36
  ## 0.2.0 - 2019-11-02
7
37
 
8
38
  ### Added
@@ -40,7 +70,7 @@ of the fixtures.
40
70
  ### Added
41
71
 
42
72
  - Added ability to document API descriptions, servers, etc... from the RSpec helper files
43
-
73
+
44
74
  ## 0.1.2 - 2019-10-22
45
75
 
46
76
  ### Added
data/README.md CHANGED
@@ -1,15 +1,14 @@
1
1
  # RSpec-rails-api
2
2
 
3
- > An RSpec plugin to test Rails api responses and generate swagger
3
+ > An RSpec plugin to test Rails API responses and generate swagger
4
4
  > documentation
5
5
 
6
6
  **This is a work in progress** but you're welcome to help, test, submit
7
7
  issues, ...
8
8
 
9
- ## Installation
9
+ **Note** For Rails 5, use version 0.2.3
10
10
 
11
- As the gem is not yet published, you have to specify its git repository
12
- in order to test it.
11
+ ## Installation
13
12
 
14
13
  Add this line to your application's Gemfile:
15
14
 
@@ -381,7 +380,7 @@ on_post '/api/items' do
381
380
  end
382
381
  ```
383
382
 
384
- ##### `for_code(http_status, description, doc_only: false &block)`
383
+ ##### `for_code(http_status, description = nil, doc_only: false, test_only: false &block)`
385
384
 
386
385
  Describes the desired output for a precedently defined URL.
387
386
 
@@ -389,12 +388,18 @@ Block takes one required argument, that should be passed to `visit`.
389
388
  This argument will contain the block context and allow `visit` to access
390
389
  the metadatas.
391
390
 
391
+ You can have only one documented code per action/url, unless you use
392
+ `test_only`.
393
+
392
394
  - `http_status` is an integer representing an
393
395
  [HTTP status](https://httpstat.us/)
394
396
  - `description` should be some valid
395
- [CommonMark](https://commonmark.org/)
397
+ [CommonMark](https://commonmark.org/). If not defined, a human readable
398
+ translation of the `http_status` will be used.
396
399
  - `doc_only` can be set to true to temporarily disable block execution
397
400
  and only create the documentation (without examples).
401
+ - `test_only` will omit the test from the documentation. Useful when you
402
+ need to test things _around_ the call (response content, db,...)
398
403
  - `block` where additional tests can be performed. If `visit()` is
399
404
  called within the block, its output will be used in documentation
400
405
  examples, and the response type and code will actually be tested.
@@ -412,6 +417,11 @@ Once again, you have to pass an argument to the block if you use
412
417
  visit url
413
418
  # ...
414
419
  end
420
+
421
+ for_code 200, 'Side test', test_only: true do |url|
422
+ visit url
423
+ # ...
424
+ end
415
425
  # ...
416
426
  ```
417
427
 
@@ -6,11 +6,13 @@ 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
9
+ def visit(example, path_params: {}, payload: {}, headers: {}) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
10
10
  raise 'Missing context. Call visit with for_code context.' unless example
11
11
 
12
- status_code = prepare_status_code example.class.description
13
- request_params = prepare_request_params example.class.parent.description, path_params, payload, headers
12
+ status_code = prepare_status_code example.class.description
13
+
14
+ request_params = prepare_request_params example.class.module_parent.description,
15
+ path_params, payload, headers
14
16
 
15
17
  send(request_params[:action],
16
18
  request_params[:url],
@@ -19,6 +21,8 @@ module RSpec
19
21
 
20
22
  check_response(response, status_code)
21
23
 
24
+ return if example.class.description.match?(/-> test (\d+)(.*)/)
25
+
22
26
  set_request_example example.class.metadata[:rrad], request_params, status_code, response.body
23
27
  end
24
28
 
@@ -51,7 +55,7 @@ module RSpec
51
55
  end
52
56
 
53
57
  def prepare_request_params(description, request_params = {}, payload = {}, request_headers = {})
54
- example_params = description.split ' '
58
+ example_params = description.split
55
59
 
56
60
  {
57
61
  action: example_params[0].downcase,
@@ -84,7 +88,7 @@ module RSpec
84
88
  end
85
89
 
86
90
  def prepare_status_code(description)
87
- code_match = /-> (\d+) - .*/.match description
91
+ code_match = /->(?: test)? (\d+) - .*/.match description
88
92
 
89
93
  raise 'Please provide a numerical code for the "for_code" block' unless code_match
90
94
 
@@ -74,17 +74,13 @@ module RSpec
74
74
  describe("#{action.upcase} #{url}", &block)
75
75
  end
76
76
 
77
- def for_code(status_code, description, doc_only: false, &block)
78
- metadata[:rrad].add_status_code(status_code, description)
79
-
80
- describe "-> #{status_code} - #{description}" do
81
- if (!ENV['DOC_ONLY'] || ENV['DOC_ONLY'] == 'false' || !doc_only) && block
82
- example 'Test and create documentation', caller: block.send(:caller) do
83
- instance_eval(&block) if block_given?
84
- end
85
- else
86
- document_only status_code
87
- end
77
+ def for_code(status_code, description = nil, doc_only: false, test_only: false, &block)
78
+ description ||= Rack::Utils::HTTP_STATUS_CODES[status_code]
79
+
80
+ metadata[:rrad].add_status_code(status_code, description) unless test_only
81
+
82
+ describe "->#{test_only ? ' test' : ''} #{status_code} - #{description}" do
83
+ execute_for_code_block(status_code, doc_only, block)
88
84
  end
89
85
  end
90
86
 
@@ -93,11 +89,21 @@ module RSpec
93
89
  def document_only(status_code)
94
90
  example 'Create documentation' do |example|
95
91
  parent_example = example.example_group
96
- request_params = prepare_request_params parent_example.parent.description
92
+ request_params = prepare_request_params parent_example.module_parent.description
97
93
 
98
94
  set_request_example parent_example.metadata[:rrad], request_params, status_code
99
95
  end
100
96
  end
97
+
98
+ def execute_for_code_block(status_code, doc_only, callback_block)
99
+ if (!ENV['DOC_ONLY'] || ENV['DOC_ONLY'] == 'false' || !doc_only) && callback_block
100
+ example 'Test and create documentation', caller: callback_block.send(:caller) do
101
+ instance_eval(&callback_block) if callback_block
102
+ end
103
+ else
104
+ document_only status_code
105
+ end
106
+ end
101
107
  end
102
108
  end
103
109
  end
@@ -11,7 +11,7 @@ module RSpec
11
11
  class FieldConfig
12
12
  attr_accessor :required, :type, :attributes, :description
13
13
 
14
- def initialize(type:, required: true, description:, attributes: nil, of: nil)
14
+ def initialize(type:, description:, required: true, attributes: nil, of: nil)
15
15
  @required = required
16
16
  @description = description
17
17
  raise "Field type not allowed: '#{type}'" unless Utils.check_attribute_type(type)
@@ -40,9 +40,10 @@ module RSpec
40
40
  private
41
41
 
42
42
  def define_attributes(attributes)
43
- @attributes = if attributes.is_a? Hash
43
+ @attributes = case attributes
44
+ when Hash
44
45
  @attributes = EntityConfig.new attributes
45
- elsif attributes.is_a? Symbol
46
+ when Symbol
46
47
  attributes
47
48
  end
48
49
  end
@@ -87,7 +87,7 @@ module RSpec
87
87
  @current_method = method
88
88
  end
89
89
 
90
- # rubocop:disable Metrics/LineLength
90
+ # rubocop:disable Layout/LineLength
91
91
  def add_status_code(status_code, description)
92
92
  check_current_context :resource, :url, :method
93
93
 
@@ -97,7 +97,7 @@ module RSpec
97
97
  example: { response: nil })
98
98
  @current_code = status_code
99
99
  end
100
- # rubocop:enable Metrics/LineLength
100
+ # rubocop:enable Layout/LineLength
101
101
 
102
102
  # rubocop:disable Metrics/ParameterLists
103
103
  def add_request_example(url: nil, action: nil, status_code: nil, response: nil, path_params: nil, params: nil)
@@ -125,25 +125,14 @@ module RSpec
125
125
 
126
126
  private
127
127
 
128
- # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Style/GuardClause
129
- def check_current_context(*scope)
128
+ def check_current_context(*scope) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
130
129
  scope ||= []
131
- if scope.include?(:resource)
132
- raise 'No resource declared' unless @current_resource
133
- end
134
- if scope.include?(:method)
135
- raise 'No action declared' unless @current_method
136
- end
137
- if scope.include?(:url)
138
- raise 'No url declared' unless @current_url
139
- end
140
- if scope.include?(:code)
141
- raise 'No status code declared' unless @current_code
142
- end
130
+ raise 'No resource declared' if scope.include?(:resource) && !@current_resource
131
+ raise 'No action declared' if scope.include?(:method) && !@current_method
132
+ raise 'No url declared' if scope.include?(:url) && !@current_url
133
+ raise 'No status code declared' if scope.include?(:code) && !@current_code
143
134
  end
144
135
 
145
- # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength, Style/GuardClause
146
-
147
136
  def path_param_scope(url_chunks, name)
148
137
  if /:#{name}/.match?(url_chunks[0])
149
138
  :path
@@ -154,11 +143,12 @@ module RSpec
154
143
  end
155
144
  end
156
145
 
157
- def organize_params(fields) # rubocop:disable Metrics/AbcSize
146
+ def organize_params(fields) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
158
147
  out = { properties: {} }
159
148
  required = []
149
+ allowed_types = %i[array object]
160
150
  fields.each do |name, field|
161
- allowed_type = %i[array object].include?(field[:type]) || PARAM_TYPES.key?(field[:type])
151
+ allowed_type = allowed_types.include?(field[:type]) || PARAM_TYPES.key?(field[:type])
162
152
  raise "Field type not allowed: #{field[:type]}" unless allowed_type
163
153
 
164
154
  required.push name.to_s if field[:required]
@@ -39,10 +39,12 @@ module RSpec
39
39
  content = prepare_metadata
40
40
  path ||= ::Rails.root.join('tmp', 'rspec_api_rails')
41
41
 
42
+ file_types = %i[yaml json]
43
+
42
44
  only.each do |type|
43
- next unless %i[yaml json].include? type
45
+ next unless file_types.include? type
44
46
 
45
- File.write "#{path}.#{type}", content.send("to_#{type}")
47
+ File.write "#{path}.#{type}", JSON.parse(JSON.pretty_generate(content)).send("to_#{type}")
46
48
  end
47
49
  end
48
50
 
@@ -89,13 +91,14 @@ module RSpec
89
91
  end
90
92
 
91
93
  def process_resource(resource: nil, resource_config: nil) # rubocop:disable Metrics/MethodLength
94
+ http_verbs = %i[get post put patch delete]
92
95
  resource_config[:paths].each do |path_key, path|
93
96
  url = path_with_params path_key.to_s
94
97
  actions = {}
95
98
  parameters = path.key?(:path_params) ? process_path_params(path[:path_params]) : []
96
99
 
97
100
  path[:actions].each_key do |action|
98
- next unless %i[get post put patch delete].include? action
101
+ next unless http_verbs.include? action
99
102
 
100
103
  actions[action] = process_action resource: resource,
101
104
  path: path_key,
@@ -117,7 +120,7 @@ module RSpec
117
120
  parameters
118
121
  end
119
122
 
120
- def process_path_param(name, param) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
123
+ def process_path_param(name, param) # rubocop:disable Metrics/MethodLength
121
124
  parameter = {
122
125
  name: name.to_s,
123
126
  description: param[:description],
@@ -138,12 +141,12 @@ module RSpec
138
141
  responses = {}
139
142
  request_body = nil
140
143
 
141
- if %i[post put patch].include? action_config
142
- if path_config[:actions][action_config][:params].keys.count.positive?
143
- schema = path_config[:actions][action_config][:params]
144
- schema_ref = escape_operation_id("#{action_config}_#{path}")
145
- request_body = process_request_body schema: schema, ref: schema_ref
146
- end
144
+ if %i[post put
145
+ patch].include?(action_config) && path_config[:actions][action_config][:params].keys.count.positive?
146
+ schema = path_config[:actions][action_config][:params]
147
+ schema_ref = escape_operation_id("#{action_config}_#{path}")
148
+ examples = process_examples(path_config[:actions][action_config][:statuses])
149
+ request_body = process_request_body schema: schema, ref: schema_ref, examples: examples
147
150
  end
148
151
 
149
152
  path_config[:actions][action_config][:statuses].each do |status_key, status|
@@ -166,14 +169,15 @@ module RSpec
166
169
  end
167
170
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
168
171
 
169
- def process_request_body(schema: nil, ref: nil)
172
+ def process_request_body(schema: nil, ref: nil, examples: {})
170
173
  Utils.deep_set @api_components, "schemas.#{ref}", schema
171
174
  {
172
175
  # description: '',
173
176
  required: true,
174
177
  content: {
175
178
  'application/json' => {
176
- schema: { '$ref' => "#/components/schemas/#{ref}" },
179
+ schema: { '$ref' => "#/components/schemas/#{ref}" },
180
+ examples: examples,
177
181
  },
178
182
  },
179
183
  }
@@ -184,17 +188,30 @@ module RSpec
184
188
  description: status_config[:description],
185
189
  }
186
190
 
187
- return response unless status.to_s != '204' && content # No content
191
+ return response if status.to_s == '204' && content # No content
188
192
 
189
193
  response[:content] = {
190
194
  'application/json': {
191
- examples: { default: { value: JSON.pretty_generate(JSON.parse(content)) } },
195
+ examples: { default: { value: JSON.parse(content) } },
192
196
  },
193
197
  }
194
198
 
195
199
  response
196
200
  end
197
201
 
202
+ def process_examples(statuses)
203
+ request_examples = {}
204
+
205
+ statuses.each do |code, request|
206
+ request_examples[code] = {
207
+ summary: "Example for a #{code} code",
208
+ value: request[:example][:params],
209
+ }
210
+ end
211
+
212
+ request_examples
213
+ end
214
+
198
215
  def path_with_params(string)
199
216
  string.gsub(/(?::(\w*))/) do |e|
200
217
  "{#{e.sub(':', '')}}"
@@ -205,7 +222,7 @@ module RSpec
205
222
  string.downcase.gsub(/[^\w]+/, '_')
206
223
  end
207
224
 
208
- def api_infos # rubocop:disable Metrics/CyclomaticComplexity
225
+ def api_infos
209
226
  @api_infos = {
210
227
  title: @api_title || 'Some sample app',
211
228
  version: @api_version || '1.0',
@@ -11,7 +11,7 @@ module RSpec
11
11
  path.split('.').inject(hash) do |sub_hash, key|
12
12
  return nil unless sub_hash.is_a?(Hash) && sub_hash.key?(key.to_sym)
13
13
 
14
- sub_hash[key.to_sym]
14
+ sub_hash[key.to_sym] # rubocop:disable Lint/UnmodifiedReduceAccumulator
15
15
  end
16
16
  end
17
17
 
@@ -27,7 +27,7 @@ module RSpec
27
27
  hash
28
28
  end
29
29
 
30
- def self.check_value_type(type, value) # rubocop:disable Metrics/CyclomaticComplexity
30
+ def self.check_value_type(type, value)
31
31
  return true if type == :boolean && (value.is_a?(TrueClass) || value.is_a?(FalseClass))
32
32
  return true if type == :array && value.is_a?(Array)
33
33
 
@@ -3,7 +3,7 @@
3
3
  module RSpec
4
4
  module Rails
5
5
  module Api
6
- VERSION = '0.2.0'
6
+ VERSION = '0.3.2'
7
7
  end
8
8
  end
9
9
  end
@@ -32,7 +32,9 @@ Gem::Specification.new do |spec|
32
32
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
33
  spec.require_paths = ['lib']
34
34
 
35
- spec.add_development_dependency 'activesupport', '>= 5.2'
35
+ spec.required_ruby_version = '>= 2.5.0'
36
+
37
+ spec.add_development_dependency 'activesupport', '~> 6.0'
36
38
  spec.add_development_dependency 'bundler', '~> 1.17'
37
39
  spec.add_development_dependency 'byebug'
38
40
  spec.add_development_dependency 'rake', '~> 10.0'
metadata CHANGED
@@ -1,29 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rspec-rails-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Manuel Tancoigne
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-11-02 00:00:00.000000000 Z
11
+ date: 2021-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '5.2'
19
+ version: '6.0'
20
20
  type: :development
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '5.2'
26
+ version: '6.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -170,7 +170,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - ">="
172
172
  - !ruby/object:Gem::Version
173
- version: '0'
173
+ version: 2.5.0
174
174
  required_rubygems_version: !ruby/object:Gem::Requirement
175
175
  requirements:
176
176
  - - ">="