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,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
|