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.
Files changed (141) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +8 -0
  3. data/.travis.yml +15 -0
  4. data/Gemfile +9 -0
  5. data/README.md +5 -1
  6. data/Rakefile +10 -0
  7. data/aggro.gemspec +8 -1
  8. data/lib/aggro.rb +191 -7
  9. data/lib/aggro/abstract_store.rb +12 -0
  10. data/lib/aggro/aggregate.rb +98 -0
  11. data/lib/aggro/aggregate_ref.rb +68 -6
  12. data/lib/aggro/attribute_dsl.rb +96 -0
  13. data/lib/aggro/binding_dsl.rb +45 -0
  14. data/lib/aggro/block_helper.rb +14 -0
  15. data/lib/aggro/channel.rb +37 -0
  16. data/lib/aggro/client.rb +12 -0
  17. data/lib/aggro/cluster_config.rb +57 -0
  18. data/lib/aggro/command.rb +16 -0
  19. data/lib/aggro/concurrent_actor.rb +26 -0
  20. data/lib/aggro/event_bus.rb +94 -0
  21. data/lib/aggro/event_dsl.rb +53 -0
  22. data/lib/aggro/event_proxy.rb +23 -0
  23. data/lib/aggro/event_serializer.rb +14 -0
  24. data/lib/aggro/file_store.rb +97 -0
  25. data/lib/aggro/file_store/reader.rb +21 -0
  26. data/lib/aggro/file_store/writer.rb +27 -0
  27. data/lib/aggro/handler/command.rb +60 -0
  28. data/lib/aggro/handler/create_aggregate.rb +42 -0
  29. data/lib/aggro/handler/get_events.rb +30 -0
  30. data/lib/aggro/handler/query.rb +60 -0
  31. data/lib/aggro/handler/start_saga.rb +56 -0
  32. data/lib/aggro/local_node.rb +28 -0
  33. data/lib/aggro/locator.rb +32 -0
  34. data/lib/aggro/message/ask.rb +16 -0
  35. data/lib/aggro/message/command.rb +36 -0
  36. data/lib/aggro/message/create_aggregate.rb +16 -0
  37. data/lib/aggro/message/endpoint.rb +16 -0
  38. data/lib/aggro/message/events.rb +24 -0
  39. data/lib/aggro/message/get_events.rb +16 -0
  40. data/lib/aggro/message/heartbeat.rb +16 -0
  41. data/lib/aggro/message/invalid_target.rb +20 -0
  42. data/lib/aggro/message/ok.rb +20 -0
  43. data/lib/aggro/message/publisher_endpoint_inquiry.rb +16 -0
  44. data/lib/aggro/message/query.rb +36 -0
  45. data/lib/aggro/message/result.rb +16 -0
  46. data/lib/aggro/message/start_saga.rb +28 -0
  47. data/lib/aggro/message/unhandled_operation.rb +20 -0
  48. data/lib/aggro/message/unknown_operation.rb +20 -0
  49. data/lib/aggro/message_parser.rb +10 -0
  50. data/lib/aggro/message_router.rb +26 -0
  51. data/lib/aggro/nanomsg_transport.rb +31 -0
  52. data/lib/aggro/nanomsg_transport/client.rb +35 -0
  53. data/lib/aggro/nanomsg_transport/connection.rb +98 -0
  54. data/lib/aggro/nanomsg_transport/publish.rb +17 -0
  55. data/lib/aggro/nanomsg_transport/publisher.rb +37 -0
  56. data/lib/aggro/nanomsg_transport/raw_reply.rb +18 -0
  57. data/lib/aggro/nanomsg_transport/raw_request.rb +18 -0
  58. data/lib/aggro/nanomsg_transport/reply.rb +17 -0
  59. data/lib/aggro/nanomsg_transport/request.rb +17 -0
  60. data/lib/aggro/nanomsg_transport/server.rb +84 -0
  61. data/lib/aggro/nanomsg_transport/socket_error.rb +20 -0
  62. data/lib/aggro/nanomsg_transport/subscribe.rb +27 -0
  63. data/lib/aggro/nanomsg_transport/subscriber.rb +82 -0
  64. data/lib/aggro/node.rb +29 -0
  65. data/lib/aggro/node_list.rb +39 -0
  66. data/lib/aggro/projection.rb +13 -0
  67. data/lib/aggro/query.rb +11 -0
  68. data/lib/aggro/saga.rb +94 -0
  69. data/lib/aggro/saga_runner.rb +87 -0
  70. data/lib/aggro/saga_runner/start_saga.rb +12 -0
  71. data/lib/aggro/saga_status.rb +29 -0
  72. data/lib/aggro/server.rb +88 -0
  73. data/lib/aggro/subscriber.rb +48 -0
  74. data/lib/aggro/subscription.rb +41 -0
  75. data/lib/aggro/transform/boolean.rb +16 -0
  76. data/lib/aggro/transform/email.rb +26 -0
  77. data/lib/aggro/transform/id.rb +34 -0
  78. data/lib/aggro/transform/integer.rb +22 -0
  79. data/lib/aggro/transform/money.rb +22 -0
  80. data/lib/aggro/transform/noop.rb +16 -0
  81. data/lib/aggro/transform/string.rb +16 -0
  82. data/lib/aggro/transform/time_interval.rb +24 -0
  83. data/lib/aggro/version.rb +1 -1
  84. data/spec/lib/aggro/abstract_store_spec.rb +15 -0
  85. data/spec/lib/aggro/aggregate_ref_spec.rb +63 -12
  86. data/spec/lib/aggro/aggregate_spec.rb +207 -0
  87. data/spec/lib/aggro/channel_spec.rb +87 -0
  88. data/spec/lib/aggro/client_spec.rb +26 -0
  89. data/spec/lib/aggro/cluster_config_spec.rb +33 -0
  90. data/spec/lib/aggro/command_spec.rb +52 -0
  91. data/spec/lib/aggro/concurrent_actor_spec.rb +44 -0
  92. data/spec/lib/aggro/event_bus_spec.rb +20 -0
  93. data/spec/lib/aggro/event_serializer_spec.rb +28 -0
  94. data/spec/lib/aggro/file_store/reader_spec.rb +32 -0
  95. data/spec/lib/aggro/file_store/writer_spec.rb +67 -0
  96. data/spec/lib/aggro/file_store_spec.rb +51 -0
  97. data/spec/lib/aggro/handler/command_spec.rb +78 -0
  98. data/spec/lib/aggro/handler/create_aggregate_spec.rb +64 -0
  99. data/spec/lib/aggro/handler/get_events_handler_spec.rb +45 -0
  100. data/spec/lib/aggro/handler/query_spec.rb +78 -0
  101. data/spec/lib/aggro/handler/start_saga_spec.rb +64 -0
  102. data/spec/lib/aggro/local_node_spec.rb +52 -0
  103. data/spec/lib/aggro/locator_spec.rb +61 -0
  104. data/spec/lib/aggro/message/ask_spec.rb +23 -0
  105. data/spec/lib/aggro/message/command_spec.rb +50 -0
  106. data/spec/lib/aggro/message/create_aggregate_spec.rb +28 -0
  107. data/spec/lib/aggro/message/endpoint_spec.rb +23 -0
  108. data/spec/lib/aggro/message/events_spec.rb +37 -0
  109. data/spec/lib/aggro/message/get_events_spec.rb +33 -0
  110. data/spec/lib/aggro/message/heartbeat_spec.rb +23 -0
  111. data/spec/lib/aggro/message/invalid_target_spec.rb +28 -0
  112. data/spec/lib/aggro/message/ok_spec.rb +27 -0
  113. data/spec/lib/aggro/message/publisher_endpoint_inquiry_spec.rb +23 -0
  114. data/spec/lib/aggro/message/query_spec.rb +50 -0
  115. data/spec/lib/aggro/message/start_saga_spec.rb +37 -0
  116. data/spec/lib/aggro/message/unhandled_operation_spec.rb +28 -0
  117. data/spec/lib/aggro/message/unknown_operation_spec.rb +28 -0
  118. data/spec/lib/aggro/message_parser_spec.rb +16 -0
  119. data/spec/lib/aggro/message_router_spec.rb +35 -0
  120. data/spec/lib/aggro/nanomsg_transport/socket_error_spec.rb +21 -0
  121. data/spec/lib/aggro/nanomsg_transport_spec.rb +37 -0
  122. data/spec/lib/aggro/node_list_spec.rb +38 -0
  123. data/spec/lib/aggro/node_spec.rb +44 -0
  124. data/spec/lib/aggro/projection_spec.rb +22 -0
  125. data/spec/lib/aggro/query_spec.rb +47 -0
  126. data/spec/lib/aggro/saga_runner_spec.rb +84 -0
  127. data/spec/lib/aggro/saga_spec.rb +126 -0
  128. data/spec/lib/aggro/saga_status_spec.rb +56 -0
  129. data/spec/lib/aggro/server_spec.rb +118 -0
  130. data/spec/lib/aggro/subscriber_spec.rb +59 -0
  131. data/spec/lib/aggro/subscription_spec.rb +50 -0
  132. data/spec/lib/aggro/transform/boolean_spec.rb +23 -0
  133. data/spec/lib/aggro/transform/email_spec.rb +13 -0
  134. data/spec/lib/aggro/transform/id_spec.rb +70 -0
  135. data/spec/lib/aggro/transform/integer_spec.rb +30 -0
  136. data/spec/lib/aggro/transform/money_spec.rb +34 -0
  137. data/spec/lib/aggro/transform/string_spec.rb +15 -0
  138. data/spec/lib/aggro/transform/time_interval_spec.rb +29 -0
  139. data/spec/lib/aggro_spec.rb +63 -19
  140. data/spec/spec_helper.rb +21 -2
  141. 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,16 @@
