servactory 2.5.0.rc1 → 2.5.0.rc3

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