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.
@@ -9,99 +9,185 @@ module RSpec
9
9
  # All these methods will be available in example groups
10
10
  # (anything but 'it', 'example', 'for_code')
11
11
  module ExampleGroup
12
- # First method to be called in a spec file
13
- # as it will initialize the metadatas.
12
+ ##
13
+ # First method to be called in a spec file # as it will initialize the
14
+ # metadata.
15
+ #
16
+ # @param name [String] Resource name
17
+ # @param description [String] Resource description
14
18
  def resource(name, description = '')
15
- metadata[:rrad] ||= Metadata.new
16
- metadata[:rrad].add_resource name, description
19
+ metadata[:rra] ||= Metadata.new
20
+ metadata[:rra].add_resource name, description
17
21
  end
18
22
 
19
- # Used to describe an entity
23
+ ##
24
+ # Describes an entity
25
+ #
26
+ # @param type [Symbol] Name of the entity for reference
27
+ # @param fields [Hash] Fields declarations
28
+ #
29
+ # @return [void]
20
30
  def entity(type, fields)
21
- metadata[:rrad].add_entity type, fields
31
+ metadata[:rra].add_entity type, fields
22
32
  end
23
33
 
24
- # Used to describe a request or path param which will be available as reference
34
+ ##
35
+ # Describes request or path parameters
36
+ #
37
+ # @param type [Symbol] Name of the parameters set for reference
38
+ # @param fields [Hash] Fields declarations
39
+ #
40
+ # @return [void]
25
41
  def parameters(type, fields)
26
- metadata[:rrad].add_parameter type, fields
42
+ metadata[:rra].add_parameter type, fields
27
43
  end
28
44
 
29
- # Used to describe query parameters
45
+ ##
46
+ # Declares parameters used in URLS (_path_)
47
+ # Use `fields` or `defined` but not both.
48
+ #
49
+ # @param [Hash, nil] fields An attributes declaration
50
+ # @param [Symbol, nil] defined An entity reference
51
+ #
52
+ # @return [void]
30
53
  def path_params(fields: nil, defined: nil)
31
- if defined && !metadata[:rrad].parameters[defined]
54
+ if defined && !metadata[:rra].parameters[defined]
32
55
  raise "Parameter #{defined} was not defined with the 'parameters' method"
33
56
  end
34
57
 
35
- fields ||= metadata[:rrad].parameters[defined]
58
+ fields ||= metadata[:rra].parameters[defined]
36
59
 
37
- metadata[:rrad].add_path_params fields
60
+ metadata[:rra].add_path_params fields
38
61
  end
39
62
 
63
+ ##
64
+ # Declares parameters for a request body
65
+ # Use `attributes` or `defined` but not both.
66
+ #
67
+ # @param [Hash, nil] attributes An attributes declaration
68
+ # @param [Symbol, nil] defined An entity reference.
69
+ #
70
+ # @return [void]
40
71
  def request_params(attributes: nil, defined: nil)
41
- if defined && !metadata[:rrad].parameters[defined]
72
+ if defined && !metadata[:rra].parameters[defined]
42
73
  raise "Parameter #{defined} was not defined with the 'parameters' method"
43
74
  end
44
75
 
45
- attributes ||= metadata[:rrad].parameters[defined]
76
+ attributes ||= metadata[:rra].parameters[defined]
46
77
 
47
- metadata[:rrad].add_request_params attributes
78
+ metadata[:rra].add_request_params attributes
48
79
  end
49
80
 
50
- def on_get(url, description = nil, &block)
51
- on_action(:get, url, description, &block)
81
+ ##
82
+ # Defines a GET action
83
+ #
84
+ # @param [String] url URL to test
85
+ # @param [String] summary What the action does
86
+ # @param [String] description Longer description
87
+ #
88
+ # @return [void]
89
+ def on_get(url, summary = nil, description = nil, &block)
90
+ on_action(:get, url, summary, description, &block)
52
91
  end
53
92
 
54
- def on_post(url, description = nil, &block)
55
- on_action(:post, url, description, &block)
93
+ ##
94
+ # Defines a POST action
95
+ #
96
+ # @param [String] url URL to test
97
+ # @param [String] summary What the action does
98
+ # @param [String] description Longer description
99
+ #
100
+ # @return [void]
101
+ def on_post(url, summary = nil, description = nil, &block)
102
+ on_action(:post, url, summary, description, &block)
56
103
  end
57
104
 
58
- def on_put(url, description = nil, &block)
59
- on_action(:put, url, description, &block)
105
+ ##
106
+ # Defines a PUT action
107
+ #
108
+ # @param [String] url URL to test
109
+ # @param [String] summary What the action does
110
+ # @param [String] description Longer description
111
+ #
112
+ # @return [void]
113
+ def on_put(url, summary = nil, description = nil, &block)
114
+ on_action(:put, url, summary, description, &block)
60
115
  end
61
116
 
