apiwork-rspec 0.1.1

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.
Files changed (39) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/Rakefile +10 -0
  4. data/lib/apiwork/rspec/matchers/base_matcher.rb +44 -0
  5. data/lib/apiwork/rspec/matchers/define_contact_matcher.rb +80 -0
  6. data/lib/apiwork/rspec/matchers/define_enum_matcher.rb +139 -0
  7. data/lib/apiwork/rspec/matchers/define_license_matcher.rb +53 -0
  8. data/lib/apiwork/rspec/matchers/define_server_matcher.rb +50 -0
  9. data/lib/apiwork/rspec/matchers/have_association_matcher.rb +247 -0
  10. data/lib/apiwork/rspec/matchers/have_attribute_matcher.rb +283 -0
  11. data/lib/apiwork/rspec/matchers/have_description_matcher.rb +30 -0
  12. data/lib/apiwork/rspec/matchers/have_discriminator_matcher.rb +30 -0
  13. data/lib/apiwork/rspec/matchers/have_example_matcher.rb +30 -0
  14. data/lib/apiwork/rspec/matchers/have_export_matcher.rb +29 -0
  15. data/lib/apiwork/rspec/matchers/have_identifier_matcher.rb +30 -0
  16. data/lib/apiwork/rspec/matchers/have_import_matcher.rb +34 -0
  17. data/lib/apiwork/rspec/matchers/have_key_format_matcher.rb +30 -0
  18. data/lib/apiwork/rspec/matchers/have_model_matcher.rb +30 -0
  19. data/lib/apiwork/rspec/matchers/have_operation_id_matcher.rb +30 -0
  20. data/lib/apiwork/rspec/matchers/have_param_matcher.rb +222 -0
  21. data/lib/apiwork/rspec/matchers/have_path_format_matcher.rb +30 -0
  22. data/lib/apiwork/rspec/matchers/have_raises_matcher.rb +30 -0
  23. data/lib/apiwork/rspec/matchers/have_representation_matcher.rb +30 -0
  24. data/lib/apiwork/rspec/matchers/have_resource_matcher.rb +142 -0
  25. data/lib/apiwork/rspec/matchers/have_root_matcher.rb +35 -0
  26. data/lib/apiwork/rspec/matchers/have_summary_matcher.rb +30 -0
  27. data/lib/apiwork/rspec/matchers/have_tags_matcher.rb +30 -0
  28. data/lib/apiwork/rspec/matchers/have_terms_of_service_matcher.rb +30 -0
  29. data/lib/apiwork/rspec/matchers/have_title_matcher.rb +30 -0
  30. data/lib/apiwork/rspec/matchers/have_type_name_matcher.rb +30 -0
  31. data/lib/apiwork/rspec/matchers/have_variant_matcher.rb +127 -0
  32. data/lib/apiwork/rspec/matchers/have_version_matcher.rb +30 -0
  33. data/lib/apiwork/rspec/matchers/no_content_matcher.rb +22 -0
  34. data/lib/apiwork/rspec/matchers/param_wrapper.rb +17 -0
  35. data/lib/apiwork/rspec/matchers.rb +388 -0
  36. data/lib/apiwork/rspec/version.rb +7 -0
  37. data/lib/apiwork/rspec.rb +25 -0
  38. data/lib/apiwork-rspec.rb +3 -0
  39. metadata +154 -0
