servactory 2.5.0.rc2 → 2.5.0.rc4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/generators/servactory/install_generator.rb +21 -0
- data/lib/generators/servactory/rspec_generator.rb +88 -0
- data/lib/generators/servactory/service_generator.rb +49 -0
- data/lib/generators/servactory/templates/services/application_service/base.rb +60 -0
- data/lib/generators/servactory/templates/services/application_service/exceptions.rb +11 -0
- data/lib/generators/servactory/templates/services/application_service/result.rb +5 -0
- data/lib/servactory/context/workspace/internals.rb +1 -1
- data/lib/servactory/info/dsl.rb +56 -4
- data/lib/servactory/maintenance/attributes/options/registrar.rb +1 -1
- data/lib/servactory/result.rb +1 -1
- data/lib/servactory/test_kit/rspec/helpers.rb +95 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +121 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +70 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +61 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +72 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +203 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +67 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +63 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +78 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +233 -0
- data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +148 -0
- data/lib/servactory/test_kit/rspec/matchers.rb +295 -0
- data/lib/servactory/test_kit/utils/faker.rb +78 -0
- data/lib/servactory/tool_kit/dynamic_options/format.rb +16 -12
- data/lib/servactory/version.rb +1 -1
- data/lib/servactory.rb +1 -0
- metadata +26 -7
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceAttributeMatchers
|
8
|
+
class MustMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name, must_names)
|
12
|
+
@described_class = described_class
|
13
|
+
@attribute_type = attribute_type
|
14
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
|
+
@attribute_name = attribute_name
|
16
|
+
@must_names = must_names
|
17
|
+
|
18
|
+
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
19
|
+
|
20
|
+
@missing_option = ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
"must: #{must_names.join(', ')}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def matches?(subject)
|
28
|
+
if submatcher_passes?(subject)
|
29
|
+
true
|
30
|
+
else
|
31
|
+
@missing_option = build_missing_option
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :described_class,
|
40
|
+
:attribute_type,
|
41
|
+
:attribute_type_plural,
|
42
|
+
:attribute_name,
|
43
|
+
:must_names,
|
44
|
+
:attribute_data
|
45
|
+
|
46
|
+
def submatcher_passes?(_subject)
|
47
|
+
attribute_must = attribute_data.fetch(:must)
|
48
|
+
|
49
|
+
attribute_must.keys.difference(must_names).empty? &&
|
50
|
+
must_names.difference(attribute_must.keys).empty?
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_missing_option
|
54
|
+
"should #{must_names.join(', ')}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceAttributeMatchers
|
8
|
+
class TypesMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name, types)
|
12
|
+
@described_class = described_class
|
13
|
+
@attribute_type = attribute_type
|
14
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
|
+
@attribute_name = attribute_name
|
16
|
+
@types = types
|
17
|
+
|
18
|
+
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
19
|
+
|
20
|
+
@missing_option = ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
result = "type"
|
25
|
+
result += types.size > 1 ? "s: " : ": "
|
26
|
+
result + types.join(", ")
|
27
|
+
end
|
28
|
+
|
29
|
+
def matches?(subject)
|
30
|
+
if submatcher_passes?(subject)
|
31
|
+
true
|
32
|
+
else
|
33
|
+
@missing_option = build_missing_option
|
34
|
+
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :described_class,
|
42
|
+
:attribute_type,
|
43
|
+
:attribute_type_plural,
|
44
|
+
:attribute_name,
|
45
|
+
:types,
|
46
|
+
:attribute_data
|
47
|
+
|
48
|
+
def submatcher_passes?(_subject)
|
49
|
+
option_types = attribute_data.fetch(:types)
|
50
|
+
|
51
|
+
option_types.difference(types).empty? &&
|
52
|
+
types.difference(option_types).empty?
|
53
|
+
end
|
54
|
+
|
55
|
+
def build_missing_option
|
56
|
+
option_types = attribute_data.fetch(:types)
|
57
|
+
|
58
|
+
text_about_types = types.size > 1 ? "with one of the following types" : "type"
|
59
|
+
|
60
|
+
<<~MESSAGE
|
61
|
+
should be a value with #{text_about_types}
|
62
|
+
|
63
|
+
expected #{types.inspect}
|
64
|
+
got #{option_types.inspect}
|
65
|
+
MESSAGE
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,203 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
class HaveServiceInputMatcher # rubocop:disable Metrics/ClassLength
|
8
|
+
attr_reader :described_class, :input_name, :options
|
9
|
+
|
10
|
+
def initialize(described_class, input_name)
|
11
|
+
@described_class = described_class
|
12
|
+
@input_name = input_name
|
13
|
+
|
14
|
+
@options = {}
|
15
|
+
@submatchers = []
|
16
|
+
|
17
|
+
@missing = ""
|
18
|
+
end
|
19
|
+
|
20
|
+
def supports_block_expectations?
|
21
|
+
true
|
22
|
+
end
|
23
|
+
|
24
|
+
def type(type)
|
25
|
+
@option_types = Array(type)
|
26
|
+
add_submatcher(
|
27
|
+
HaveServiceAttributeMatchers::TypesMatcher,
|
28
|
+
described_class,
|
29
|
+
:input,
|
30
|
+
input_name,
|
31
|
+
@option_types
|
32
|
+
)
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def types(*types)
|
37
|
+
@option_types = types
|
38
|
+
add_submatcher(
|
39
|
+
HaveServiceAttributeMatchers::TypesMatcher,
|
40
|
+
described_class,
|
41
|
+
:input,
|
42
|
+
input_name,
|
43
|
+
@option_types
|
44
|
+
)
|
45
|
+
self
|
46
|
+
end
|
47
|
+
|
48
|
+
def required(custom_message = nil)
|
49
|
+
remove_submatcher(HaveServiceInputMatchers::OptionalMatcher)
|
50
|
+
add_submatcher(
|
51
|
+
HaveServiceInputMatchers::RequiredMatcher,
|
52
|
+
described_class,
|
53
|
+
:input,
|
54
|
+
input_name,
|
55
|
+
custom_message
|
56
|
+
)
|
57
|
+
self
|
58
|
+
end
|
59
|
+
|
60
|
+
def optional
|
61
|
+
remove_submatcher(HaveServiceInputMatchers::RequiredMatcher)
|
62
|
+
add_submatcher(
|
63
|
+
HaveServiceInputMatchers::OptionalMatcher,
|
64
|
+
described_class,
|
65
|
+
:input,
|
66
|
+
input_name
|
67
|
+
)
|
68
|
+
self
|
69
|
+
end
|
70
|
+
|
71
|
+
def default(value)
|
72
|
+
add_submatcher(
|
73
|
+
HaveServiceInputMatchers::DefaultMatcher,
|
74
|
+
described_class,
|
75
|
+
:input,
|
76
|
+
input_name,
|
77
|
+
value
|
78
|
+
)
|
79
|
+
self
|
80
|
+
end
|
81
|
+
|
82
|
+
def consists_of(*types) # rubocop:disable Metrics/MethodLength
|
83
|
+
message = block_given? ? yield : nil
|
84
|
+
|
85
|
+
add_submatcher(
|
86
|
+
HaveServiceAttributeMatchers::ConsistsOfMatcher,
|
87
|
+
described_class,
|
88
|
+
:input,
|
89
|
+
input_name,
|
90
|
+
@option_types,
|
91
|
+
Array(types),
|
92
|
+
message
|
93
|
+
)
|
94
|
+
self
|
95
|
+
end
|
96
|
+
|
97
|
+
def inclusion(values)
|
98
|
+
add_submatcher(
|
99
|
+
HaveServiceAttributeMatchers::InclusionMatcher,
|
100
|
+
described_class,
|
101
|
+
:input,
|
102
|
+
input_name,
|
103
|
+
Array(values)
|
104
|
+
)
|
105
|
+
self
|
106
|
+
end
|
107
|
+
|
108
|
+
def must(*must_names)
|
109
|
+
add_submatcher(
|
110
|
+
HaveServiceAttributeMatchers::MustMatcher,
|
111
|
+
described_class,
|
112
|
+
:input,
|
113
|
+
input_name,
|
114
|
+
Array(must_names)
|
115
|
+
)
|
116
|
+
self
|
117
|
+
end
|
118
|
+
|
119
|
+
def valid_with(attributes)
|
120
|
+
add_submatcher(
|
121
|
+
HaveServiceInputMatchers::ValidWithMatcher,
|
122
|
+
described_class,
|
123
|
+
:input,
|
124
|
+
input_name,
|
125
|
+
attributes
|
126
|
+
)
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
# NOTE: Used for delayed chain implementation
|
131
|
+
# def not_implemented_chain(*description)
|
132
|
+
# Kernel.warn <<-MESSAGE.squish
|
133
|
+
# This chain has not yet been implemented.
|
134
|
+
# This message is for informational purposes only.
|
135
|
+
# Description: #{description}
|
136
|
+
# MESSAGE
|
137
|
+
# self
|
138
|
+
# end
|
139
|
+
|
140
|
+
def description
|
141
|
+
"#{input_name} with #{submatchers.map(&:description).join(', ')}"
|
142
|
+
end
|
143
|
+
|
144
|
+
def failure_message
|
145
|
+
"Expected #{expectation}, which #{missing_options}"
|
146
|
+
end
|
147
|
+
|
148
|
+
def failure_message_when_negated
|
149
|
+
"Did not expect #{expectation} with specified options"
|
150
|
+
end
|
151
|
+
|
152
|
+
def matches?(subject)
|
153
|
+
@subject = subject
|
154
|
+
|
155
|
+
submatchers_match?
|
156
|
+
end
|
157
|
+
|
158
|
+
protected
|
159
|
+
|
160
|
+
attr_reader :submatchers, :missing, :subject
|
161
|
+
|
162
|
+
def add_submatcher(matcher_class, *args)
|
163
|
+
remove_submatcher(matcher_class)
|
164
|
+
submatchers << matcher_class.new(*args)
|
165
|
+
end
|
166
|
+
|
167
|
+
def remove_submatcher(matcher_class)
|
168
|
+
submatchers.delete_if do |submatcher|
|
169
|
+
submatcher.is_a?(matcher_class)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def expectation
|
174
|
+
"#{described_class.name} to have a service input attribute named #{input_name}"
|
175
|
+
end
|
176
|
+
|
177
|
+
def missing_options
|
178
|
+
missing_options = [missing, missing_options_for_failing_submatchers]
|
179
|
+
missing_options.flatten.select(&:present?).join(", ")
|
180
|
+
end
|
181
|
+
|
182
|
+
def failing_submatchers
|
183
|
+
@failing_submatchers ||= submatchers.reject do |matcher|
|
184
|
+
matcher.matches?(subject)
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def missing_options_for_failing_submatchers
|
189
|
+
if defined?(failing_submatchers)
|
190
|
+
failing_submatchers.map(&:missing_option)
|
191
|
+
else
|
192
|
+
[]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
def submatchers_match?
|
197
|
+
failing_submatchers.empty?
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceInputMatchers
|
8
|
+
class DefaultMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name, default_value)
|
12
|
+
@described_class = described_class
|
13
|
+
@attribute_type = attribute_type
|
14
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
|
+
@attribute_name = attribute_name
|
16
|
+
@default_value = default_value
|
17
|
+
|
18
|
+
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
19
|
+
|
20
|
+
@missing_option = ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
"default: #{default_value.inspect}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def matches?(subject)
|
28
|
+
if submatcher_passes?(subject)
|
29
|
+
true
|
30
|
+
else
|
31
|
+
@missing_option = build_missing_option
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :described_class,
|
40
|
+
:attribute_type,
|
41
|
+
:attribute_type_plural,
|
42
|
+
:attribute_name,
|
43
|
+
:default_value,
|
44
|
+
:attribute_data
|
45
|
+
|
46
|
+
def submatcher_passes?(_subject)
|
47
|
+
attribute_default_value = attribute_data.fetch(:default)
|
48
|
+
|
49
|
+
attribute_default_value.casecmp(default_value).zero?
|
50
|
+
end
|
51
|
+
|
52
|
+
def build_missing_option
|
53
|
+
attribute_default_value = attribute_data.fetch(:default)
|
54
|
+
|
55
|
+
<<~MESSAGE
|
56
|
+
should have a default value
|
57
|
+
|
58
|
+
expected #{default_value.inspect}
|
59
|
+
got #{attribute_default_value.inspect}
|
60
|
+
MESSAGE
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceInputMatchers
|
8
|
+
class OptionalMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name)
|
12
|
+
@described_class = described_class
|
13
|
+
@attribute_type = attribute_type
|
14
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
|
+
@attribute_name = attribute_name
|
16
|
+
|
17
|
+
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
18
|
+
|
19
|
+
@missing_option = ""
|
20
|
+
end
|
21
|
+
|
22
|
+
def description
|
23
|
+
"required: false"
|
24
|
+
end
|
25
|
+
|
26
|
+
def matches?(subject)
|
27
|
+
if submatcher_passes?(subject)
|
28
|
+
true
|
29
|
+
else
|
30
|
+
@missing_option = build_missing_option
|
31
|
+
|
32
|
+
false
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :described_class,
|
39
|
+
:attribute_type,
|
40
|
+
:attribute_type_plural,
|
41
|
+
:attribute_name,
|
42
|
+
:attribute_data
|
43
|
+
|
44
|
+
def submatcher_passes?(_subject)
|
45
|
+
input_required = attribute_data.fetch(:required).fetch(:is)
|
46
|
+
|
47
|
+
input_required == false
|
48
|
+
end
|
49
|
+
|
50
|
+
def build_missing_option
|
51
|
+
<<~MESSAGE
|
52
|
+
should be optional
|
53
|
+
|
54
|
+
expected required: false
|
55
|
+
got required: true
|
56
|
+
MESSAGE
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Servactory
|
4
|
+
module TestKit
|
5
|
+
module Rspec
|
6
|
+
module Matchers
|
7
|
+
module HaveServiceInputMatchers
|
8
|
+
class RequiredMatcher
|
9
|
+
attr_reader :missing_option
|
10
|
+
|
11
|
+
def initialize(described_class, attribute_type, attribute_name, custom_message)
|
12
|
+
@described_class = described_class
|
13
|
+
@attribute_type = attribute_type
|
14
|
+
@attribute_type_plural = attribute_type.to_s.pluralize.to_sym
|
15
|
+
@attribute_name = attribute_name
|
16
|
+
@custom_message = custom_message
|
17
|
+
|
18
|
+
@attribute_data = described_class.info.public_send(attribute_type_plural).fetch(attribute_name)
|
19
|
+
|
20
|
+
@missing_option = ""
|
21
|
+
end
|
22
|
+
|
23
|
+
def description
|
24
|
+
"required: true"
|
25
|
+
end
|
26
|
+
|
27
|
+
def matches?(subject)
|
28
|
+
if submatcher_passes?(subject)
|
29
|
+
true
|
30
|
+
else
|
31
|
+
@missing_option = build_missing_option
|
32
|
+
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :described_class,
|
40
|
+
:attribute_type,
|
41
|
+
:attribute_type_plural,
|
42
|
+
:attribute_name,
|
43
|
+
:custom_message,
|
44
|
+
:attribute_data
|
45
|
+
|
46
|
+
def submatcher_passes?(_subject) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
47
|
+
attribute_required = attribute_data.fetch(:required).fetch(:is)
|
48
|
+
attribute_required_message = attribute_data.fetch(:required).fetch(:message)
|
49
|
+
|
50
|
+
matched = attribute_required == true
|
51
|
+
|
52
|
+
if attribute_required_message.nil?
|
53
|
+
attribute_required_message = I18n.t(
|
54
|
+
"servactory.#{attribute_type_plural}.validations.required.default_error.default",
|
55
|
+
service_class_name: described_class.name,
|
56
|
+
"#{attribute_type}_name": attribute_name
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
matched &&= attribute_required_message.casecmp(custom_message).zero? if custom_message.present?
|
61
|
+
|
62
|
+
matched
|
63
|
+
end
|
64
|
+
|
65
|
+
def build_missing_option
|
66
|
+
<<~MESSAGE
|
67
|
+
should be required
|
68
|
+
|
69
|
+
expected required: true
|
70
|
+
got required: false
|
71
|
+
MESSAGE
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|