rspec-rails-api 0.4.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -3,7 +3,7 @@
3
3
  module RSpec
4
4
  module Rails
5
5
  module Api
6
- VERSION = '0.4.0'
6
+ VERSION = '0.6.0'
7
7
  end
8
8
  end
9
9
  end
@@ -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: Float },
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
@@ -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.5.0'
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.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: 2021-12-19 00:00:00.000000000 Z
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.5.0
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
- rubyforge_project:
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