1
+ module Aggro
2
+ module Transform
3
+ # Private: No op transform.
4
+ module NOOP
5
+ module_function
6
+
7
+ def deserialize(value)
8
+ value
9
+ end
10
+
11
+ def serialize(value)
12
+ value
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Aggro
2
+ module Transform
3
+ # Private: Transforms string representations.
4
+ module String
5
+ module_function
6
+
7
+ def deserialize(value)
8
+ value.to_s
9
+ end
10
+
11
+ def serialize(value)
12
+ value.to_s
13
+ end
14
+ end
15
+ end
16
+ 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
@@ -1,3 +1,3 @@
1
1
  module Aggro
2
- VERSION = '0.0.1'
2
+ VERSION = '0.0.2'
3
3
  end
@@ -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
- require 'spec_helper'
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(:fake_ring) { double(node_for: '10.0.0.70') }
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(Aggro).to receive(:hash_ring).and_return fake_ring
13
+ allow(ref).to receive(:locator).and_return(fake_locator)
11
14
  end
12
15
 
13
- describe '#server' do
14
- it 'should return the server on which the aggregate lives' do
15
- expect(ref.server).to eq '10.0.0.70'
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
- it 'should memorize the lookup to reduce hashing' do
19
- expect(fake_ring).to receive(:node_for).once
20
- 5.times { ref.server }
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