aggro 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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