servactory 2.5.0.rc2 → 2.5.0.rc4

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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/generators/servactory/install_generator.rb +21 -0
  4. data/lib/generators/servactory/rspec_generator.rb +88 -0
  5. data/lib/generators/servactory/service_generator.rb +49 -0
  6. data/lib/generators/servactory/templates/services/application_service/base.rb +60 -0
  7. data/lib/generators/servactory/templates/services/application_service/exceptions.rb +11 -0
  8. data/lib/generators/servactory/templates/services/application_service/result.rb +5 -0
  9. data/lib/servactory/context/workspace/internals.rb +1 -1
  10. data/lib/servactory/info/dsl.rb +56 -4
  11. data/lib/servactory/maintenance/attributes/options/registrar.rb +1 -1
  12. data/lib/servactory/result.rb +1 -1
  13. data/lib/servactory/test_kit/rspec/helpers.rb +95 -0
  14. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/consists_of_matcher.rb +121 -0
  15. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/inclusion_matcher.rb +70 -0
  16. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/must_matcher.rb +61 -0
  17. data/lib/servactory/test_kit/rspec/matchers/have_service_attribute_matchers/types_matcher.rb +72 -0
  18. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matcher.rb +203 -0
  19. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/default_matcher.rb +67 -0
  20. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/optional_matcher.rb +63 -0
  21. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/required_matcher.rb +78 -0
  22. data/lib/servactory/test_kit/rspec/matchers/have_service_input_matchers/valid_with_matcher.rb +233 -0
  23. data/lib/servactory/test_kit/rspec/matchers/have_service_internal_matcher.rb +148 -0
  24. data/lib/servactory/test_kit/rspec/matchers.rb +295 -0
  25. data/lib/servactory/test_kit/utils/faker.rb +78 -0
  26. data/lib/servactory/tool_kit/dynamic_options/format.rb +16 -12
  27. data/lib/servactory/version.rb +1 -1
  28. data/lib/servactory.rb +1 -0
  29. 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