62
- def on_patch(url, description = nil, &block)
63
- on_action(:patch, url, description, &block)
117
+ ##
118
+ # Defines a PATCH action
119
+ #
120
+ # @param [String] url URL to test
121
+ # @param [String] summary What the action does
122
+ # @param [String] description Longer description
123
+ #
124
+ # @return [void]
125
+ def on_patch(url, summary = nil, description = nil, &block)
126
+ on_action(:patch, url, summary, description, &block)
64
127
  end
65
128
 
66
- def on_delete(url, description = nil, &block)
67
- on_action(:delete, url, description, &block)
129
+ ##
130
+ # Defines a DELETE action
131
+ #
132
+ # @param [String] url URL to test
133
+ # @param [String] summary What the action does
134
+ # @param [String] description Longer description
135
+ #
136
+ # @return [void]
137
+ def on_delete(url, summary = nil, description = nil, &block)
138
+ on_action(:delete, url, summary, description, &block)
68
139
  end
69
140
 
70
- # Currently fill metadatas with the action
71
- def on_action(action, url, description, &block)
72
- metadata[:rrad].add_action(action, url, description)
73
-
74
- describe("#{action.upcase} #{url}", &block)
75
- end
76
-
77
- def for_code(status_code, description = nil, doc_only: false, test_only: false, &block)
141
+ ##
142
+ # Adds an HTTP code declaration to metadata, with expected result
143
+ # If no expectation is provided, the response will be expected to be empty
144
+ #
145
+ # @param status_code [Number] Status code to test for
146
+ # @param description [String] Description of the route/status pair
147
+ # @param expect_many [Symbol] Check the response for a list of given entity
148
+ # @param expect_one [Symbol] Check the response for a given entity
149
+ # @param test_only [Boolean] When true, test the response without filling the documentation
150
+ #
151
+ # @return [void]
152
+ #
153
+ def for_code(status_code, description = nil, expect_many: nil, expect_one: false, test_only: false, &block)
78
154
  description ||= Rack::Utils::HTTP_STATUS_CODES[status_code]
79
155
 
80
- metadata[:rrad].add_status_code(status_code, description) unless test_only
156
+ metadata[:rra].add_status_code(status_code, description) unless test_only
157
+ metadata[:rra].add_expectations(expect_one, expect_many)
158
+ metadata[:rra_current_example] = metadata[:rra].current_example
81
159
 
82
160
  describe "->#{test_only ? ' test' : ''} #{status_code} - #{description}" do
83
- execute_for_code_block(status_code, doc_only, block)
161
+ execute_for_code_block(block)
84
162
  end
85
163
  end
86
164
 
87
165
  private
88
166
 
89
- def document_only(status_code)
90
- example 'Create documentation' do |example|
91
- parent_example = example.example_group
92
- request_params = prepare_request_params parent_example.module_parent.description
167
+ ##
168
+ # Currently fill metadata with the action
169
+ #
170
+ # @param [Symbol] action HTTP verb
171
+ # @param [String] url URL to test
172
+ # @param [String, nil] summary What the action does
173
+ # @param [String, nil] description Longer description
174
+ #
175
+ # @return [void]
176
+ def on_action(action, url, summary, description, &block)
177
+ metadata[:rra].add_action(action, url, summary, description)
93
178
 
94
- set_request_example parent_example.metadata[:rrad], request_params, status_code
95
- end
179
+ describe("#{action.upcase} #{url}", &block)
96
180
  end
97
181
 
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
182
+ ##
183
+ # Visit the URL and test response
184
+ #
185
+ # @param callback_block [block] Block to execute for testing the response
186
+ #
187
+ # @return [void]
188
+ def execute_for_code_block(callback_block)
189
+ example 'Test response and create documentation', caller: callback_block.send(:caller) do
190
+ instance_eval(&callback_block) if callback_block
105
191
  end
106
192
  end
107
193
  end
@@ -18,6 +18,8 @@ module RSpec
18
18
  end
19
19
  end
20
20
 
21
+ ##
22
+ # @return [Hash] Entity configuration
21
23
  def to_h
22
24
  out = {}
23
25
  @fields.each_key do |key|
@@ -26,18 +28,40 @@ module RSpec
26
28
  out
27
29
  end
28
30
 
31
+ ##
32
+ # Replaces the arrays 'of' and objects 'attributes' with the corresponding
33
+ # entities, recursively
34
+ #
35
+ # @param entities [Hash] List of entities
36
+ #
37
+ # @return [Hash]
29
38
  def expand_with(entities)
30
39
  hash = to_h
31
40
  hash.each_pair do |field, config|
32
41
  next unless %i[array object].include? config[:type]
33
42
 
34
- attributes = config[:attributes]
35
- next unless attributes.is_a? Symbol
36
- raise "Entity #{attributes} not found for entity completion." unless entities[attributes]
43
+ attribute = config[:attributes]
44
+ next unless attribute.is_a? Symbol
37
45
 
38
- hash[field][:attributes] = entities[attributes].expand_with(entities)
46
+ hash[field][:attributes] = expand_attribute attribute, entities
39
47
  end
