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,222 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for params in type definitions.
8
+ class HaveParamMatcher < BaseMatcher
9
+ BOOLEAN_CHECKS = {
10
+ deprecated: { expected: true, key: :deprecated },
11
+ nullable: { expected: true, key: :nullable },
12
+ optional: { expected: true, key: :optional },
13
+ required: { expected: false, key: :optional },
14
+ }.freeze
15
+
16
+ VALUE_CHECKS = %i[default description enum example format max min type].freeze
17
+
18
+ def initialize(name)
19
+ super()
20
+ @name = name
21
+ @checks = {}
22
+ end
23
+
24
+ # @api public
25
+ # Requires the param to have the expected type.
26
+ #
27
+ # @param type [Symbol] [:array, :binary, :boolean, :date, :datetime, :decimal, :integer, :number, :object, :record, :string, :time, :unknown, :uuid]
28
+ # The type.
29
+ # @return [self]
30
+ def of_type(type)
31
+ @checks[:type] = type
32
+ self
33
+ end
34
+
35
+ # @api public
36
+ # Requires the param to be required.
37
+ #
38
+ # @return [self]
39
+ def required
40
+ @checks[:required] = true
41
+ self
42
+ end
43
+
44
+ # @api public
45
+ # Requires the param to be optional.
46
+ #
47
+ # @return [self]
48
+ def optional
49
+ @checks[:optional] = true
50
+ self
51
+ end
52
+
53
+ # @api public
54
+ # Requires the param to be nullable.
55
+ #
56
+ # @return [self]
57
+ def nullable
58
+ @checks[:nullable] = true
59
+ self
60
+ end
61
+
62
+ # @api public
63
+ # Requires the param to be deprecated.
64
+ #
65
+ # @return [self]
66
+ def deprecated
67
+ @checks[:deprecated] = true
68
+ self
69
+ end
70
+
71
+ # @api public
72
+ # Requires the param to have the expected enum values.
73
+ #
74
+ # @param values [Array]
75
+ # The enum values.
76
+ # @return [self]
77
+ def with_enum(values)
78
+ @checks[:enum] = values
79
+ self
80
+ end
81
+
82
+ # @api public
83
+ # Requires the param to have the expected format.
84
+ #
85
+ # @param format [Symbol] [:date, :datetime, :double, :email, :float, :hostname, :int32, :int64, :ipv4, :ipv6, :password, :text, :url, :uuid]
86
+ # The format. Valid formats by type: `:decimal`/`:number` (`:double`, `:float`), `:integer` (`:int32`, `:int64`),
87
+ # `:string` (`:date`, `:datetime`, `:email`, `:hostname`, `:ipv4`, `:ipv6`, `:password`, `:text`, `:url`, `:uuid`).
88
+ # @return [self]
89
+ def with_format(format)
90
+ @checks[:format] = format
91
+ self
92
+ end
93
+
94
+ # @api public
95
+ # Requires the param to have the expected minimum.
96
+ #
97
+ # @param value [Numeric]
98
+ # The minimum.
99
+ # @return [self]
100
+ def with_min(value)
101
+ @checks[:min] = value
102
+ self
103
+ end
104
+
105
+ # @api public
106
+ # Requires the param to have the expected maximum.
107
+ #
108
+ # @param value [Numeric]
109
+ # The maximum.
110
+ # @return [self]
111
+ def with_max(value)
112
+ @checks[:max] = value
113
+ self
114
+ end
115
+
116
+ # @api public
117
+ # Requires the param to have the expected default.
118
+ #
119
+ # @param value [Object]
120
+ # The default.
121
+ # @return [self]
122
+ def with_default(value)
123
+ @checks[:default] = value
124
+ self
125
+ end
126
+
127
+ # @api public
128
+ # Requires the param to have the expected description.
129
+ #
130
+ # @param text [String]
131
+ # The description.
132
+ # @return [self]
133
+ def with_description(text)
134
+ @checks[:description] = text
135
+ self
136
+ end
137
+
138
+ # @api public
139
+ # Requires the param to have the expected example.
140
+ #
141
+ # @param value [Object]
142
+ # The example.
143
+ # @return [self]
144
+ def with_example(value)
145
+ @checks[:example] = value
146
+ self
147
+ end
148
+
149
+ def matches?(subject)
150
+ @subject = subject
151
+ param = subject.params[@name]
152
+ return fail_with("expected #{format_subject} to have param #{@name.inspect}") unless param
153
+
154
+ verify_all(param)
155
+ end
156
+
157
+ def description
158
+ parts = ["have param #{@name.inspect}"]
159
+ parts << "of type #{@checks[:type].inspect}" if @checks.key?(:type)
160
+ boolean_parts = boolean_description_parts
161
+ parts << "that is #{join_sentence(boolean_parts)}" if boolean_parts.any?
162
+ (VALUE_CHECKS - [:type]).each do |key|
163
+ parts << "with #{key} #{@checks[key].inspect}" if @checks.key?(key)
164
+ end
165
+ parts.join(' ')
166
+ end
167
+
168
+ private
169
+
170
+ def verify_all(param)
171
+ verify_value_checks(param) &&
172
+ verify_boolean_checks(param)
173
+ end
174
+
175
+ def verify_value_checks(param)
176
+ VALUE_CHECKS.each do |key|
177
+ next unless @checks.key?(key)
178
+
179
+ actual = param[key]
180
+ next if actual == @checks[key]
181
+
182
+ return fail_with(
183
+ "expected #{format_subject} to have param #{@name.inspect} " \
184
+ "#{format_value_check(key, @checks[key])}, but got #{actual.inspect}",
185
+ )
186
+ end
187
+ true
188
+ end
189
+
190
+ def verify_boolean_checks(param)
191
+ BOOLEAN_CHECKS.each do |check_name, config|
192
+ next unless @checks.key?(check_name)
193
+
194
+ actual = param[config[:key]]
195
+ next if actual == config[:expected]
196
+
197
+ return fail_with(
198
+ "expected #{format_subject} to have param #{@name.inspect} " \
199
+ "that is #{check_name}, but it is not",
200
+ )
201
+ end
202
+ true
203
+ end
204
+
205
+ def format_value_check(key, value)
206
+ case key
207
+ when :type
208
+ "of type #{value.inspect}"
209
+ else
210
+ "with #{key} #{value.inspect}"
211
+ end
212
+ end
213
+
214
+ def boolean_description_parts
215
+ parts = []
216
+ BOOLEAN_CHECKS.each_key { |key| parts << key.to_s if @checks.key?(key) }
217
+ parts
218
+ end
219
+ end
220
+ end
221
+ end
222
+ 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 path format.
8
+ class HavePathFormatMatcher < BaseMatcher
9
+ def initialize(format)
10
+ super()
11
+ @format = format
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.path_format
17
+ return true if actual == @format
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have path format #{@format.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have path 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 an action has the expected raises.
8
+ class HaveRaisesMatcher < BaseMatcher
9
+ def initialize(codes)
10
+ super()
11
+ @codes = codes
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.raises
17
+ return true if actual == @codes
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have raises #{@codes.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have raises #{@codes.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 contract has the expected representation.
8
+ class HaveRepresentationMatcher < BaseMatcher
9
+ def initialize(klass)
10
+ super()
11
+ @klass = klass
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.representation_class
17
+ return true if actual == @klass
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have representation #{@klass.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have representation #{@klass.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,142 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apiwork
4
+ module RSpec
5
+ module Matchers
6
+ # @api public
7
+ # Matcher for API resource structure.
8
+ class HaveResourceMatcher < BaseMatcher
9
+ VALUE_CHECKS = {
10
+ except: :except,
11
+ only: :only,
12
+ }.freeze
13
+
14
+ def initialize(name)
15
+ super()
16
+ @name = name
17
+ @parents = []
18
+ @checks = {}
19
+ end
20
+
21
+ # @api public
22
+ # Specifies the parent resources for a nested resource.
23
+ #
24
+ # @param parents [Array<Symbol>]
25
+ # The parent resource names.
26
+ # @return [self]
27
+ def under(*parents)
28
+ @parents = parents
29
+ self
30
+ end
31
+
32
+ # @api public
33
+ # Requires the resource to be singular.
34
+ #
35
+ # @return [self]
36
+ def singular
37
+ @checks[:singular] = true
38
+ self
39
+ end
40
+
41
+ # @api public
42
+ # Requires the resource to have the expected allowed actions.
43
+ #
44
+ # @param actions [Array<Symbol>] [:create, :destroy, :index, :show, :update]
45
+ # The allowed actions.
46
+ # @return [self]
47
+ def with_only(*actions)
48
+ @checks[:only] = actions.flatten
49
+ self
50
+ end
51
+
52
+ # @api public
53
+ # Requires the resource to have the expected excluded actions.
54
+ #
55
+ # @param actions [Array<Symbol>] [:create, :destroy, :index, :show, :update]
56
+ # The excluded actions.
57
+ # @return [self]
58
+ def with_except(*actions)
59
+ @checks[:except] = actions.flatten
60
+ self
61
+ end
62
+
63
+ def matches?(subject)
64
+ @subject = subject
65
+ resource = find_resource(subject)
66
+ return false unless resource
67
+
68
+ verify_singular(resource) &&
69
+ verify_value_checks(resource)
70
+ end
71
+
72
+ def description
73
+ parts = ["have resource #{@name.inspect}"]
74
+ parts << "under #{@parents.map(&:inspect).join(', ')}" if @parents.any?
75
+ parts << 'that is singular' if @checks.key?(:singular)
76
+ VALUE_CHECKS.each_key do |key|
77
+ parts << "with #{key} #{@checks[key].inspect}" if @checks.key?(key)
78
+ end
79
+ parts.join(' ')
80
+ end
81
+
82
+ private
83
+
84
+ def find_resource(subject)
85
+ parent = subject.root_resource
86
+
87
+ @parents.each do |parent_name|
88
+ child = parent.resources[parent_name]
89
+ unless child
90
+ fail_with(
91
+ "expected #{format_subject} to have resource #{@name.inspect} " \
92
+ "under #{@parents.map(&:inspect).join(', ')}, " \
93
+ "but #{parent_name.inspect} was not found",
94
+ )
95
+ return nil
96
+ end
97
+ parent = child
98
+ end
99
+
100
+ resource = parent.resources[@name]
101
+ unless resource
102
+ if @parents.any?
103
+ fail_with(
104
+ "expected #{format_subject} to have resource #{@name.inspect} " \
105
+ "under #{@parents.map(&:inspect).join(', ')}",
106
+ )
107
+ else
108
+ fail_with("expected #{format_subject} to have resource #{@name.inspect}")
109
+ end
110
+ return nil
111
+ end
112
+
113
+ resource
114
+ end
115
+
116
+ def verify_singular(resource)
117
+ return true unless @checks.key?(:singular)
118
+ return true if resource.singular
119
+
120
+ fail_with(
121
+ "expected #{format_subject} to have resource #{@name.inspect} that is singular, but it is not",
122
+ )
123
+ end
124
+
125
+ def verify_value_checks(resource)
126
+ VALUE_CHECKS.each do |key, method|
127
+ next unless @checks.key?(key)
128
+
129
+ actual = resource.public_send(method)
130
+ next if actual == @checks[key]
131
+
132
+ return fail_with(
133
+ "expected #{format_subject} to have resource #{@name.inspect} " \
134
+ "with #{key} #{@checks[key].inspect}, but got #{actual.inspect}",
135
+ )
136
+ end
137
+ true
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
@@ -0,0 +1,35 @@
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 root key.
8
+ class HaveRootMatcher < BaseMatcher
9
+ def initialize(singular, plural)
10
+ super()
11
+ @singular = singular
12
+ @plural = plural
13
+ end
14
+
15
+ def matches?(subject)
16
+ @subject = subject
17
+ root = subject.root_key
18
+ actual_singular = root.singular
19
+ actual_plural = root.plural
20
+
21
+ return true if actual_singular == @singular && actual_plural == @plural
22
+
23
+ fail_with(
24
+ "expected #{format_subject} to have root #{@singular.inspect}, #{@plural.inspect}, " \
25
+ "but got #{actual_singular.inspect}, #{actual_plural.inspect}",
26
+ )
27
+ end
28
+
29
+ def description
30
+ "have root #{@singular.inspect}, #{@plural.inspect}"
31
+ end
32
+ end
33
+ end
34
+ end
35
+ 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 summary.
8
+ class HaveSummaryMatcher < BaseMatcher
9
+ def initialize(text)
10
+ super()
11
+ @text = text
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.summary
17
+ return true if actual == @text
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have summary #{@text.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have summary #{@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 an action has the expected tags.
8
+ class HaveTagsMatcher < BaseMatcher
9
+ def initialize(tags)
10
+ super()
11
+ @tags = tags
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.tags
17
+ return true if actual == @tags
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have tags #{@tags.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have tags #{@tags.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 API info has the expected terms of service.
8
+ class HaveTermsOfServiceMatcher < BaseMatcher
9
+ def initialize(url)
10
+ super()
11
+ @url = url
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.terms_of_service
17
+ return true if actual == @url
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have terms of service #{@url.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have terms of service #{@url.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 API info has the expected title.
8
+ class HaveTitleMatcher < BaseMatcher
9
+ def initialize(text)
10
+ super()
11
+ @text = text
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.title
17
+ return true if actual == @text
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have title #{@text.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have title #{@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 representation has the expected type name.
8
+ class HaveTypeNameMatcher < BaseMatcher
9
+ def initialize(value)
10
+ super()
11
+ @value = value
12
+ end
13
+
14
+ def matches?(subject)
15
+ @subject = subject
16
+ actual = subject.type_name
17
+ return true if actual == @value
18
+
19
+ fail_with(
20
+ "expected #{format_subject} to have type name #{@value.inspect}, but got #{actual.inspect}",
21
+ )
22
+ end
23
+
24
+ def description
25
+ "have type name #{@value.inspect}"
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end