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,87 @@
1
+ RSpec.describe Channel do
2
+ subject(:channel) { Channel.new id, 'Cat' }
3
+
4
+ let(:id) { SecureRandom.uuid }
5
+ let(:cat) { spy }
6
+ let(:cat_class) { double(new: 1, allows?: allowed, responds_to?: responds) }
7
+
8
+ let(:allowed) { true }
9
+ let(:responds) { true }
10
+
11
+ before do
12
+ stub_const 'Cat', cat_class
13
+ stub_const 'Aggro::ConcurrentActor', double(spawn!: cat)
14
+ end
15
+
16
+ describe '#forward_command' do
17
+ context 'command is allowed' do
18
+ it 'should send the command to the aggregate mailbox' do
19
+ channel.forward_command 'hello'
20
+
21
+ expect(cat).to have_received(:<<).with 'hello'
22
+ end
23
+ end
24
+
25
+ context 'command is not allowed' do
26
+ let(:allowed) { false }
27
+
28
+ it 'should not send the command to the aggregate mailbox' do
29
+ channel.forward_command 'hello'
30
+
31
+ expect(cat).to_not have_received(:<<).with 'hello'
32
+ end
33
+ end
34
+ end
35
+
36
+ describe '#handles_command?' do
37
+ context 'command is allowed' do
38
+ it 'should should return true' do
39
+ expect(channel.handles_command?('command')).to be_truthy
40
+ end
41
+ end
42
+
43
+ context 'command is not allowed' do
44
+ let(:allowed) { false }
45
+
46
+ it 'should return false' do
47
+ expect(channel.handles_command?('command')).to be_falsey
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '#handles?' do
53
+ context 'query is allowed' do
54
+ it 'should should return true' do
55
+ expect(channel.handles_query?('query')).to be_truthy
56
+ end
57
+ end
58
+
59
+ context 'query is not allowed' do
60
+ let(:responds) { false }
61
+
62
+ it 'should return false' do
63
+ expect(channel.handles_query?('query')).to be_falsey
64
+ end
65
+ end
66
+ end
67
+
68
+ describe '#run_query' do
69
+ context 'query is allowed' do
70
+ it 'should send the command to the aggregate mailbox' do
71
+ channel.run_query 'hello'
72
+
73
+ expect(cat).to have_received(:ask).with 'hello'
74
+ end
75
+ end
76
+
77
+ context 'query is not allowed' do
78
+ let(:responds) { false }
79
+
80
+ it 'should not send the command to the aggregate mailbox' do
81
+ channel.run_query 'hello'
82
+
83
+ expect(cat).to_not have_received(:ask).with 'hello'
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,26 @@
1
+ RSpec.describe Client do
2
+ subject(:client) { Client.new(endpoint) }
3
+
4
+ let(:endpoint) { 'tcp://10.0.0.1:4000' }
5
+
6
+ describe '#post' do
7
+ let(:transport) { spy(client: transport_client) }
8
+ let(:transport_client) { spy(post: 'hi') }
9
+
10
+ before do
11
+ allow(Aggro).to receive(:transport).and_return transport
12
+ allow(MessageParser).to receive(:parse).and_return 'parsed'
13
+ end
14
+
15
+ it 'should start a server for the node using the current transport' do
16
+ client.post 'hello'
17
+
18
+ expect(transport).to have_received(:client).with(endpoint)
19
+ expect(transport_client).to have_received(:post).with 'hello'
20
+ end
21
+
22
+ it 'should parse the response with MessageParser' do
23
+ expect(client.post('hello')).to eq 'parsed'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,33 @@
1
+ RSpec.describe ClusterConfig do
2
+ subject(:config) { ClusterConfig.new CLUSTER_CONFIG_PATH }
3
+
4
+ describe '.new' do
5
+ context 'node has an existing persisted node name' do
6
+ it 'should use the existing node name' do
7
+ @existing_name = ClusterConfig.new(CLUSTER_CONFIG_PATH).node_name
8
+
9
+ expect(config.node_name).to eq @existing_name
10
+ end
11
+ end
12
+
13
+ context 'node is being started for the first time' do
14
+ it 'should generate a new node name' do
15
+ expect(config.node_name).to be_a String
16
+ end
17
+ end
18
+ end
19
+
20
+ describe '#add_node' do
21
+ it 'should add the node to the nodes hash' do
22
+ config.add_node 'test-node', 'localhost:7000'
23
+
24
+ expect(config.nodes['test-node']).to eq 'localhost:7000'
25
+ end
26
+
27
+ it 'should persist node list' do
28
+ ClusterConfig.new(CLUSTER_CONFIG_PATH).add_node 'fluffy', 'localhost:80'
29
+
30
+ expect(config.nodes['fluffy']).to eq 'localhost:80'
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ RSpec.describe Command do
2
+ class TestCommand
3
+ include Command
4
+
5
+ generate_id :thing_id
6
+
7
+ id :owner_id
8
+ string :thing
9
+ validates :thing, presence: true
10
+
11
+ integer :times
12
+ end
13
+
14
+ subject(:command) { TestCommand.new thing: 'puppy', times: '100' }
15
+
16
+ describe '#attributes' do
17
+ it 'should return a hash of attributes' do
18
+ expect(command.attributes).to be_a Hash
19
+ expect(command.attributes[:thing]).to eq 'puppy'
20
+ end
21
+ end
22
+
23
+ describe '#serialized_attributes' do
24
+ it 'should return a hash of attributes run through the transforms' do
25
+ expect(command.serialized_attributes).to be_a Hash
26
+ expect(command.serialized_attributes[:times]).to eq 100
27
+ end
28
+ end
29
+
30
+ describe '#to_details' do
31
+ it 'should return a hash containing the command name and arguments' do
32
+ details = command.to_details
33
+
34
+ expect(details).to be_a Hash
35
+ expect(details[:name]).to eq 'TestCommand'
36
+ expect(details[:args][:thing_id]).to match Transform::ID::ID_REGEX
37
+ expect(details[:args][:thing]).to eq 'puppy'
38
+ expect(details[:args][:times]).to eq 100
39
+ expect(details[:args][:owner_id]).to eq nil
40
+ end
41
+ end
42
+
43
+ describe '#valid?' do
44
+ it 'should return true if command is valid' do
45
+ expect(command.valid?).to be_truthy
46
+ end
47
+
48
+ it 'should return false if command is not valid' do
49
+ expect(TestCommand.new.valid?).to be_falsey
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ RSpec.describe ConcurrentActor do
2
+ subject(:actor) { ConcurrentActor.new aggregate }
3
+
4
+ let(:aggregate) { spy }
5
+
6
+ class TestCommand
7
+ include Command
8
+ end
9
+
10
+ class TestQuery
11
+ include Query
12
+ end
13
+
14
+ describe '#on_message' do
15
+ context 'the message is a command' do
16
+ it 'should send the message to the aggregate' do
17
+ message = TestCommand.new
18
+
19
+ actor.on_message message
20
+
21
+ expect(aggregate).to have_received(:apply_command).with(message)
22
+ end
23
+ end
24
+
25
+ context 'the message is a query' do
26
+ it 'should send the message to the aggregate' do
27
+ message = TestQuery.new
28
+
29
+ actor.on_message message
30
+
31
+ expect(aggregate).to have_received(:run_query).with(message)
32
+ end
33
+ end
34
+
35
+ context 'the message is not a query or command' do
36
+ it 'should not send the message to the aggregate' do
37
+ actor.on_message :terminate!
38
+
39
+ expect(aggregate).to_not have_received(:apply_command)
40
+ expect(aggregate).to_not have_received(:run_query)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,20 @@
1
+ RSpec.describe EventBus do
2
+ subject(:event_bus) { EventBus.new }
3
+
4
+ let(:fake_server) { spy }
5
+
6
+ let(:topic) { SecureRandom.uuid }
7
+ let(:event) { :event! }
8
+
9
+ before do
10
+ allow(Aggro).to receive(:server).and_return fake_server
11
+ end
12
+
13
+ describe '#publish' do
14
+ it 'should publish an event via the given publisher' do
15
+ event_bus.publish topic, event
16
+
17
+ expect(fake_server).to have_received(:publish).with Message::Events
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ RSpec.describe EventSerializer do
2
+ let(:data) { { one: 9000, two: 'pizza', three: ['foo', 123] } }
3
+ let(:event) { Event.new :tested_system, Time.new(2015), data }
4
+ describe '.serialize' do
5
+ it 'should convert a hash to some serialized form' do
6
+ serialized = EventSerializer.serialize event
7
+
8
+ expect(serialized).to be_a String
9
+ end
10
+ end
11
+
12
+ describe '.deserialize' do
13
+ it 'should convert serialized data back to the original form' do
14
+ revived = EventSerializer.deserialize EventSerializer.serialize(event)
15
+
16
+ expect(revived.name).to eq :tested_system
17
+
18
+ expect(revived.occured_at).to eq Time.new(2015)
19
+
20
+ expect(revived.details).to be_a Hash
21
+ expect(revived.details.keys.length).to eq 3
22
+ expect(revived.details[:one]).to eq 9000
23
+ expect(revived.details[:two]).to eq 'pizza'
24
+ expect(revived.details[:three].first).to eq 'foo'
25
+ expect(revived.details[:three].last).to eq 123
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,32 @@
1
+ RSpec.describe FileStore::Reader do
2
+ subject(:reader) { FileStore::Reader.new data_io, index_io }
3
+
4
+ let(:data) { { one: 9000, two: 'pizza', three: ['foo', 123] } }
5
+ let(:existing_event) { Event.new :tested_pizza, Time.new(2014), data }
6
+
7
+ let(:data_content) { EventSerializer.serialize(existing_event) }
8
+ let(:index_content) { Marshal.dump data_content.bytesize }
9
+
10
+ let(:data_io) { StringIO.new(data_content, 'rb') }
11
+ let(:index_io) { StringIO.new(index_content, 'rb') }
12
+
13
+ describe '#read' do
14
+ context 'files have stored events' do
15
+ it 'should return an Enumerable of events' do
16
+ result = reader.read
17
+
18
+ expect(result).to respond_to :each
19
+ expect(result.to_a[0]).to eq existing_event
20
+ end
21
+ end
22
+
23
+ context 'files have no content' do
24
+ let(:data_content) { '' }
25
+ let(:index_content) { '' }
26
+
27
+ it 'should return an empty Enumerable' do
28
+ expect(reader.read.to_a).to eq []
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,67 @@
1
+ RSpec.describe FileStore::Writer do
2
+ subject(:writer) { FileStore::Writer.new(data_io, index_io) }
3
+
4
+ let(:data_content) { '' }
5
+ let(:index_content) { '' }
6
+
7
+ let(:data_io) { StringIO.new(data_content, 'a+b') }
8
+ let(:index_io) { StringIO.new(index_content, 'a+b') }
9
+
10
+ let(:id) { SecureRandom.uuid }
11
+
12
+ let(:data) { { one: 9000, two: 'pizza', three: ['foo', 123] } }
13
+ let(:event_one) { Event.new 'tested_system', Time.new(2015), data }
14
+ let(:event_two) { Event.new 'logged_in', Time.new(2015, 2), data }
15
+ let(:events) { [event_one, event_two] }
16
+
17
+ describe '#write' do
18
+ it 'should write event data to the data file' do
19
+ writer.write(events)
20
+
21
+ data_io.rewind
22
+ expect(data_io.read.length).to be > 0
23
+ end
24
+
25
+ it 'should write an offsets to the index file for each event' do
26
+ writer.write(events)
27
+
28
+ index_io.rewind
29
+ offsets = ObjectStream.new(index_io, type: 'marshal').to_a
30
+ expect(offsets.length).to eq 2
31
+ end
32
+
33
+ context 'when files already contain data' do
34
+ let(:existing_event) { Event.new 'tested_pizza', Time.new(2014), data }
35
+ let(:data_content) { EventSerializer.serialize(existing_event) }
36
+ let(:index_content) { Marshal.dump data_content.bytesize }
37
+
38
+ it 'should write event data to the data file' do
39
+ writer.write(events)
40
+
41
+ data_io.rewind
42
+ expect(data_io.read.length).to be > 0
43
+ end
44
+
45
+ it 'should write an offsets to the index file for each event' do
46
+ writer.write(events)
47
+
48
+ index_io.rewind
49
+ offsets = ObjectStream.new(index_io, type: 'marshal').to_a
50
+ expect(offsets.length).to eq 3
51
+ end
52
+
53
+ it 'should write corrent offsets for each event' do
54
+ writer.write(events)
55
+
56
+ index_io.rewind
57
+ offsets = ObjectStream.new(index_io, type: 'marshal').to_a
58
+
59
+ expect do
60
+ EventSerializer.deserialize data_content[0...(offsets[0])]
61
+ EventSerializer.deserialize data_content[offsets[0]...offsets[1]]
62
+ EventSerializer.deserialize data_content[offsets[1]...offsets[2]]
63
+ end.to_not raise_error
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,51 @@
1
+ RSpec.describe FileStore do
2
+ subject(:store) { FileStore.new STORE_DIR }
3
+
4
+ let(:data_io) { StringIO.new }
5
+ let(:index_io) { StringIO.new }
6
+
7
+ let(:id) { SecureRandom.uuid }
8
+ let(:type) { 'Test' }
9
+
10
+ let(:data) { { one: 9000, two: 'pizza', three: ['foo', 123] } }
11
+ let(:event_one) { Event.new :tested_system, Time.new(2015), data }
12
+ let(:event_two) { Event.new :logged_in, Time.new(2015, 2), data }
13
+ let(:events) { [event_one, event_two] }
14
+
15
+ let(:event_stream) { EventStream.new id, type, events }
16
+
17
+ describe '#create' do
18
+ before do
19
+ FileStore.new(STORE_DIR).create(SecureRandom.uuid, type)
20
+ FileStore.new(STORE_DIR).create(SecureRandom.uuid, type)
21
+ end
22
+
23
+ it 'should be able to restore written data' do
24
+ all = store.all
25
+
26
+ expect(all.length).to eq 2
27
+ expect(all.first.type).to eq type
28
+ expect(all.last.type).to eq type
29
+ end
30
+ end
31
+
32
+ describe '#read' do
33
+ before do
34
+ FileStore.new(STORE_DIR).create(id, type).write([event_stream])
35
+ end
36
+
37
+ it 'should be able to restore written data' do
38
+ restored_stream = store.read([id]).first
39
+
40
+ expect(restored_stream.id).to eq id
41
+ expect(restored_stream.type).to eq type
42
+ expect(restored_stream.events.to_a).to eq events
43
+ end
44
+ end
45
+
46
+ describe '#write' do
47
+ it 'should write data to disk' do
48
+ expect { store.write [event_stream] }.to_not raise_error
49
+ end
50
+ end
51
+ end