40
48
  end
49
+
50
+ private
51
+
52
+ ##
53
+ # Expands an attribute for "for" and "attributes" keys
54
+ #
55
+ # @param attribute [Symbol] Attribute name
56
+ # @param entities [Hash] List of entities
57
+ def expand_attribute(attribute, entities)
58
+ # Primitives support
59
+ return { type: attribute } if PRIMITIVES.include? attribute
60
+
61
+ raise "Entity #{attribute} not found for entity completion." unless entities[attribute]
62
+
63
+ entities[attribute].expand_with(entities)
64
+ end
41
65
  end
42
66
  end
43
67
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec/rails/api/entity_config'
4
- require 'rspec/rails/api/utils'
4
+ require 'rspec/rails/api/validator'
5
5
 
6
6
  module RSpec
7
7
  module Rails
@@ -14,7 +14,7 @@ module RSpec
14
14
  def initialize(type:, description:, required: true, attributes: nil, of: nil)
15
15
  @required = required
16
16
  @description = description
17
- raise "Field type not allowed: '#{type}'" unless Utils.check_attribute_type(type)
17
+ raise "Field type not allowed: '#{type}'" unless Validator.valid_type?(type)
18
18
 
19
19
  define_attributes attributes if type == :object
20
20
  define_attributes of if type == :array
@@ -22,6 +22,8 @@ module RSpec
22
22
  @type = type
23
23
  end
24
24
 
25
+ ##
26
+ # @return [Hash] Field configuration
25
27
  def to_h
26
28
  out = { required: @required, type: @type }
27
29
  out[:description] = @description unless @description.nil?
@@ -39,6 +41,12 @@ module RSpec
39
41
 
40
42
  private
41
43
 
44
+ ##
45
+ # Sets @attributes of the field when it's an Array or Hash
46
+ #
47
+ # @param attributes [Hash, Symbol] The attributes definition or reference
48
+ #
49
+ # @return [void]
42
50
  def define_attributes(attributes)
43
51
  @attributes = case attributes
44
52
  when Hash
@@ -1,40 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'active_support/hash_with_indifferent_access'
4
+ require 'yaml'
4
5
 
6
+ require 'rspec/rails/api/validator'
5
7
  require 'rspec/rails/api/utils'
6
8
 
7
- # FIXME: Split the matcher in something else; it's too messy.
9
+ ##
10
+ # RSpec matcher to check something against an array of `expected`
8
11
  RSpec::Matchers.define :have_many do |expected|
9
12
  match do |actual|
10
- @actual = actual
11
- @actual = JSON.parse(actual.body) if actual.respond_to? :body
13
+ actual = RSpec::Rails::Api::Utils.hash_from_response actual
12
14
 
13
- raise "Response is not an array: #{@actual.class}" unless @actual.is_a? Array
14
- raise 'Response has no item to compare with' unless @actual.count.positive?
15
+ raise "Response is not an array: #{actual.class}" unless actual.is_a? Array
16
+ raise 'Response has no item to compare with' unless actual.count.positive?
15
17
 
16
- # Check every entry
17
- ok = true
18
- @actual.each do |item|
19
- ok = false unless RSpec::Rails::Api::Utils.validate_object_structure item, expected
20
- end
18
+ @errors = RSpec::Rails::Api::Validator.validate_array actual, expected
21
19
 
22
- ok
20
+ @errors.blank?
23
21
  end
24
22
 
25
- diffable
23
+ failure_message do |actual|
24
+ object = RSpec::Rails::Api::Utils.hash_from_response(actual).to_json.chomp
25
+ RSpec::Rails::Api::Validator.format_failure_message @errors, object
26
+ end
26
27
  end
27
28
 
28
- # FIXME: Split the matcher in something else; it's too messy.
29
+ ##
30
+ # RSpec matcher to check something against the `expected` definition
29
31
  RSpec::Matchers.define :have_one do |expected|
30
32
  match do |actual|
31
- @actual = actual
32
- @actual = JSON.parse(actual.body) if actual.respond_to? :body
33
+ actual = RSpec::Rails::Api::Utils.hash_from_response actual
33
34
 
34
- raise "Response is not a hash: #{@actual.class}" unless @actual.is_a? Hash
35
+ @errors = if expected.keys.count == 1 && expected.key?(:type)
36
+ RSpec::Rails::Api::Validator.validate_type actual, expected[:type]
37
+ else
38
+ RSpec::Rails::Api::Validator.validate_object actual, expected
39
+ end
35
40
 
36
- RSpec::Rails::Api::Utils.validate_object_structure @actual, expected
41
+ @errors.blank?
37
42
  end
38
43
 
39
- diffable
44
+ failure_message do |actual|
45
+ object = RSpec::Rails::Api::Utils.hash_from_response(actual).to_json.chomp
46
+ RSpec::Rails::Api::Validator.format_failure_message @errors, object
47
+ end
40
48
  end