@@ -0,0 +1,283 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for representation attributes.
8
+ class HaveAttributeMatcher < BaseMatcher
9
+ BOOLEAN_CHECKS = {
10
+ deprecated: :deprecated?,
11
+ empty: :empty,
12
+ filterable: :filterable?,
13
+ nullable: :nullable?,
14
+ optional: :optional?,
15
+ sortable: :sortable?,
16
+ }.freeze
17
+
18
+ VALUE_CHECKS = {
19
+ default: :default,
20
+ description: :description,
21
+ enum: :enum,
22
+ example: :example,
23
+ format: :format,
24
+ max: :max,
25
+ min: :min,
26
+ type: :type,
27
+ }.freeze
28
+
29
+ def initialize(name)
30
+ super()
31
+ @name = name
32
+ @checks = {}
33
+ end
34
+
35
+ # @api public
36
+ # Requires the attribute to have the expected type.
37
+ #
38
+ # @param type [Symbol] [:array, :binary, :boolean, :date, :datetime, :decimal, :integer, :number, :object, :record, :string, :time, :unknown, :uuid]
39
+ # The type.
40
+ # @return [self]
41
+ def of_type(type)
42
+ @checks[:type] = type
43
+ self
44
+ end
45
+
46
+ # @api public
47
+ # Requires the attribute to be writable.
48
+ #
49
+ # @param action [Symbol, Boolean] (true) [Symbol: :create, :update]
50
+ # The action to check writability for, or `true` for any action.
51
+ # @return [self]
52
+ def writable(action = true)
53
+ @checks[:writable] = action
54
+ self
55
+ end
56
+
57
+ # @api public
58
+ # Requires the attribute to be empty.
59
+ #
60
+ # @return [self]
61
+ def empty
62
+ @checks[:empty] = true
63
+ self
64
+ end
65
+
66
+ # @api public
67
+ # Requires the attribute to be deprecated.
68
+ #
69
+ # @return [self]
70
+ def deprecated
71
+ @checks[:deprecated] = true
72
+ self
73
+ end
74
+
75
+ # @api public
76
+ # Requires the attribute to be optional.
77
+ #
78
+ # @return [self]
79
+ def optional
80
+ @checks[:optional] = true
81
+ self
82
+ end
83
+
84
+ # @api public
85
+ # Requires the attribute to be nullable.
86
+ #
87
+ # @return [self]
88
+ def nullable
89
+ @checks[:nullable] = true
90
+ self
91
+ end
92
+
93
+ # @api public
94
+ # Requires the attribute to be filterable.
95
+ #
96
+ # @return [self]
97
+ def filterable
98
+ @checks[:filterable] = true
99
+ self
100
+ end
101
+
102
+ # @api public
103
+ # Requires the attribute to be sortable.
104
+ #
105
+ # @return [self]
106
+ def sortable
107
+ @checks[:sortable] = true
108
+ self
109
+ end
110
+
111
+ # @api public
112
+ # Requires the attribute to have the expected enum values.
113
+ #
114
+ # @param values [Array]
115
+ # The enum values.
116
+ # @return [self]
117
+ def with_enum(values)
118
+ @checks[:enum] = values
119
+ self
120
+ end
121
+
122
+ # @api public
123
+ # Requires the attribute to have the expected format.
124
+ #
125
+ # @param format [Symbol] [:date, :datetime, :double, :email, :float, :hostname, :int32, :int64, :ipv4, :ipv6, :password, :text, :url, :uuid]
126
+ # The format. Valid formats by type: `:decimal`/`:number` (`:double`, `:float`), `:integer` (`:int32`, `:int64`),
127
+ # `:string` (`:date`, `:datetime`, `:email`, `:hostname`, `:ipv4`, `:ipv6`, `:password`, `:text`, `:url`, `:uuid`).
128
+ # @return [self]
129
+ def with_format(format)
130
+ @checks[:format] = format
131
+ self
132
+ end
133
+
134
+ # @api public
135
+ # Requires the attribute to have the expected minimum.
136
+ #
137
+ # @param value [Numeric]
138
+ # The minimum.
139
+ # @return [self]
140
+ def with_min(value)
141
+ @checks[:min] = value
142
+ self
143
+ end
144
+
145
+ # @api public
146
+ # Requires the attribute to have the expected maximum.
147
+ #
148
+ # @param value [Numeric]
149
+ # The maximum.
150
+ # @return [self]
151
+ def with_max(value)
152
+ @checks[:max] = value
153
+ self
154
+ end
155
+
156
+ # @api public
157
+ # Requires the attribute to have the expected default.
158
+ #
159
+ # @param value [Object]
160
+ # The default.
161
+ # @return [self]
162
+ def with_default(value)
163
+ @checks[:default] = value
164
+ self
165
+ end
166
+
167
+ # @api public
168
+ # Requires the attribute to have the expected description.
169
+ #
170
+ # @param text [String]
171
+ # The description.
172
+ # @return [self]
173
+ def with_description(text)
174
+ @checks[:description] = text
175
+ self
176
+ end
177
+
178
+ # @api public
179
+ # Requires the attribute to have the expected example.
180
+ #
181
+ # @param value [Object]
182
+ # The example.
183
+ # @return [self]
184
+ def with_example(value)
185
+ @checks[:example] = value
186
+ self
187
+ end
188
+
189
+ def matches?(subject)
190
+ @subject = subject
191
+ attribute = subject.attributes[@name]
192
+ return fail_with("expected #{format_subject} to have attribute #{@name.inspect}") unless attribute
193
+
194
+ verify_all(attribute)
195
+ end
196
+
197
+ def description
198
+ parts = ["have attribute #{@name.inspect}"]
199
+ parts << "of type #{@checks[:type].inspect}" if @checks.key?(:type)
200
+ boolean_parts = boolean_description_parts
201
+ parts << "that is #{join_sentence(boolean_parts)}" if boolean_parts.any?
202
+ VALUE_CHECKS.except(:type).each_key do |key|
203
+ parts << "with #{key} #{@checks[key].inspect}" if @checks.key?(key)
204
+ end
205
+ parts.join(' ')
206
+ end
207
+
208
+ private
209
+
210
+ def verify_all(attribute)
211
+ verify_value_checks(attribute) &&
212
+ verify_boolean_checks(attribute) &&
213
+ verify_writable(attribute)
214
+ end
215
+
216
+ def verify_value_checks(attribute)
217
+ VALUE_CHECKS.each do |key, method|
218
+ next unless @checks.key?(key)
219
+
220
+ actual = attribute.public_send(method)
221
+ next if actual == @checks[key]
222
+
223
+ return fail_with(
224
+ "expected #{format_subject} to have attribute #{@name.inspect} " \
225
+ "#{format_value_check(key, @checks[key])}, but got #{actual.inspect}",
226
+ )
227
+ end
228
+ true
229
+ end
230
+
231
+ def verify_boolean_checks(attribute)
232
+ BOOLEAN_CHECKS.each do |key, method|
233
+ next unless @checks.key?(key)
234
+ next if attribute.public_send(method)
235
+
236
+ return fail_with(
237
+ "expected #{format_subject} to have attribute #{@name.inspect} that is #{key}, but it is not",
238
+ )
239
+ end
240
+ true
241
+ end
242
+
243
+ def verify_writable(attribute)
244
+ return true unless @checks.key?(:writable)
245
+
246
+ expected = @checks[:writable]
247
+ if expected == true
248
+ return true if attribute.writable?
249
+
250
+ return fail_with(
251
+ "expected #{format_subject} to have attribute #{@name.inspect} that is writable, but it is not",
252
+ )
253
+ end
254
+
255
+ return true if attribute.writable_for?(expected)
256
+
257
+ fail_with(
258
+ "expected #{format_subject} to have attribute #{@name.inspect} " \
259
+ "that is writable for #{expected.inspect}, but it is not",
260
+ )
261
+ end
262
+
263
+ def format_value_check(key, value)
264
+ case key
265
+ when :type
266
+ "of type #{value.inspect}"
267
+ else
268
+ "with #{key} #{value.inspect}"
269
+ end
270
+ end
271
+
272
+ def boolean_description_parts
273
+ parts = []
274
+ BOOLEAN_CHECKS.each_key { |key| parts << key.to_s if @checks.key?(key) }
275
+ if @checks.key?(:writable)
276
+ parts << (@checks[:writable] == true ? 'writable' : "writable for #{@checks[:writable].inspect}")
277
+ end
278
+ parts
279
+ end
280
+ end
281
+ end
282
+ end
283
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying a subject has the expected description.
8
+ class HaveDescriptionMatcher < BaseMatcher
9
+ def initialize(text)
10
+ super()
11
+ @text = text
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.description
17
+ return true if actual == @text
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have description #{@text.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have description #{@text.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying a union has the expected discriminator.
8
+ class HaveDiscriminatorMatcher < BaseMatcher
9
+ def initialize(field)
10
+ super()
11
+ @field = field
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.discriminator
17
+ return true if actual == @field
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have discriminator #{@field.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have discriminator #{@field.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying a subject has the expected example.
8
+ class HaveExampleMatcher < BaseMatcher
9
+ def initialize(value)
10
+ super()
11
+ @value = value
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.example
17
+ return true if actual == @value
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have example #{@value.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have example #{@value.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying an API has a specific export enabled.
8
+ class HaveExportMatcher < BaseMatcher
9
+ def initialize(name)
10
+ super()
11
+ @name = name
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ return true if subject.export_configs.key?(@name)
17
+
18
+ fail_with(
19
+ "expected #{format_subject} to have export #{@name.inspect}",
20
+ )
21
+ end
22
+
23
+ def description
24
+ "have export #{@name.inspect}"
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying a contract has the expected identifier.
8
+ class HaveIdentifierMatcher < BaseMatcher
9
+ def initialize(value)
10
+ super()
11
+ @value = value
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.identifier
17
+ return true if actual == @value
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have identifier #{@value.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have identifier #{@value.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying a contract imports another contract.
8
+ class HaveImportMatcher < BaseMatcher
9
+ def initialize(klass, as:)
10
+ super()
11
+ @klass = klass
12
+ @alias = as
13
+ end
14
+
15
+ def matches?(subject)
16
+ @subject = subject
17
+ actual = subject.imports[@alias]
18
+
19
+ return fail_with("expected #{format_subject} to have import #{@alias.inspect}") unless actual
20
+ return true if actual == @klass
21
+
22
+ fail_with(
23
+ "expected #{format_subject} to have import #{@alias.inspect} " \
24
+ "of #{@klass.inspect}, but got #{actual.inspect}",
25
+ )
26
+ end
27
+
28
+ def description
29
+ "have import #{@klass.inspect} as #{@alias.inspect}"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying an API has the expected key format.
8
+ class HaveKeyFormatMatcher < BaseMatcher
9
+ def initialize(format)
10
+ super()
11
+ @format = format
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.key_format
17
+ return true if actual == @format
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have key format #{@format.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have key format #{@format.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying a representation has the expected model.
8
+ class HaveModelMatcher < BaseMatcher
9
+ def initialize(klass)
10
+ super()
11
+ @klass = klass
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.model_class
17
+ return true if actual == @klass
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have model #{@klass.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have model #{@klass.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for verifying an action has the expected operation ID.
8
+ class HaveOperationIdMatcher < BaseMatcher
9
+ def initialize(id)
10
+ super()
11
+ @id = id
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.operation_id
17
+ return true if actual == @id
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have operation ID #{@id.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have operation ID #{@id.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end