aggro 0.0.1 → 0.0.2
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/.rubocop.yml +8 -0
- data/.travis.yml +15 -0
- data/Gemfile +9 -0
- data/README.md +5 -1
- data/Rakefile +10 -0
- data/aggro.gemspec +8 -1
- data/lib/aggro.rb +191 -7
- data/lib/aggro/abstract_store.rb +12 -0
- data/lib/aggro/aggregate.rb +98 -0
- data/lib/aggro/aggregate_ref.rb +68 -6
- data/lib/aggro/attribute_dsl.rb +96 -0
- data/lib/aggro/binding_dsl.rb +45 -0
- data/lib/aggro/block_helper.rb +14 -0
- data/lib/aggro/channel.rb +37 -0
- data/lib/aggro/client.rb +12 -0
- data/lib/aggro/cluster_config.rb +57 -0
- data/lib/aggro/command.rb +16 -0
- data/lib/aggro/concurrent_actor.rb +26 -0
- data/lib/aggro/event_bus.rb +94 -0
- data/lib/aggro/event_dsl.rb +53 -0
- data/lib/aggro/event_proxy.rb +23 -0
- data/lib/aggro/event_serializer.rb +14 -0
- data/lib/aggro/file_store.rb +97 -0
- data/lib/aggro/file_store/reader.rb +21 -0
- data/lib/aggro/file_store/writer.rb +27 -0
- data/lib/aggro/handler/command.rb +60 -0
- data/lib/aggro/handler/create_aggregate.rb +42 -0
- data/lib/aggro/handler/get_events.rb +30 -0
- data/lib/aggro/handler/query.rb +60 -0
- data/lib/aggro/handler/start_saga.rb +56 -0
- data/lib/aggro/local_node.rb +28 -0
- data/lib/aggro/locator.rb +32 -0
- data/lib/aggro/message/ask.rb +16 -0
- data/lib/aggro/message/command.rb +36 -0
- data/lib/aggro/message/create_aggregate.rb +16 -0
- data/lib/aggro/message/endpoint.rb +16 -0
- data/lib/aggro/message/events.rb +24 -0
- data/lib/aggro/message/get_events.rb +16 -0
- data/lib/aggro/message/heartbeat.rb +16 -0
- data/lib/aggro/message/invalid_target.rb +20 -0
- data/lib/aggro/message/ok.rb +20 -0
- data/lib/aggro/message/publisher_endpoint_inquiry.rb +16 -0
- data/lib/aggro/message/query.rb +36 -0
- data/lib/aggro/message/result.rb +16 -0
- data/lib/aggro/message/start_saga.rb +28 -0
- data/lib/aggro/message/unhandled_operation.rb +20 -0
- data/lib/aggro/message/unknown_operation.rb +20 -0
- data/lib/aggro/message_parser.rb +10 -0
- data/lib/aggro/message_router.rb +26 -0
- data/lib/aggro/nanomsg_transport.rb +31 -0
- data/lib/aggro/nanomsg_transport/client.rb +35 -0
- data/lib/aggro/nanomsg_transport/connection.rb +98 -0
- data/lib/aggro/nanomsg_transport/publish.rb +17 -0
- data/lib/aggro/nanomsg_transport/publisher.rb +37 -0
- data/lib/aggro/nanomsg_transport/raw_reply.rb +18 -0
- data/lib/aggro/nanomsg_transport/raw_request.rb +18 -0
- data/lib/aggro/nanomsg_transport/reply.rb +17 -0
- data/lib/aggro/nanomsg_transport/request.rb +17 -0
- data/lib/aggro/nanomsg_transport/server.rb +84 -0
- data/lib/aggro/nanomsg_transport/socket_error.rb +20 -0
- data/lib/aggro/nanomsg_transport/subscribe.rb +27 -0
- data/lib/aggro/nanomsg_transport/subscriber.rb +82 -0
- data/lib/aggro/node.rb +29 -0
- data/lib/aggro/node_list.rb +39 -0
- data/lib/aggro/projection.rb +13 -0
- data/lib/aggro/query.rb +11 -0
- data/lib/aggro/saga.rb +94 -0
- data/lib/aggro/saga_runner.rb +87 -0
- data/lib/aggro/saga_runner/start_saga.rb +12 -0
- data/lib/aggro/saga_status.rb +29 -0
- data/lib/aggro/server.rb +88 -0
- data/lib/aggro/subscriber.rb +48 -0
- data/lib/aggro/subscription.rb +41 -0
- data/lib/aggro/transform/boolean.rb +16 -0
- data/lib/aggro/transform/email.rb +26 -0
- data/lib/aggro/transform/id.rb +34 -0
- data/lib/aggro/transform/integer.rb +22 -0
- data/lib/aggro/transform/money.rb +22 -0
- data/lib/aggro/transform/noop.rb +16 -0
- data/lib/aggro/transform/string.rb +16 -0
- data/lib/aggro/transform/time_interval.rb +24 -0
- data/lib/aggro/version.rb +1 -1
- data/spec/lib/aggro/abstract_store_spec.rb +15 -0
- data/spec/lib/aggro/aggregate_ref_spec.rb +63 -12
- data/spec/lib/aggro/aggregate_spec.rb +207 -0
- data/spec/lib/aggro/channel_spec.rb +87 -0
- data/spec/lib/aggro/client_spec.rb +26 -0
- data/spec/lib/aggro/cluster_config_spec.rb +33 -0
- data/spec/lib/aggro/command_spec.rb +52 -0
- data/spec/lib/aggro/concurrent_actor_spec.rb +44 -0
- data/spec/lib/aggro/event_bus_spec.rb +20 -0
- data/spec/lib/aggro/event_serializer_spec.rb +28 -0
- data/spec/lib/aggro/file_store/reader_spec.rb +32 -0
- data/spec/lib/aggro/file_store/writer_spec.rb +67 -0
- data/spec/lib/aggro/file_store_spec.rb +51 -0
- data/spec/lib/aggro/handler/command_spec.rb +78 -0
- data/spec/lib/aggro/handler/create_aggregate_spec.rb +64 -0
- data/spec/lib/aggro/handler/get_events_handler_spec.rb +45 -0
- data/spec/lib/aggro/handler/query_spec.rb +78 -0
- data/spec/lib/aggro/handler/start_saga_spec.rb +64 -0
- data/spec/lib/aggro/local_node_spec.rb +52 -0
- data/spec/lib/aggro/locator_spec.rb +61 -0
- data/spec/lib/aggro/message/ask_spec.rb +23 -0
- data/spec/lib/aggro/message/command_spec.rb +50 -0
- data/spec/lib/aggro/message/create_aggregate_spec.rb +28 -0
- data/spec/lib/aggro/message/endpoint_spec.rb +23 -0
- data/spec/lib/aggro/message/events_spec.rb +37 -0
- data/spec/lib/aggro/message/get_events_spec.rb +33 -0
- data/spec/lib/aggro/message/heartbeat_spec.rb +23 -0
- data/spec/lib/aggro/message/invalid_target_spec.rb +28 -0
- data/spec/lib/aggro/message/ok_spec.rb +27 -0
- data/spec/lib/aggro/message/publisher_endpoint_inquiry_spec.rb +23 -0
- data/spec/lib/aggro/message/query_spec.rb +50 -0
- data/spec/lib/aggro/message/start_saga_spec.rb +37 -0
- data/spec/lib/aggro/message/unhandled_operation_spec.rb +28 -0
- data/spec/lib/aggro/message/unknown_operation_spec.rb +28 -0
- data/spec/lib/aggro/message_parser_spec.rb +16 -0
- data/spec/lib/aggro/message_router_spec.rb +35 -0
- data/spec/lib/aggro/nanomsg_transport/socket_error_spec.rb +21 -0
- data/spec/lib/aggro/nanomsg_transport_spec.rb +37 -0
- data/spec/lib/aggro/node_list_spec.rb +38 -0
- data/spec/lib/aggro/node_spec.rb +44 -0
- data/spec/lib/aggro/projection_spec.rb +22 -0
- data/spec/lib/aggro/query_spec.rb +47 -0
- data/spec/lib/aggro/saga_runner_spec.rb +84 -0
- data/spec/lib/aggro/saga_spec.rb +126 -0
- data/spec/lib/aggro/saga_status_spec.rb +56 -0
- data/spec/lib/aggro/server_spec.rb +118 -0
- data/spec/lib/aggro/subscriber_spec.rb +59 -0
- data/spec/lib/aggro/subscription_spec.rb +50 -0
- data/spec/lib/aggro/transform/boolean_spec.rb +23 -0
- data/spec/lib/aggro/transform/email_spec.rb +13 -0
- data/spec/lib/aggro/transform/id_spec.rb +70 -0
- data/spec/lib/aggro/transform/integer_spec.rb +30 -0
- data/spec/lib/aggro/transform/money_spec.rb +34 -0
- data/spec/lib/aggro/transform/string_spec.rb +15 -0
- data/spec/lib/aggro/transform/time_interval_spec.rb +29 -0
- data/spec/lib/aggro_spec.rb +63 -19
- data/spec/spec_helper.rb +21 -2
- metadata +283 -3
@@ -0,0 +1,41 @@
|
|
1
|
+
module Aggro
|
2
|
+
# Private: Handles invoking events on a subscriber object.
|
3
|
+
class Subscription
|
4
|
+
def initialize(topic, subscriber, namespace, filters, at_version)
|
5
|
+
@topic = topic
|
6
|
+
@subscriber = subscriber
|
7
|
+
@namespace = namespace
|
8
|
+
@filters = filters
|
9
|
+
@at_version = at_version
|
10
|
+
@canceled = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def cancel
|
14
|
+
Aggro.event_bus.unsubscribe @topic, self unless @canceled
|
15
|
+
@canceled = true
|
16
|
+
end
|
17
|
+
|
18
|
+
def handle_event(event)
|
19
|
+
return if @canceled
|
20
|
+
|
21
|
+
invoke(event) if handles_event?(event) && matches_filter?(event)
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def handles_event?(event)
|
27
|
+
@subscriber.handles_event? event.name, @namespace
|
28
|
+
end
|
29
|
+
|
30
|
+
def invoke(event)
|
31
|
+
Invokr.invoke method: "#{@namespace}_#{event.name}",
|
32
|
+
using: event.details, on: @subscriber
|
33
|
+
end
|
34
|
+
|
35
|
+
def matches_filter?(event)
|
36
|
+
@filters.all? do |filter_key, filter_value|
|
37
|
+
event.details[filter_key] == filter_value
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Aggro
|
2
|
+
module Transform
|
3
|
+
# Private: Transforms boolean representations.
|
4
|
+
module Boolean
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def deserialize(value)
|
8
|
+
value if value == false || value == true
|
9
|
+
end
|
10
|
+
|
11
|
+
def serialize(value)
|
12
|
+
value if value == false || value == true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Aggro
|
2
|
+
module Transform
|
3
|
+
# Private: Transforms integer representations.
|
4
|
+
module Email
|
5
|
+
EMAIL_REGEX = %r{
|
6
|
+
\A([-a-z0-9!\#$%&'*+/=?^_`{|}~]+\.)*
|
7
|
+
[-a-z0-9!\#$%&'*+/=?^_`{|}~]+
|
8
|
+
@
|
9
|
+
((?:[-a-z0-9]+\.)+
|
10
|
+
[a-z]{2,})\Z
|
11
|
+
}xi
|
12
|
+
|
13
|
+
module_function
|
14
|
+
|
15
|
+
def deserialize(value)
|
16
|
+
value = value.to_s
|
17
|
+
|
18
|
+
value if value.match(EMAIL_REGEX)
|
19
|
+
end
|
20
|
+
|
21
|
+
def serialize(value)
|
22
|
+
deserialize value
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Aggro
|
2
|
+
module Transform
|
3
|
+
# Private: Transforms integer representations.
|
4
|
+
class ID
|
5
|
+
ID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
|
6
|
+
|
7
|
+
def initialize(generate: false)
|
8
|
+
@generate = generate
|
9
|
+
end
|
10
|
+
|
11
|
+
def deserialize(value)
|
12
|
+
value = value.to_s
|
13
|
+
|
14
|
+
return value if value.match(ID_REGEX)
|
15
|
+
|
16
|
+
generate_id if should_generate_id?
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize(value)
|
20
|
+
deserialize value
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def generate_id
|
26
|
+
SecureRandom.uuid
|
27
|
+
end
|
28
|
+
|
29
|
+
def should_generate_id?
|
30
|
+
@generate == true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Aggro
|
2
|
+
module Transform
|
3
|
+
# Private: Transforms integer representations.
|
4
|
+
module Integer
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def deserialize(value)
|
8
|
+
if value.is_a?(::String)
|
9
|
+
string = value.gsub(/[^\d\.]/, '')
|
10
|
+
|
11
|
+
string == '' ? nil : string.to_i
|
12
|
+
else
|
13
|
+
value.to_i
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def serialize(value)
|
18
|
+
value.to_i
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Aggro
|
2
|
+
module Transform
|
3
|
+
# Private: Transforms money representations.
|
4
|
+
module Money
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def deserialize(value)
|
8
|
+
if value.is_a? ::String
|
9
|
+
Monetize.parse(value)
|
10
|
+
elsif value.is_a? ::Integer
|
11
|
+
Monetize.parse(value.to_s)
|
12
|
+
elsif value.is_a? ::Money
|
13
|
+
value
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def serialize(value)
|
18
|
+
value.format with_currency: true if value.is_a? ::Money
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Aggro
|
2
|
+
module Transform
|
3
|
+
# Private: Transforms money representations.
|
4
|
+
module TimeInterval
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def deserialize(value)
|
8
|
+
if value.is_a? ::String
|
9
|
+
::TimeInterval.parse(value)
|
10
|
+
elsif interval? value
|
11
|
+
value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def interval?(value)
|
16
|
+
value.class.parents.include? ::TimeInterval
|
17
|
+
end
|
18
|
+
|
19
|
+
def serialize(value)
|
20
|
+
value.iso8601 if interval? value
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/aggro/version.rb
CHANGED
@@ -0,0 +1,15 @@
|
|
1
|
+
RSpec.describe AbstractStore do
|
2
|
+
subject(:store) { AbstractStore.new }
|
3
|
+
|
4
|
+
describe '#read' do
|
5
|
+
it 'should raise not implemented error' do
|
6
|
+
expect { store.read(double) }.to raise_error NotImplementedError
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
describe '#write' do
|
11
|
+
it 'should raise not implemented error' do
|
12
|
+
expect { store.write(double) }.to raise_error NotImplementedError
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -1,23 +1,74 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
describe AggregateRef do
|
4
|
-
subject(:ref) { AggregateRef.new(id) }
|
1
|
+
RSpec.describe AggregateRef do
|
2
|
+
subject(:ref) { AggregateRef.new(id, type) }
|
5
3
|
|
6
4
|
let(:id) { SecureRandom.uuid }
|
7
|
-
let(:
|
5
|
+
let(:type) { 'type' }
|
6
|
+
|
7
|
+
let(:response) { Message::OK.new }
|
8
|
+
let(:client) { spy post: response }
|
9
|
+
let(:node) { double(client: client) }
|
10
|
+
let(:fake_locator) { double(primary_node: node) }
|
8
11
|
|
9
12
|
before do
|
10
|
-
allow(
|
13
|
+
allow(ref).to receive(:locator).and_return(fake_locator)
|
11
14
|
end
|
12
15
|
|
13
|
-
describe '#
|
14
|
-
it 'should
|
15
|
-
|
16
|
+
describe '#command' do
|
17
|
+
it 'should send the command to the aggregate via the client' do
|
18
|
+
command = double(to_details: { name: 'TestCommand' })
|
19
|
+
|
20
|
+
ref.command command
|
21
|
+
|
22
|
+
expect(client).to have_received(:post).with kind_of Message::Command
|
16
23
|
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#create' do
|
27
|
+
context 'the node also thinks it is the relavent node' do
|
28
|
+
let(:response) { Message::OK.new }
|
29
|
+
|
30
|
+
it 'should send a CreateAggregate message to the relavent node' do
|
31
|
+
ref.create
|
32
|
+
|
33
|
+
expect(client).to have_received(:post).with(Message::CreateAggregate)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'the node does not think it is the right node for id' do
|
38
|
+
let(:response) { Message::Ask.new(SecureRandom.uuid) }
|
39
|
+
|
40
|
+
it 'should raise an error' do
|
41
|
+
expect { ref.create }.to raise_error
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#query' do
|
47
|
+
let(:query) { double to_details: { name: 'TestQuery' } }
|
48
|
+
|
49
|
+
context 'the response is not an error' do
|
50
|
+
let(:response) { Message::Result.new 'hello' }
|
51
|
+
|
52
|
+
it 'should send the command to the aggregate via the client' do
|
53
|
+
ref.query query
|
54
|
+
|
55
|
+
expect(client).to have_received(:post).with kind_of Message::Query
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'should return the result value' do
|
59
|
+
result = ref.query(query)
|
60
|
+
|
61
|
+
expect(result).to eq 'hello'
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context 'the response is an error' do
|
66
|
+
let(:error) { RuntimeError.new 'Something went wrong' }
|
67
|
+
let(:response) { Message::Result.new QueryError.new error }
|
17
68
|
|
18
|
-
|
19
|
-
|
20
|
-
|
69
|
+
it 'should return the result value' do
|
70
|
+
expect { ref.query(query) }.to raise_error 'Something went wrong'
|
71
|
+
end
|
21
72
|
end
|
22
73
|
end
|
23
74
|
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
RSpec.describe Aggregate do
|
4
|
+
class GiveSomething
|
5
|
+
include Aggro::Command
|
6
|
+
|
7
|
+
string :thing
|
8
|
+
money :value
|
9
|
+
end
|
10
|
+
|
11
|
+
class CheckThings
|
12
|
+
include Aggro::Query
|
13
|
+
end
|
14
|
+
|
15
|
+
class CatSerializer
|
16
|
+
include Aggro::Projection
|
17
|
+
|
18
|
+
def serialize
|
19
|
+
JSON.dump data
|
20
|
+
end
|
21
|
+
|
22
|
+
events do
|
23
|
+
def gave_thing(thing)
|
24
|
+
things << thing
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def data
|
31
|
+
@data ||= { things: things }
|
32
|
+
end
|
33
|
+
|
34
|
+
def things
|
35
|
+
@things ||= []
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Cat
|
40
|
+
include Aggregate
|
41
|
+
|
42
|
+
attr_reader :executed_self
|
43
|
+
attr_reader :executed_command
|
44
|
+
attr_reader :executed_money
|
45
|
+
|
46
|
+
def things_i_have
|
47
|
+
@things ||= []
|
48
|
+
end
|
49
|
+
|
50
|
+
projection :json, via: CatSerializer
|
51
|
+
|
52
|
+
allows GiveSomething do |command|
|
53
|
+
@executed_self = self
|
54
|
+
@executed_command = command
|
55
|
+
|
56
|
+
did.gave_thing
|
57
|
+
end
|
58
|
+
|
59
|
+
responds_to CheckThings do
|
60
|
+
things_i_have
|
61
|
+
end
|
62
|
+
|
63
|
+
events do
|
64
|
+
def gave_thing(thing, value)
|
65
|
+
things_i_have << thing
|
66
|
+
@executed_money = value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
subject(:aggregate) { Cat.new(id) }
|
72
|
+
|
73
|
+
let(:id) { SecureRandom.uuid }
|
74
|
+
let(:command) { GiveSomething.new thing: 'milk', value: '$100 NZD' }
|
75
|
+
let(:query) { CheckThings.new }
|
76
|
+
|
77
|
+
let(:existing_event) do
|
78
|
+
Event.new(:gave_thing, Time.now, thing: 'cake', value: Money.new(100))
|
79
|
+
end
|
80
|
+
let(:response) { Message::Events.new(id, [existing_event]) }
|
81
|
+
let(:client) { double(post: response) }
|
82
|
+
let(:publisher_endpoint) { 'tcp://127.0.0.1:6000' }
|
83
|
+
let(:node) { double(client: client, publisher_endpoint: publisher_endpoint) }
|
84
|
+
let(:fake_locator) { double primary_node: node }
|
85
|
+
let(:locator_class) { double new: fake_locator }
|
86
|
+
|
87
|
+
let(:id) { SecureRandom.uuid }
|
88
|
+
|
89
|
+
let(:fake_server) { spy publish: true }
|
90
|
+
|
91
|
+
before do
|
92
|
+
stub_const 'Aggro::Locator', locator_class
|
93
|
+
allow(Aggro).to receive(:server).and_return fake_server
|
94
|
+
end
|
95
|
+
|
96
|
+
describe '.allows' do
|
97
|
+
it 'should register a command handler' do
|
98
|
+
Cat.allows(Fixnum) { true }
|
99
|
+
|
100
|
+
expect(Cat.allows?(1)).to be_truthy
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
describe '.allows?' do
|
105
|
+
context 'input command has a registeded handler' do
|
106
|
+
it 'should return true' do
|
107
|
+
expect(Cat.allows?(GiveSomething.new)).to be_truthy
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
context 'input command does not have a registed handler' do
|
112
|
+
it 'should return false' do
|
113
|
+
expect(Cat.allows?('')).to be_falsey
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
describe '.create' do
|
119
|
+
let(:id) { SecureRandom.uuid }
|
120
|
+
let(:aggregate_ref) { double create: true }
|
121
|
+
let(:aggregate_ref_class) { spy(new: aggregate_ref) }
|
122
|
+
let(:response) { Message::OK.new }
|
123
|
+
|
124
|
+
before do
|
125
|
+
stub_const 'Aggro::Locator', locator_class
|
126
|
+
stub_const 'Aggro::AggregateRef', aggregate_ref_class
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'should send a CreateAggregate message to the relavent node' do
|
130
|
+
Cat.create id
|
131
|
+
|
132
|
+
expect(aggregate_ref).to have_received(:create)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
describe '.find' do
|
137
|
+
let(:aggregate_ref) { double }
|
138
|
+
let(:aggregate_ref_class) { spy(new: aggregate_ref) }
|
139
|
+
|
140
|
+
let(:id) { SecureRandom.uuid }
|
141
|
+
|
142
|
+
before do
|
143
|
+
stub_const 'Aggro::AggregateRef', aggregate_ref_class
|
144
|
+
end
|
145
|
+
|
146
|
+
it 'should return an AggregateRef for the aggregate' do
|
147
|
+
expect(Cat.find(id)).to eq aggregate_ref
|
148
|
+
expect(aggregate_ref_class).to have_received(:new).with id, 'Cat'
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe '.new' do
|
153
|
+
let(:event_bus) { spy }
|
154
|
+
|
155
|
+
before do
|
156
|
+
allow(Aggro).to receive(:event_bus).and_return(event_bus)
|
157
|
+
end
|
158
|
+
|
159
|
+
it 'should subscribe itself to events for the given ID' do
|
160
|
+
aggregate
|
161
|
+
|
162
|
+
expect(event_bus).to have_received(:subscribe).with id, aggregate
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
describe '#apply_command' do
|
167
|
+
it 'should execute the handler with the aggregate as self' do
|
168
|
+
aggregate.send :apply_command, command
|
169
|
+
|
170
|
+
expect(aggregate.executed_self).to eq aggregate
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'should preserve types' do
|
174
|
+
aggregate.send :apply_command, command
|
175
|
+
|
176
|
+
expect(aggregate.executed_money).to be_a Money
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'should execute the handler with the command as an argument' do
|
180
|
+
aggregate.send :apply_command, command
|
181
|
+
|
182
|
+
expect(aggregate.executed_command).to eq command
|
183
|
+
end
|
184
|
+
|
185
|
+
it 'should have the existing event applied' do
|
186
|
+
expect(aggregate.things_i_have).to include 'cake'
|
187
|
+
end
|
188
|
+
|
189
|
+
it 'should apply events via the #did proxy' do
|
190
|
+
aggregate.send :apply_command, command
|
191
|
+
|
192
|
+
expect(aggregate.things_i_have).to include 'milk'
|
193
|
+
end
|
194
|
+
|
195
|
+
it 'should apply event to the projections' do
|
196
|
+
aggregate.send :apply_command, command
|
197
|
+
|
198
|
+
expect(aggregate.json.serialize).to eq '{"things":["cake","milk"]}'
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe '#run_query' do
|
203
|
+
it 'should return the result of the query method' do
|
204
|
+
expect(aggregate.send(:run_query, query)).to eq ['cake']
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|