rspec-rails-api 0.4.0 → 0.6.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.
- checksums.yaml +4 -4
- data/.gitignore +0 -5
- data/.gitlab-ci.yml +1 -1
- data/.rubocop.yml +5 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +119 -27
- data/Gemfile.lock +98 -0
- data/README.md +210 -7
- data/lib/rspec/rails/api/dsl/example.rb +36 -19
- data/lib/rspec/rails/api/dsl/example_group.rb +12 -2
- data/lib/rspec/rails/api/entity_config.rb +7 -10
- data/lib/rspec/rails/api/field_config.rb +6 -3
- data/lib/rspec/rails/api/matchers.rb +22 -27
- data/lib/rspec/rails/api/metadata.rb +71 -35
- data/lib/rspec/rails/api/open_api_renderer.rb +161 -45
- data/lib/rspec/rails/api/utils.rb +28 -129
- data/lib/rspec/rails/api/validator.rb +211 -0
- data/lib/rspec/rails/api/version.rb +1 -1
- data/lib/rspec_rails_api.rb +2 -5
- data/rspec-rails-api.gemspec +4 -1
- metadata +49 -5
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support/hash_with_indifferent_access'
|
4
|
+
require 'rspec_rails_api'
|
5
|
+
|
6
|
+
module RSpec
|
7
|
+
module Rails
|
8
|
+
module Api
|
9
|
+
# Set of method to validate data and data structures
|
10
|
+
class Validator
|
11
|
+
class << self
|
12
|
+
##
|
13
|
+
# Validates an object keys and values types
|
14
|
+
#
|
15
|
+
# @param actual [*] Value to compare
|
16
|
+
# @param expected [Hash, NilClass] Definition
|
17
|
+
#
|
18
|
+
# @return [String, Hash, NilClass] Nil when no error, string when not an object and dictionary of errors
|
19
|
+
# otherwise
|
20
|
+
def validate_object(actual, expected)
|
21
|
+
return 'is not a hash' unless actual.is_a? Hash
|
22
|
+
# Don't validate without a definition
|
23
|
+
return unless expected
|
24
|
+
|
25
|
+
keys_errors = validate_object_keys actual, expected
|
26
|
+
return keys_errors unless keys_errors.nil?
|
27
|
+
|
28
|
+
attributes_errors = validate_object_attributes(actual, expected)
|
29
|
+
|
30
|
+
attributes_errors unless attributes_errors.keys.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Validates each entry of an array
|
35
|
+
#
|
36
|
+
# @param array [*] The array to check
|
37
|
+
# @param expected [Symbol, Hash, NilClass] Attributes configuration
|
38
|
+
#
|
39
|
+
# @return [String, Hash, NilClass] Nil when no error, string when not an object and dictionary of errors
|
40
|
+
# otherwise
|
41
|
+
def validate_array(array, expected)
|
42
|
+
return 'is not an array' unless array.is_a? Array
|
43
|
+
# Arrays without an expected entry type
|
44
|
+
return unless expected
|
45
|
+
|
46
|
+
errors = {}
|
47
|
+
array.each_with_index do |array_entry, index|
|
48
|
+
value_error = validate_array_entry array_entry, expected
|
49
|
+
errors["##{index}"] = value_error if value_error
|
50
|
+
end
|
51
|
+
|
52
|
+
errors unless errors.keys.empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
##
|
56
|
+
# Returns a human-readable string from matcher errors
|
57
|
+
#
|
58
|
+
# @param errors [String,Hash] Validation errors
|
59
|
+
# @param values [String] JSON string representing the value
|
60
|
+
#
|
61
|
+
# @return [String]
|
62
|
+
def format_failure_message(errors, values)
|
63
|
+
if errors.is_a? Hash
|
64
|
+
errors = errors.deep_stringify_keys.to_yaml.split("\n")
|
65
|
+
errors.shift
|
66
|
+
errors.map! do |line|
|
67
|
+
" #{line.sub(/^(\s+)"(#\d+)":(.*)$/, '\1\2:\3')}"
|
68
|
+
end
|
69
|
+
errors = errors.join("\n")
|
70
|
+
end
|
71
|
+
|
72
|
+
<<~TXT
|
73
|
+
expected object structure not to have these errors:
|
74
|
+
#{errors}
|
75
|
+
|
76
|
+
As a notice, here is the JSON object:
|
77
|
+
#{values}
|
78
|
+
TXT
|
79
|
+
end
|
80
|
+
|
81
|
+
##
|
82
|
+
# Checks if a given type is in the supported types list
|
83
|
+
#
|
84
|
+
# @param type [Symbol] Type to check
|
85
|
+
# @param except [[Symbol]] List of types to ignore
|
86
|
+
#
|
87
|
+
# @return [Boolean]
|
88
|
+
def valid_type?(type, except: [])
|
89
|
+
keys = PARAM_TYPES.keys.reject { |key| except.include? key }
|
90
|
+
keys.include?(type)
|
91
|
+
end
|
92
|
+
|
93
|
+
##
|
94
|
+
# Checks if a value is of the given type
|
95
|
+
#
|
96
|
+
# @param value [*] Value to test
|
97
|
+
# @param type [Symbol] Type to compare to
|
98
|
+
#
|
99
|
+
# @return [String,NilClass] True when the value corresponds to the given type
|
100
|
+
def validate_type(value, type)
|
101
|
+
if type == :boolean
|
102
|
+
return nil if value.is_a?(TrueClass) || value.is_a?(FalseClass)
|
103
|
+
|
104
|
+
return 'is not a "boolean"'
|
105
|
+
end
|
106
|
+
|
107
|
+
raise "Unknown type #{type}" unless PARAM_TYPES.key? type
|
108
|
+
|
109
|
+
return nil if value.is_a? PARAM_TYPES[type][:class]
|
110
|
+
|
111
|
+
"is not a \"#{type}\""
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# Checks if a key should be skipped, whether it's missing and optional or nil
|
117
|
+
#
|
118
|
+
# @param key [Symbol] Key to check
|
119
|
+
# @param value [*] Associated value
|
120
|
+
# @param definition [Hash] Entity definitions
|
121
|
+
#
|
122
|
+
# @return [Boolean]
|
123
|
+
def skip_key_check?(key, value, definition)
|
124
|
+
# Ignore missing optional keys
|
125
|
+
return true unless value.key?(key.to_s) || definition[key][:required]
|
126
|
+
# Ignore null optional keys
|
127
|
+
return true if !definition[key][:required] && value[key.to_s].nil?
|
128
|
+
|
129
|
+
false
|
130
|
+
end
|
131
|
+
|
132
|
+
# Validates the keys of a hash
|
133
|
+
#
|
134
|
+
# @param actual [Hash] The hash to check
|
135
|
+
# @param definition [Hash] The object definition
|
136
|
+
#
|
137
|
+
# @return [String, Hash, NilClass] Nil when no error, string when not an object and dictionary of errors
|
138
|
+
# otherwise
|
139
|
+
def validate_object_keys(actual, definition)
|
140
|
+
# Hashes without an expected attributes type
|
141
|
+
return unless definition
|
142
|
+
|
143
|
+
errors = {}
|
144
|
+
actual.each_key do |key|
|
145
|
+
errors[key] = 'is not defined' unless definition.key?(key.to_sym)
|
146
|
+
end
|
147
|
+
|
148
|
+
errors unless errors.keys.empty?
|
149
|
+
end
|
150
|
+
|
151
|
+
##
|
152
|
+
# Validates the attributes of a Hash
|
153
|
+
#
|
154
|
+
# @param actual [Hash] Value to compare
|
155
|
+
# @param expected [Hash] Definition
|
156
|
+
#
|
157
|
+
# @return [String, Hash, NilClass] Nil when no error, string when not an object and dictionary of errors
|
158
|
+
# otherwise
|
159
|
+
def validate_object_attributes(actual, expected)
|
160
|
+
errors = {}
|
161
|
+
expected.each_key do |key|
|
162
|
+
next if skip_key_check? key, actual, expected
|
163
|
+
|
164
|
+
value_error = validate_object_attribute key, actual, expected[key][:type], expected[key][:attributes]
|
165
|
+
errors[key] = value_error unless value_error.nil?
|
166
|
+
end
|
167
|
+
|
168
|
+
errors unless errors.keys.nil?
|
169
|
+
end
|
170
|
+
|
171
|
+
# Checks the value of an entry in a Hash
|
172
|
+
#
|
173
|
+
# @param key [Symbol] Key to check
|
174
|
+
# @param actual [Hash] Hash to check
|
175
|
+
# @param expected_type [Symbol] Expected type
|
176
|
+
# @param definition [Symbol,Hash] Attribute definition
|
177
|
+
#
|
178
|
+
# @return [String,Hash,NilClass] Nil when no error is met, string when not a primitive and dictionary of
|
179
|
+
# errors otherwise
|
180
|
+
def validate_object_attribute(key, actual, expected_type, definition)
|
181
|
+
return 'is missing' unless actual.key? key.to_s
|
182
|
+
|
183
|
+
case expected_type
|
184
|
+
when :object
|
185
|
+
validate_object actual[key.to_s], definition
|
186
|
+
when :array
|
187
|
+
validate_array actual[key.to_s], definition
|
188
|
+
else
|
189
|
+
validate_type actual[key.to_s], expected_type
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# Checks the validity of an array entry against a definition
|
194
|
+
#
|
195
|
+
# @param entry [*] Entry to check
|
196
|
+
# @param definition [Hash] Fields definition
|
197
|
+
#
|
198
|
+
# @return [String,Hash,NilClass] Nil when no error is met, string when not a primitive and dictionary of
|
199
|
+
# errors otherwise
|
200
|
+
def validate_array_entry(entry, definition)
|
201
|
+
if definition[:type].is_a? Symbol # Array of "simple" values
|
202
|
+
validate_type entry, definition[:type]
|
203
|
+
else # Objects
|
204
|
+
validate_object entry, definition
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
data/lib/rspec_rails_api.rb
CHANGED
@@ -30,16 +30,13 @@ 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:
|
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
|
-
EXCLUDED_PRIMITIVES = %i[array object].freeze
|
39
|
-
|
40
39
|
PRIMITIVES = PARAM_TYPES.keys
|
41
|
-
.reject { |key| EXCLUDED_PRIMITIVES.include? key }
|
42
|
-
.map { |key| "type_#{key}".to_sym }
|
43
40
|
end
|
44
41
|
end
|
45
42
|
end
|
data/rspec-rails-api.gemspec
CHANGED
@@ -33,15 +33,18 @@ Gem::Specification.new do |spec|
|
|
33
33
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
34
34
|
spec.require_paths = ['lib']
|
35
35
|
|
36
|
-
spec.required_ruby_version = '>= 2.
|
36
|
+
spec.required_ruby_version = '>= 2.7.0'
|
37
37
|
|
38
38
|
spec.add_development_dependency 'activesupport', '~> 6.0'
|
39
39
|
spec.add_development_dependency 'bundler'
|
40
40
|
spec.add_development_dependency 'byebug'
|
41
|
+
spec.add_development_dependency 'rack'
|
41
42
|
spec.add_development_dependency 'rake', '~> 10.0'
|
42
43
|
spec.add_development_dependency 'rspec', '~> 3.0'
|
43
44
|
spec.add_development_dependency 'rubocop'
|
44
45
|
spec.add_development_dependency 'rubocop-performance'
|
46
|
+
spec.add_development_dependency 'rubocop-rake'
|
47
|
+
spec.add_development_dependency 'rubocop-rspec'
|
45
48
|
spec.add_development_dependency 'simplecov'
|
46
49
|
spec.add_development_dependency 'yard'
|
47
50
|
end
|
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.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Manuel Tancoigne
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-07-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -52,6 +52,20 @@ dependencies:
|
|
52
52
|
- - ">="
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rack
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
55
69
|
- !ruby/object:Gem::Dependency
|
56
70
|
name: rake
|
57
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,6 +122,34 @@ dependencies:
|
|
108
122
|
- - ">="
|
109
123
|
- !ruby/object:Gem::Version
|
110
124
|
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: rubocop-rake
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: rubocop-rspec
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
111
153
|
- !ruby/object:Gem::Dependency
|
112
154
|
name: simplecov
|
113
155
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,10 +191,12 @@ files:
|
|
149
191
|
- ".gitlab-ci.yml"
|
150
192
|
- ".rspec"
|
151
193
|
- ".rubocop.yml"
|
194
|
+
- ".ruby-version"
|
152
195
|
- ".travis.yml"
|
153
196
|
- CHANGELOG.md
|
154
197
|
- CODE_OF_CONDUCT.md
|
155
198
|
- Gemfile
|
199
|
+
- Gemfile.lock
|
156
200
|
- LICENSE.txt
|
157
201
|
- README.md
|
158
202
|
- Rakefile
|
@@ -166,6 +210,7 @@ files:
|
|
166
210
|
- lib/rspec/rails/api/metadata.rb
|
167
211
|
- lib/rspec/rails/api/open_api_renderer.rb
|
168
212
|
- lib/rspec/rails/api/utils.rb
|
213
|
+
- lib/rspec/rails/api/validator.rb
|
169
214
|
- lib/rspec/rails/api/version.rb
|
170
215
|
- lib/rspec_rails_api.rb
|
171
216
|
- rspec-rails-api.gemspec
|
@@ -185,15 +230,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
185
230
|
requirements:
|
186
231
|
- - ">="
|
187
232
|
- !ruby/object:Gem::Version
|
188
|
-
version: 2.
|
233
|
+
version: 2.7.0
|
189
234
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
190
235
|
requirements:
|
191
236
|
- - ">="
|
192
237
|
- !ruby/object:Gem::Version
|
193
238
|
version: '0'
|
194
239
|
requirements: []
|
195
|
-
|
196
|
-
rubygems_version: 2.7.6
|
240
|
+
rubygems_version: 3.4.1
|
197
241
|
signing_key:
|
198
242
|
specification_version: 4
|
199
243
|
summary: Tests standard Rails API responses and generate doc
|