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.
- checksums.yaml +7 -0
- data/LICENSE.txt +21 -0
- data/Rakefile +10 -0
- data/lib/apiwork/rspec/matchers/base_matcher.rb +44 -0
- data/lib/apiwork/rspec/matchers/define_contact_matcher.rb +80 -0
- data/lib/apiwork/rspec/matchers/define_enum_matcher.rb +139 -0
- data/lib/apiwork/rspec/matchers/define_license_matcher.rb +53 -0
- data/lib/apiwork/rspec/matchers/define_server_matcher.rb +50 -0
- data/lib/apiwork/rspec/matchers/have_association_matcher.rb +247 -0
- data/lib/apiwork/rspec/matchers/have_attribute_matcher.rb +283 -0
- data/lib/apiwork/rspec/matchers/have_description_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_discriminator_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_example_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_export_matcher.rb +29 -0
- data/lib/apiwork/rspec/matchers/have_identifier_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_import_matcher.rb +34 -0
- data/lib/apiwork/rspec/matchers/have_key_format_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_model_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_operation_id_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_param_matcher.rb +222 -0
- data/lib/apiwork/rspec/matchers/have_path_format_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_raises_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_representation_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_resource_matcher.rb +142 -0
- data/lib/apiwork/rspec/matchers/have_root_matcher.rb +35 -0
- data/lib/apiwork/rspec/matchers/have_summary_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_tags_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_terms_of_service_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_title_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_type_name_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/have_variant_matcher.rb +127 -0
- data/lib/apiwork/rspec/matchers/have_version_matcher.rb +30 -0
- data/lib/apiwork/rspec/matchers/no_content_matcher.rb +22 -0
- data/lib/apiwork/rspec/matchers/param_wrapper.rb +17 -0
- data/lib/apiwork/rspec/matchers.rb +388 -0
- data/lib/apiwork/rspec/version.rb +7 -0
- data/lib/apiwork/rspec.rb +25 -0
- data/lib/apiwork-rspec.rb +3 -0
- metadata +154 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 83aafbeb0b15f739a2b538f43f386322d94631de471addc08f1b69196a38e254
|
|
4
|
+
data.tar.gz: 34000a080944fe635de1e5bd6d4b07021e8f6c9f8d1d7feb86c496373e3f05e3
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 186e6a9a8e19a7f5507dd9152e9e3d22284faecf4b97fbcf58d47b03d52e2ce3d6d143084d141786f444eb7b8ce2cfb0a9e75f122805b54052c9096c2761760e
|
|
7
|
+
data.tar.gz: e38d9d193b0b5ddf9b3df0282502402fd4da58caaddde442cef8227642330053ad9edc9140bb1aefe7f8f73ec7f726c2a14893ec5c2c5a3c1c80e6cf6f686789
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Copyright (c) 2026 skiftle
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module RSpec
|
|
5
|
+
module Matchers
|
|
6
|
+
# @api public
|
|
7
|
+
# Base class for matchers.
|
|
8
|
+
class BaseMatcher
|
|
9
|
+
include ::RSpec::Matchers::Composable
|
|
10
|
+
|
|
11
|
+
attr_reader :failure_message
|
|
12
|
+
|
|
13
|
+
def initialize
|
|
14
|
+
@failure_message = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def does_not_match?(subject)
|
|
18
|
+
!matches?(subject)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def failure_message_when_negated
|
|
22
|
+
"expected #{format_subject} not to #{description}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def format_subject
|
|
28
|
+
@subject.respond_to?(:name) ? @subject.name : @subject.to_s
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fail_with(message)
|
|
32
|
+
@failure_message = message
|
|
33
|
+
false
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def join_sentence(words)
|
|
37
|
+
return words.join(' and ') if words.size <= 2
|
|
38
|
+
|
|
39
|
+
"#{words[0..-2].join(', ')}, and #{words[-1]}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
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 defines a contact.
|
|
8
|
+
class DefineContactMatcher < BaseMatcher
|
|
9
|
+
VALUE_CHECKS = {
|
|
10
|
+
email: :email,
|
|
11
|
+
url: :url,
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(name)
|
|
15
|
+
super()
|
|
16
|
+
@name = name
|
|
17
|
+
@checks = {}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @api public
|
|
21
|
+
# Requires the contact to have the expected email.
|
|
22
|
+
#
|
|
23
|
+
# @param email [String]
|
|
24
|
+
# The email.
|
|
25
|
+
# @return [self]
|
|
26
|
+
def with_email(email)
|
|
27
|
+
@checks[:email] = email
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @api public
|
|
32
|
+
# Requires the contact to have the expected URL.
|
|
33
|
+
#
|
|
34
|
+
# @param url [String]
|
|
35
|
+
# The URL.
|
|
36
|
+
# @return [self]
|
|
37
|
+
def with_url(url)
|
|
38
|
+
@checks[:url] = url
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def matches?(subject)
|
|
43
|
+
@subject = subject
|
|
44
|
+
contact = subject.contact
|
|
45
|
+
return fail_with("expected #{format_subject} to define contact #{@name.inspect}") unless contact
|
|
46
|
+
unless contact.name == @name
|
|
47
|
+
return fail_with("expected #{format_subject} to define contact #{@name.inspect}, but got #{contact.name.inspect}")
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
verify_value_checks(contact)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def description
|
|
54
|
+
parts = ["define contact #{@name.inspect}"]
|
|
55
|
+
VALUE_CHECKS.each_key do |key|
|
|
56
|
+
parts << "with #{key} #{@checks[key].inspect}" if @checks.key?(key)
|
|
57
|
+
end
|
|
58
|
+
parts.join(' ')
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def verify_value_checks(contact)
|
|
64
|
+
VALUE_CHECKS.each do |key, method|
|
|
65
|
+
next unless @checks.key?(key)
|
|
66
|
+
|
|
67
|
+
actual = contact.public_send(method)
|
|
68
|
+
next if actual == @checks[key]
|
|
69
|
+
|
|
70
|
+
return fail_with(
|
|
71
|
+
"expected #{format_subject} to define contact #{@name.inspect} " \
|
|
72
|
+
"with #{key} #{@checks[key].inspect}, but got #{actual.inspect}",
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module RSpec
|
|
5
|
+
module Matchers
|
|
6
|
+
# @api public
|
|
7
|
+
# Matcher for enum definitions.
|
|
8
|
+
class DefineEnumMatcher < BaseMatcher
|
|
9
|
+
VALUE_CHECKS = {
|
|
10
|
+
description: :description,
|
|
11
|
+
example: :example,
|
|
12
|
+
}.freeze
|
|
13
|
+
|
|
14
|
+
def initialize(name)
|
|
15
|
+
super()
|
|
16
|
+
@name = name
|
|
17
|
+
@expected_values = nil
|
|
18
|
+
@checks = {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @api public
|
|
22
|
+
# Requires the enum to have the expected values.
|
|
23
|
+
#
|
|
24
|
+
# @param values [Array]
|
|
25
|
+
# The enum values.
|
|
26
|
+
# @return [self]
|
|
27
|
+
def with_values(values)
|
|
28
|
+
@expected_values = values
|
|
29
|
+
self
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @api public
|
|
33
|
+
# Requires the enum to be deprecated.
|
|
34
|
+
#
|
|
35
|
+
# @return [self]
|
|
36
|
+
def deprecated
|
|
37
|
+
@checks[:deprecated] = true
|
|
38
|
+
self
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @api public
|
|
42
|
+
# Requires the enum to have the expected description.
|
|
43
|
+
#
|
|
44
|
+
# @param text [String]
|
|
45
|
+
# The description.
|
|
46
|
+
# @return [self]
|
|
47
|
+
def with_description(text)
|
|
48
|
+
@checks[:description] = text
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @api public
|
|
53
|
+
# Requires the enum to have the expected example.
|
|
54
|
+
#
|
|
55
|
+
# @param value [Object]
|
|
56
|
+
# The example.
|
|
57
|
+
# @return [self]
|
|
58
|
+
def with_example(value)
|
|
59
|
+
@checks[:example] = value
|
|
60
|
+
self
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def matches?(subject)
|
|
64
|
+
@subject = subject
|
|
65
|
+
values = subject.enum_values(@name)
|
|
66
|
+
return fail_with("expected #{format_subject} to define enum #{@name.inspect}") unless values
|
|
67
|
+
|
|
68
|
+
verify_values(values) &&
|
|
69
|
+
verify_metadata(subject)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def description
|
|
73
|
+
parts = ["define enum #{@name.inspect}"]
|
|
74
|
+
parts << 'that is deprecated' if @checks.key?(:deprecated)
|
|
75
|
+
parts << "with values #{@expected_values.inspect}" if @expected_values
|
|
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 verify_values(values)
|
|
85
|
+
return true unless @expected_values
|
|
86
|
+
return true if values == @expected_values
|
|
87
|
+
|
|
88
|
+
fail_with(
|
|
89
|
+
"expected #{format_subject} to define enum #{@name.inspect} " \
|
|
90
|
+
"with values #{@expected_values.inspect}, but got #{values.inspect}",
|
|
91
|
+
)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def verify_metadata(subject)
|
|
95
|
+
definition = find_enum_definition(subject)
|
|
96
|
+
return true unless definition_required?
|
|
97
|
+
return fail_with("expected #{format_subject} to support enum metadata (use API class)") unless definition
|
|
98
|
+
|
|
99
|
+
verify_deprecated(definition) &&
|
|
100
|
+
verify_value_checks(definition)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def find_enum_definition(subject)
|
|
104
|
+
return unless subject.respond_to?(:enum_registry)
|
|
105
|
+
|
|
106
|
+
subject.enum_registry[@name]
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def definition_required?
|
|
110
|
+
@checks.key?(:deprecated) || @checks.key?(:description) || @checks.key?(:example)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def verify_deprecated(definition)
|
|
114
|
+
return true unless @checks.key?(:deprecated)
|
|
115
|
+
return true if definition.deprecated?
|
|
116
|
+
|
|
117
|
+
fail_with(
|
|
118
|
+
"expected #{format_subject} to define enum #{@name.inspect} that is deprecated, but it is not",
|
|
119
|
+
)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def verify_value_checks(definition)
|
|
123
|
+
VALUE_CHECKS.each do |key, method|
|
|
124
|
+
next unless @checks.key?(key)
|
|
125
|
+
|
|
126
|
+
actual = definition.public_send(method)
|
|
127
|
+
next if actual == @checks[key]
|
|
128
|
+
|
|
129
|
+
return fail_with(
|
|
130
|
+
"expected #{format_subject} to define enum #{@name.inspect} " \
|
|
131
|
+
"with #{key} #{@checks[key].inspect}, but got #{actual.inspect}",
|
|
132
|
+
)
|
|
133
|
+
end
|
|
134
|
+
true
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
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 defines a license.
|
|
8
|
+
class DefineLicenseMatcher < BaseMatcher
|
|
9
|
+
def initialize(name)
|
|
10
|
+
super()
|
|
11
|
+
@name = name
|
|
12
|
+
@checks = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @api public
|
|
16
|
+
# Requires the license to have the expected URL.
|
|
17
|
+
#
|
|
18
|
+
# @param url [String]
|
|
19
|
+
# The URL.
|
|
20
|
+
# @return [self]
|
|
21
|
+
def with_url(url)
|
|
22
|
+
@checks[:url] = url
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def matches?(subject)
|
|
27
|
+
@subject = subject
|
|
28
|
+
license = subject.license
|
|
29
|
+
return fail_with("expected #{format_subject} to define license #{@name.inspect}") unless license
|
|
30
|
+
unless license.name == @name
|
|
31
|
+
return fail_with("expected #{format_subject} to define license #{@name.inspect}, but got #{license.name.inspect}")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
return true unless @checks.key?(:url)
|
|
35
|
+
|
|
36
|
+
actual = license.url
|
|
37
|
+
return true if actual == @checks[:url]
|
|
38
|
+
|
|
39
|
+
fail_with(
|
|
40
|
+
"expected #{format_subject} to define license #{@name.inspect} " \
|
|
41
|
+
"with url #{@checks[:url].inspect}, but got #{actual.inspect}",
|
|
42
|
+
)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def description
|
|
46
|
+
parts = ["define license #{@name.inspect}"]
|
|
47
|
+
parts << "with url #{@checks[:url].inspect}" if @checks.key?(:url)
|
|
48
|
+
parts.join(' ')
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
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 defines a server.
|
|
8
|
+
class DefineServerMatcher < BaseMatcher
|
|
9
|
+
def initialize(url)
|
|
10
|
+
super()
|
|
11
|
+
@url = url
|
|
12
|
+
@checks = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @api public
|
|
16
|
+
# Requires the server to have the expected description.
|
|
17
|
+
#
|
|
18
|
+
# @param text [String]
|
|
19
|
+
# The description.
|
|
20
|
+
# @return [self]
|
|
21
|
+
def with_description(text)
|
|
22
|
+
@checks[:description] = text
|
|
23
|
+
self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def matches?(subject)
|
|
27
|
+
@subject = subject
|
|
28
|
+
server = subject.servers.find { |s| s.url == @url }
|
|
29
|
+
return fail_with("expected #{format_subject} to define server #{@url.inspect}") unless server
|
|
30
|
+
|
|
31
|
+
return true unless @checks.key?(:description)
|
|
32
|
+
|
|
33
|
+
actual = server.description
|
|
34
|
+
return true if actual == @checks[:description]
|
|
35
|
+
|
|
36
|
+
fail_with(
|
|
37
|
+
"expected #{format_subject} to define server #{@url.inspect} " \
|
|
38
|
+
"with description #{@checks[:description].inspect}, but got #{actual.inspect}",
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def description
|
|
43
|
+
parts = ["define server #{@url.inspect}"]
|
|
44
|
+
parts << "with description #{@checks[:description].inspect}" if @checks.key?(:description)
|
|
45
|
+
parts.join(' ')
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Apiwork
|
|
4
|
+
module RSpec
|
|
5
|
+
module Matchers
|
|
6
|
+
# @api public
|
|
7
|
+
# Matcher for representation associations.
|
|
8
|
+
class HaveAssociationMatcher < BaseMatcher
|
|
9
|
+
BOOLEAN_CHECKS = {
|
|
10
|
+
allow_destroy: :allow_destroy,
|
|
11
|
+
deprecated: :deprecated?,
|
|
12
|
+
filterable: :filterable?,
|
|
13
|
+
nullable: :nullable?,
|
|
14
|
+
polymorphic: :polymorphic?,
|
|
15
|
+
sortable: :sortable?,
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
VALUE_CHECKS = {
|
|
19
|
+
description: :description,
|
|
20
|
+
example: :example,
|
|
21
|
+
include: :include,
|
|
22
|
+
representation: :representation_class,
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
def initialize(name)
|
|
26
|
+
super()
|
|
27
|
+
@name = name
|
|
28
|
+
@checks = {}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# @api public
|
|
32
|
+
# Requires the association to have the expected type.
|
|
33
|
+
#
|
|
34
|
+
# @param type [Symbol] [:belongs_to, :has_many, :has_one]
|
|
35
|
+
# The type.
|
|
36
|
+
# @return [self]
|
|
37
|
+
def of_type(type)
|
|
38
|
+
@checks[:type] = type
|
|
39
|
+
self
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# @api public
|
|
43
|
+
# Requires the association to be writable.
|
|
44
|
+
#
|
|
45
|
+
# @param action [Symbol, Boolean] (true) [Symbol: :create, :update]
|
|
46
|
+
# The action to check writability for, or `true` for any action.
|
|
47
|
+
# @return [self]
|
|
48
|
+
def writable(action = true)
|
|
49
|
+
@checks[:writable] = action
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @api public
|
|
54
|
+
# Requires the association to allow destroy.
|
|
55
|
+
#
|
|
56
|
+
# @return [self]
|
|
57
|
+
def allow_destroy
|
|
58
|
+
@checks[:allow_destroy] = true
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @api public
|
|
63
|
+
# Requires the association 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 association to be filterable.
|
|
73
|
+
#
|
|
74
|
+
# @return [self]
|
|
75
|
+
def filterable
|
|
76
|
+
@checks[:filterable] = true
|
|
77
|
+
self
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @api public
|
|
81
|
+
# Requires the association to be nullable.
|
|
82
|
+
#
|
|
83
|
+
# @return [self]
|
|
84
|
+
def nullable
|
|
85
|
+
@checks[:nullable] = true
|
|
86
|
+
self
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# @api public
|
|
90
|
+
# Requires the association to be polymorphic.
|
|
91
|
+
#
|
|
92
|
+
# @return [self]
|
|
93
|
+
def polymorphic
|
|
94
|
+
@checks[:polymorphic] = true
|
|
95
|
+
self
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# @api public
|
|
99
|
+
# Requires the association to be sortable.
|
|
100
|
+
#
|
|
101
|
+
# @return [self]
|
|
102
|
+
def sortable
|
|
103
|
+
@checks[:sortable] = true
|
|
104
|
+
self
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# @api public
|
|
108
|
+
# Requires the association to have the expected inclusion strategy.
|
|
109
|
+
#
|
|
110
|
+
# @param value [Symbol] [:always, :optional]
|
|
111
|
+
# The inclusion strategy.
|
|
112
|
+
# @return [self]
|
|
113
|
+
def with_include(value)
|
|
114
|
+
@checks[:include] = value
|
|
115
|
+
self
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# @api public
|
|
119
|
+
# Requires the association to have the expected representation class.
|
|
120
|
+
#
|
|
121
|
+
# @param klass [Class]
|
|
122
|
+
# The representation class.
|
|
123
|
+
# @return [self]
|
|
124
|
+
def with_representation(klass)
|
|
125
|
+
@checks[:representation] = klass
|
|
126
|
+
self
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# @api public
|
|
130
|
+
# Requires the association to have the expected description.
|
|
131
|
+
#
|
|
132
|
+
# @param text [String]
|
|
133
|
+
# The description.
|
|
134
|
+
# @return [self]
|
|
135
|
+
def with_description(text)
|
|
136
|
+
@checks[:description] = text
|
|
137
|
+
self
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# @api public
|
|
141
|
+
# Requires the association to have the expected example.
|
|
142
|
+
#
|
|
143
|
+
# @param value [Object]
|
|
144
|
+
# The example.
|
|
145
|
+
# @return [self]
|
|
146
|
+
def with_example(value)
|
|
147
|
+
@checks[:example] = value
|
|
148
|
+
self
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def matches?(subject)
|
|
152
|
+
@subject = subject
|
|
153
|
+
association = subject.associations[@name]
|
|
154
|
+
return fail_with("expected #{format_subject} to have association #{@name.inspect}") unless association
|
|
155
|
+
|
|
156
|
+
verify_all(association)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def description
|
|
160
|
+
parts = ["have association #{@name.inspect}"]
|
|
161
|
+
parts << "of type #{@checks[:type].inspect}" if @checks.key?(:type)
|
|
162
|
+
boolean_parts = boolean_description_parts
|
|
163
|
+
parts << "that is #{join_sentence(boolean_parts)}" if boolean_parts.any?
|
|
164
|
+
VALUE_CHECKS.each_key do |key|
|
|
165
|
+
parts << "with #{key} #{@checks[key].inspect}" if @checks.key?(key)
|
|
166
|
+
end
|
|
167
|
+
parts.join(' ')
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
private
|
|
171
|
+
|
|
172
|
+
def verify_all(association)
|
|
173
|
+
verify_type(association) &&
|
|
174
|
+
verify_boolean_checks(association) &&
|
|
175
|
+
verify_writable(association) &&
|
|
176
|
+
verify_value_checks(association)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def verify_type(association)
|
|
180
|
+
return true unless @checks.key?(:type)
|
|
181
|
+
return true if association.type == @checks[:type]
|
|
182
|
+
|
|
183
|
+
fail_with(
|
|
184
|
+
"expected #{format_subject} to have association #{@name.inspect} " \
|
|
185
|
+
"of type #{@checks[:type].inspect}, but got #{association.type.inspect}",
|
|
186
|
+
)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def verify_boolean_checks(association)
|
|
190
|
+
BOOLEAN_CHECKS.each do |key, method|
|
|
191
|
+
next unless @checks.key?(key)
|
|
192
|
+
next if association.public_send(method)
|
|
193
|
+
|
|
194
|
+
return fail_with(
|
|
195
|
+
"expected #{format_subject} to have association #{@name.inspect} that is #{key}, but it is not",
|
|
196
|
+
)
|
|
197
|
+
end
|
|
198
|
+
true
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def verify_writable(association)
|
|
202
|
+
return true unless @checks.key?(:writable)
|
|
203
|
+
|
|
204
|
+
expected = @checks[:writable]
|
|
205
|
+
if expected == true
|
|
206
|
+
return true if association.writable?
|
|
207
|
+
|
|
208
|
+
return fail_with(
|
|
209
|
+
"expected #{format_subject} to have association #{@name.inspect} that is writable, but it is not",
|
|
210
|
+
)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
return true if association.writable_for?(expected)
|
|
214
|
+
|
|
215
|
+
fail_with(
|
|
216
|
+
"expected #{format_subject} to have association #{@name.inspect} " \
|
|
217
|
+
"that is writable for #{expected.inspect}, but it is not",
|
|
218
|
+
)
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
def verify_value_checks(association)
|
|
222
|
+
VALUE_CHECKS.each do |key, method|
|
|
223
|
+
next unless @checks.key?(key)
|
|
224
|
+
|
|
225
|
+
actual = association.public_send(method)
|
|
226
|
+
next if actual == @checks[key]
|
|
227
|
+
|
|
228
|
+
return fail_with(
|
|
229
|
+
"expected #{format_subject} to have association #{@name.inspect} " \
|
|
230
|
+
"with #{key} #{@checks[key].inspect}, but got #{actual.inspect}",
|
|
231
|
+
)
|
|
232
|
+
end
|
|
233
|
+
true
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def boolean_description_parts
|
|
237
|
+
parts = []
|
|
238
|
+
BOOLEAN_CHECKS.each_key { |key| parts << key.to_s if @checks.key?(key) }
|
|
239
|
+
if @checks.key?(:writable)
|
|
240
|
+
parts << (@checks[:writable] == true ? 'writable' : "writable for #{@checks[:writable].inspect}")
|
|
241
|
+
end
|
|
242
|
+
parts
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
end
|
|
247
|
+
end
|