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