yes-core 1.2.0 → 1.3.0
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 +4 -4
- data/CHANGELOG.md +4 -0
- data/lib/yes/core/aggregate/dsl/class_name_convention.rb +8 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command_group/base.rb +34 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command_group/command.rb +43 -0
- data/lib/yes/core/aggregate/dsl/class_resolvers/command_group/guard_evaluator.rb +35 -0
- data/lib/yes/core/aggregate/dsl/command_group_data.rb +45 -0
- data/lib/yes/core/aggregate/dsl/command_group_definer.rb +100 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command_group/base.rb +29 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command_group/can_command_group.rb +41 -0
- data/lib/yes/core/aggregate/dsl/method_definers/command_group/command_group.rb +40 -0
- data/lib/yes/core/aggregate.rb +50 -0
- data/lib/yes/core/command_handling/command_group_executor.rb +236 -0
- data/lib/yes/core/command_handling/command_group_handler.rb +89 -0
- data/lib/yes/core/commands/command_group.rb +147 -0
- data/lib/yes/core/commands/command_group_response.rb +66 -0
- data/lib/yes/core/commands/group.rb +7 -12
- data/lib/yes/core/commands/group_payload_normalizer.rb +45 -0
- data/lib/yes/core/configuration.rb +22 -0
- data/lib/yes/core/test_support/aggregate/command_test_dsl.rb +77 -2
- data/lib/yes/core/test_support/aggregate/shared_examples.rb +45 -0
- data/lib/yes/core/utils/command_utils.rb +21 -0
- data/lib/yes/core/version.rb +1 -1
- metadata +14 -1
|
@@ -21,6 +21,28 @@ module Yes
|
|
|
21
21
|
# end
|
|
22
22
|
# end
|
|
23
23
|
module CommandTestDsl
|
|
24
|
+
# Returns the event-type aggregate prefix that the runtime publishes
|
|
25
|
+
# for a given aggregate and draft flag. Mirrors
|
|
26
|
+
# `CommandUtils#aggregate_name_with_draft_suffix` so DSL-generated
|
|
27
|
+
# `expected_event_type` values match the runtime-published event
|
|
28
|
+
# types — including the case where `draftable changes_read_model:`
|
|
29
|
+
# was set explicitly, which makes the camelized read-model name the
|
|
30
|
+
# event-type prefix instead of the generic `<Aggregate>Draft`.
|
|
31
|
+
#
|
|
32
|
+
# @param aggregate_class [Class] The aggregate class under test
|
|
33
|
+
# @param draft [Boolean] Whether the test exercises a draft command
|
|
34
|
+
# @return [String] The event-type aggregate prefix
|
|
35
|
+
def self.expected_event_prefix(aggregate_class, draft:)
|
|
36
|
+
return aggregate_class.aggregate unless draft
|
|
37
|
+
|
|
38
|
+
if aggregate_class.respond_to?(:_changes_read_model_explicit) &&
|
|
39
|
+
aggregate_class._changes_read_model_explicit
|
|
40
|
+
aggregate_class.changes_read_model_name.camelize
|
|
41
|
+
else
|
|
42
|
+
"#{aggregate_class.aggregate}Draft"
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
24
46
|
# Defines a test block for a command
|
|
25
47
|
#
|
|
26
48
|
# @param command_name [String, Symbol] the name of the command to test
|
|
@@ -41,8 +63,8 @@ module Yes
|
|
|
41
63
|
end
|
|
42
64
|
let(:command_data) { {} }
|
|
43
65
|
let(:expected_event_type) do
|
|
44
|
-
|
|
45
|
-
|
|
66
|
+
prefix = CommandTestDsl.expected_event_prefix(aggregate_class, draft:)
|
|
67
|
+
"#{aggregate_class.context}::#{prefix}#{aggregate_class.commands[command].event_name.to_s.classify}"
|
|
46
68
|
end
|
|
47
69
|
let(:expected_event_data) { command_data_with_id }
|
|
48
70
|
let(:expected_event_metadata) { nil }
|
|
@@ -99,6 +121,59 @@ module Yes
|
|
|
99
121
|
def setup(&)
|
|
100
122
|
before(&)
|
|
101
123
|
end
|
|
124
|
+
|
|
125
|
+
# Defines a test block for a command group, mirroring {.command}.
|
|
126
|
+
#
|
|
127
|
+
# @param group_name [String, Symbol] the command_group name
|
|
128
|
+
# @param options [Array<Hash>] additional options (e.g., `draft: true`)
|
|
129
|
+
# @yield block for configuring test cases (success, invalid, no_change)
|
|
130
|
+
def command_group(group_name, *options, &block)
|
|
131
|
+
describe group_name.to_s, *options do
|
|
132
|
+
let(:draft) { options.first&.dig(:draft) }
|
|
133
|
+
let(:aggregate) { described_class.new(draft:) } unless method_defined?(:aggregate)
|
|
134
|
+
|
|
135
|
+
subject { aggregate.public_send(group, command_data) }
|
|
136
|
+
|
|
137
|
+
let(:group) { group_name.to_sym }
|
|
138
|
+
let(:aggregate_class) { aggregate.class }
|
|
139
|
+
let(:command_data) { {} }
|
|
140
|
+
let(:expected_event_types) do
|
|
141
|
+
prefix = CommandTestDsl.expected_event_prefix(aggregate_class, draft:)
|
|
142
|
+
aggregate_class.command_groups[group].sub_command_names.map do |sub_name|
|
|
143
|
+
sub_event_name = aggregate_class.commands[sub_name].event_name.to_s.classify
|
|
144
|
+
"#{aggregate_class.context}::#{prefix}#{sub_event_name}"
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
let(:success_attributes) { command_data.without(:locale) } unless method_defined?(:success_attributes)
|
|
148
|
+
|
|
149
|
+
class_eval(&block) if block_given?
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Defines a successful test for a command group.
|
|
154
|
+
def success_group(description = 'when successfully executing command group', options = {}, &block)
|
|
155
|
+
context description, options do
|
|
156
|
+
instance_eval(&block) if block_given?
|
|
157
|
+
it_behaves_like 'successful command group'
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Defines an invalid-transition test for a command group.
|
|
162
|
+
def invalid_group(description, options = {}, &block)
|
|
163
|
+
context "when #{description}", options do
|
|
164
|
+
instance_eval(&block) if block_given?
|
|
165
|
+
it_behaves_like 'invalid command group transition'
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Defines a no-change test for a command group.
|
|
170
|
+
def no_change_group(description = 'when command group causes no change', options = {}, &block)
|
|
171
|
+
context description.to_s, options do
|
|
172
|
+
instance_eval(&block) if block_given?
|
|
173
|
+
before { aggregate.public_send(group, command_data) }
|
|
174
|
+
it_behaves_like 'no change command group transition'
|
|
175
|
+
end
|
|
176
|
+
end
|
|
102
177
|
end
|
|
103
178
|
end
|
|
104
179
|
end
|
|
@@ -75,3 +75,48 @@ RSpec.shared_examples 'no change transition' do
|
|
|
75
75
|
)
|
|
76
76
|
end
|
|
77
77
|
end
|
|
78
|
+
|
|
79
|
+
RSpec.shared_examples 'successful command group' do
|
|
80
|
+
it 'returns a successful CommandGroupResponse' do
|
|
81
|
+
expect(subject).to be_a(Yes::Core::Commands::CommandGroupResponse)
|
|
82
|
+
expect(subject).to be_success
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it 'publishes one event per sub-command in declaration order' do
|
|
86
|
+
expect(subject.events.map(&:type)).to eq(expected_event_types)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
it 'reflects the cumulative state on the read model' do
|
|
90
|
+
if success_attributes.any?
|
|
91
|
+
expect { subject }.to change {
|
|
92
|
+
aggregate.read_model.reload.attributes.to_h.symbolize_keys.slice(*success_attributes.keys)
|
|
93
|
+
}.to(success_attributes)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
RSpec.shared_examples 'invalid command group transition' do
|
|
99
|
+
it 'returns an InvalidTransition error and no events' do
|
|
100
|
+
aggregate_failures do
|
|
101
|
+
expect(subject).not_to be_success
|
|
102
|
+
expect(subject.error).to be_a(Yes::Core::CommandHandling::GuardEvaluator::InvalidTransition)
|
|
103
|
+
expect(subject.events).to be_empty
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
it 'does not change the aggregate state' do
|
|
108
|
+
success_attributes.each_key do |attribute|
|
|
109
|
+
expect { subject }.not_to(change { aggregate.public_send(attribute) })
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
RSpec.shared_examples 'no change command group transition' do
|
|
115
|
+
it 'returns a NoChangeTransition error and no events' do
|
|
116
|
+
aggregate_failures do
|
|
117
|
+
expect(subject).not_to be_success
|
|
118
|
+
expect(subject.error).to be_a(Yes::Core::CommandHandling::GuardEvaluator::NoChangeTransition)
|
|
119
|
+
expect(subject.events).to be_empty
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -51,6 +51,27 @@ module Yes
|
|
|
51
51
|
fetch_class(name, :guard_evaluator)
|
|
52
52
|
end
|
|
53
53
|
|
|
54
|
+
# Builds a command_group instance for a given group name and flat payload.
|
|
55
|
+
# The aggregate_id is injected automatically.
|
|
56
|
+
#
|
|
57
|
+
# @param group_name [Symbol] The command group name
|
|
58
|
+
# @param payload [Hash] The flat / partially-nested input payload
|
|
59
|
+
# @return [Yes::Core::Commands::CommandGroup] The instantiated group command
|
|
60
|
+
# @raise [RuntimeError] If the command_group class cannot be found
|
|
61
|
+
def build_group_command(group_name, payload)
|
|
62
|
+
group_class = fetch_class(group_name, :command_group)
|
|
63
|
+
group_class.new("#{aggregate.underscore}_id": aggregate_id, **payload)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Fetches the guard evaluator class for a given command group name.
|
|
67
|
+
#
|
|
68
|
+
# @param group_name [Symbol] The command group name
|
|
69
|
+
# @return [Class] The guard evaluator class
|
|
70
|
+
# @raise [RuntimeError] If the guard evaluator class cannot be found
|
|
71
|
+
def fetch_guard_evaluator_class_for_group(group_name)
|
|
72
|
+
fetch_class(group_name, :command_group_guard_evaluator)
|
|
73
|
+
end
|
|
74
|
+
|
|
54
75
|
# Fetches the state updater class for a given command name
|
|
55
76
|
#
|
|
56
77
|
# @param name [Symbol] The command name
|
data/lib/yes/core/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: yes-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.3.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nico Ritsche
|
|
@@ -195,11 +195,16 @@ files:
|
|
|
195
195
|
- lib/yes/core/aggregate/dsl/class_resolvers/command/guard_evaluator.rb
|
|
196
196
|
- lib/yes/core/aggregate/dsl/class_resolvers/command/simple_authorizer.rb
|
|
197
197
|
- lib/yes/core/aggregate/dsl/class_resolvers/command/state_updater.rb
|
|
198
|
+
- lib/yes/core/aggregate/dsl/class_resolvers/command_group/base.rb
|
|
199
|
+
- lib/yes/core/aggregate/dsl/class_resolvers/command_group/command.rb
|
|
200
|
+
- lib/yes/core/aggregate/dsl/class_resolvers/command_group/guard_evaluator.rb
|
|
198
201
|
- lib/yes/core/aggregate/dsl/class_resolvers/read_model.rb
|
|
199
202
|
- lib/yes/core/aggregate/dsl/class_resolvers/read_model_filter.rb
|
|
200
203
|
- lib/yes/core/aggregate/dsl/class_resolvers/read_model_serializer.rb
|
|
201
204
|
- lib/yes/core/aggregate/dsl/command_data.rb
|
|
202
205
|
- lib/yes/core/aggregate/dsl/command_definer.rb
|
|
206
|
+
- lib/yes/core/aggregate/dsl/command_group_data.rb
|
|
207
|
+
- lib/yes/core/aggregate/dsl/command_group_definer.rb
|
|
203
208
|
- lib/yes/core/aggregate/dsl/command_shortcut_expander.rb
|
|
204
209
|
- lib/yes/core/aggregate/dsl/constant_resolver.rb
|
|
205
210
|
- lib/yes/core/aggregate/dsl/method_definers/attribute/accessor.rb
|
|
@@ -208,6 +213,9 @@ files:
|
|
|
208
213
|
- lib/yes/core/aggregate/dsl/method_definers/command/base.rb
|
|
209
214
|
- lib/yes/core/aggregate/dsl/method_definers/command/can_command.rb
|
|
210
215
|
- lib/yes/core/aggregate/dsl/method_definers/command/command.rb
|
|
216
|
+
- lib/yes/core/aggregate/dsl/method_definers/command_group/base.rb
|
|
217
|
+
- lib/yes/core/aggregate/dsl/method_definers/command_group/can_command_group.rb
|
|
218
|
+
- lib/yes/core/aggregate/dsl/method_definers/command_group/command_group.rb
|
|
211
219
|
- lib/yes/core/aggregate/has_authorizer.rb
|
|
212
220
|
- lib/yes/core/aggregate/has_read_model.rb
|
|
213
221
|
- lib/yes/core/aggregate/read_model_rebuilder.rb
|
|
@@ -223,6 +231,8 @@ files:
|
|
|
223
231
|
- lib/yes/core/command.rb
|
|
224
232
|
- lib/yes/core/command_handling/aggregate_tracker.rb
|
|
225
233
|
- lib/yes/core/command_handling/command_executor.rb
|
|
234
|
+
- lib/yes/core/command_handling/command_group_executor.rb
|
|
235
|
+
- lib/yes/core/command_handling/command_group_handler.rb
|
|
226
236
|
- lib/yes/core/command_handling/command_handler.rb
|
|
227
237
|
- lib/yes/core/command_handling/event_publisher.rb
|
|
228
238
|
- lib/yes/core/command_handling/guard_evaluator.rb
|
|
@@ -233,7 +243,10 @@ files:
|
|
|
233
243
|
- lib/yes/core/command_handling/read_model_updater.rb
|
|
234
244
|
- lib/yes/core/command_handling/state_updater.rb
|
|
235
245
|
- lib/yes/core/commands/bus.rb
|
|
246
|
+
- lib/yes/core/commands/command_group.rb
|
|
247
|
+
- lib/yes/core/commands/command_group_response.rb
|
|
236
248
|
- lib/yes/core/commands/group.rb
|
|
249
|
+
- lib/yes/core/commands/group_payload_normalizer.rb
|
|
237
250
|
- lib/yes/core/commands/group_response.rb
|
|
238
251
|
- lib/yes/core/commands/helper.rb
|
|
239
252
|
- lib/yes/core/commands/notifier.rb
|