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,78 @@
|
|
1
|
+
RSpec.describe Handler::Command do
|
2
|
+
subject(:handler) { Handler::Command.new message, server }
|
3
|
+
|
4
|
+
let(:command) { double }
|
5
|
+
let(:commandee_id) { SecureRandom.uuid }
|
6
|
+
let(:message) { double to_command: command, commandee_id: commandee_id }
|
7
|
+
let(:server) { double }
|
8
|
+
|
9
|
+
let(:node) { double }
|
10
|
+
let(:local) { true }
|
11
|
+
let(:fake_locator) { double local?: local, primary_node: node }
|
12
|
+
let(:locator_class) { double new: fake_locator }
|
13
|
+
|
14
|
+
before do
|
15
|
+
stub_const 'Aggro::Locator', locator_class
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#call' do
|
19
|
+
context 'comandee is not handled by the server' do
|
20
|
+
let(:node_id) { SecureRandom.uuid }
|
21
|
+
let(:client) { spy post: Message::OK.new }
|
22
|
+
let(:node) { double id: node_id, client: client }
|
23
|
+
let(:local) { false }
|
24
|
+
|
25
|
+
it 'should forward the request to the correct node and return reply' do
|
26
|
+
expect(handler.call).to be_a Message::OK
|
27
|
+
expect(client).to have_received(:post)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'local system knows the command' do
|
32
|
+
context 'commandee exists on system' do
|
33
|
+
let(:channel) { spy handles_command?: handles }
|
34
|
+
|
35
|
+
before do
|
36
|
+
fake_channels = { commandee_id => channel }
|
37
|
+
stub_const 'Aggro', double(channels: fake_channels)
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'channel understands command type' do
|
41
|
+
let(:handles) { true }
|
42
|
+
|
43
|
+
it 'should return OK' do
|
44
|
+
expect(handler.call).to be_a Message::OK
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should forward command to the channel' do
|
48
|
+
handler.call
|
49
|
+
|
50
|
+
expect(channel).to have_received(:forward_command).with command
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'channel does not understand command type' do
|
55
|
+
let(:handles) { false }
|
56
|
+
|
57
|
+
it 'should return UnhandledOperation' do
|
58
|
+
expect(handler.call).to be_a Message::UnhandledOperation
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'commandee does not exist on system' do
|
64
|
+
it 'should return InvalidTarget' do
|
65
|
+
expect(handler.call).to be_a Message::InvalidTarget
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'local system does not know the command' do
|
71
|
+
it 'should return UnknownOperation' do
|
72
|
+
allow(message).to receive(:to_command).and_return(nil)
|
73
|
+
|
74
|
+
expect(handler.call).to be_a Message::UnknownOperation
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
RSpec.describe Handler::CreateAggregate do
|
2
|
+
subject(:handler) { Handler::CreateAggregate.new message, server }
|
3
|
+
|
4
|
+
let(:type) { 'Test' }
|
5
|
+
let(:id) { SecureRandom.uuid }
|
6
|
+
let(:message) { double id: id, type: type }
|
7
|
+
let(:server) { double }
|
8
|
+
|
9
|
+
let(:node) { double }
|
10
|
+
let(:local) { true }
|
11
|
+
let(:fake_locator) { double local?: local, primary_node: node }
|
12
|
+
let(:locator_class) { double new: fake_locator }
|
13
|
+
|
14
|
+
let(:fake_store) { spy(create: true) }
|
15
|
+
|
16
|
+
before do
|
17
|
+
stub_const 'Aggro::Locator', locator_class
|
18
|
+
|
19
|
+
allow(Aggro).to receive(:store).and_return fake_store
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#call' do
|
23
|
+
context 'id is handled by the server' do
|
24
|
+
it 'should return an OK message' do
|
25
|
+
expect(handler.call).to be_a Message::OK
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should create the aggregate in the current store' do
|
29
|
+
handler.call
|
30
|
+
|
31
|
+
expect(fake_store).to have_received(:create).with(id, type)
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'does not exist in the channel list' do
|
35
|
+
let(:fake_channels) { {} }
|
36
|
+
|
37
|
+
let(:fake_channel) { double }
|
38
|
+
let(:channel_class) { double new: fake_channel }
|
39
|
+
|
40
|
+
before do
|
41
|
+
allow(Aggro).to receive(:channels).and_return fake_channels
|
42
|
+
stub_const 'Aggro::Channel', channel_class
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'should add a channel for the aggregate to the channels' do
|
46
|
+
handler.call
|
47
|
+
|
48
|
+
expect(fake_channels[id]).to eq fake_channel
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'id is not handled by the server' do
|
54
|
+
let(:node_id) { SecureRandom.uuid }
|
55
|
+
let(:node) { double id: node_id }
|
56
|
+
let(:local) { false }
|
57
|
+
|
58
|
+
it 'should return Ask with another node to try' do
|
59
|
+
expect(handler.call).to be_a Message::Ask
|
60
|
+
expect(handler.call.node_id).to eq node_id
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
RSpec.describe Handler::GetEvents do
|
2
|
+
subject(:handler) { Handler::GetEvents.new message, server }
|
3
|
+
|
4
|
+
let(:type) { 'Test' }
|
5
|
+
let(:id) { SecureRandom.uuid }
|
6
|
+
let(:message) { double id: id, from_version: 0 }
|
7
|
+
let(:server) { double }
|
8
|
+
|
9
|
+
let(:node) { double }
|
10
|
+
let(:local) { true }
|
11
|
+
let(:fake_locator) { double local?: local, primary_node: node }
|
12
|
+
let(:locator_class) { double new: fake_locator }
|
13
|
+
|
14
|
+
let(:stream) { double id: id, type: type, events: [] }
|
15
|
+
let(:fake_store) { spy(read: [stream]) }
|
16
|
+
|
17
|
+
before do
|
18
|
+
stub_const 'Aggro::Locator', locator_class
|
19
|
+
|
20
|
+
allow(Aggro).to receive(:store).and_return fake_store
|
21
|
+
end
|
22
|
+
|
23
|
+
describe '#call' do
|
24
|
+
context 'id is handled by the server' do
|
25
|
+
it 'should return an Events message' do
|
26
|
+
response = handler.call
|
27
|
+
|
28
|
+
expect(response).to be_a Message::Events
|
29
|
+
expect(response.id).to eq id
|
30
|
+
expect(response.events).to eq []
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'id is not handled by the server' do
|
35
|
+
let(:node_id) { SecureRandom.uuid }
|
36
|
+
let(:node) { double id: node_id }
|
37
|
+
let(:local) { false }
|
38
|
+
|
39
|
+
it 'should return Ask with another node to try' do
|
40
|
+
expect(handler.call).to be_a Message::Ask
|
41
|
+
expect(handler.call.node_id).to eq node_id
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
RSpec.describe Handler::Query do
|
2
|
+
subject(:handler) { Handler::Query.new message, server }
|
3
|
+
|
4
|
+
let(:query) { double }
|
5
|
+
let(:queryable_id) { SecureRandom.uuid }
|
6
|
+
let(:message) { double to_query: query, queryable_id: queryable_id }
|
7
|
+
let(:server) { double }
|
8
|
+
|
9
|
+
let(:node) { double }
|
10
|
+
let(:local) { true }
|
11
|
+
let(:fake_locator) { double local?: local, primary_node: node }
|
12
|
+
let(:locator_class) { double new: fake_locator }
|
13
|
+
|
14
|
+
before do
|
15
|
+
stub_const 'Aggro::Locator', locator_class
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#call' do
|
19
|
+
context 'comandee is not handled by the server' do
|
20
|
+
let(:node_id) { SecureRandom.uuid }
|
21
|
+
let(:client) { spy post: Message::Result.new }
|
22
|
+
let(:node) { double id: node_id, client: client }
|
23
|
+
let(:local) { false }
|
24
|
+
|
25
|
+
it 'should forward the request to the correct node and return reply' do
|
26
|
+
expect(handler.call).to be_a Message::Result
|
27
|
+
expect(client).to have_received(:post)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'local system knows the query' do
|
32
|
+
context 'queryee exists on system' do
|
33
|
+
let(:channel) { spy handles_query?: handles }
|
34
|
+
|
35
|
+
before do
|
36
|
+
fake_channels = { queryable_id => channel }
|
37
|
+
stub_const 'Aggro', double(channels: fake_channels)
|
38
|
+
end
|
39
|
+
|
40
|
+
context 'channel understands query type' do
|
41
|
+
let(:handles) { true }
|
42
|
+
|
43
|
+
it 'should return Result' do
|
44
|
+
expect(handler.call).to be_a Message::Result
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should forward query to the channel' do
|
48
|
+
handler.call
|
49
|
+
|
50
|
+
expect(channel).to have_received(:run_query).with query
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context 'channel does not understand query type' do
|
55
|
+
let(:handles) { false }
|
56
|
+
|
57
|
+
it 'should return UnhandledOperation' do
|
58
|
+
expect(handler.call).to be_a Message::UnhandledOperation
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'queryee does not exist on system' do
|
64
|
+
it 'should return InvalidTarget' do
|
65
|
+
expect(handler.call).to be_a Message::InvalidTarget
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
context 'local system does not know the query' do
|
71
|
+
it 'should return UnknownOperation' do
|
72
|
+
allow(message).to receive(:to_query).and_return(nil)
|
73
|
+
|
74
|
+
expect(handler.call).to be_a Message::UnknownOperation
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
RSpec.describe Handler::StartSaga do
|
2
|
+
subject(:handler) { Handler::StartSaga.new message, server }
|
3
|
+
|
4
|
+
class TestSaga
|
5
|
+
include Saga
|
6
|
+
end
|
7
|
+
|
8
|
+
let(:id) { SecureRandom.uuid }
|
9
|
+
let(:args) { { test: 'foo' } }
|
10
|
+
let(:message) { double name: 'TestSaga', id: id, args: args }
|
11
|
+
let(:server) { double }
|
12
|
+
|
13
|
+
let(:node) { double }
|
14
|
+
let(:local) { true }
|
15
|
+
let(:fake_locator) { double local?: local, primary_node: node }
|
16
|
+
let(:locator_class) { double new: fake_locator }
|
17
|
+
|
18
|
+
before do
|
19
|
+
stub_const 'Aggro::Locator', locator_class
|
20
|
+
end
|
21
|
+
|
22
|
+
describe '#call' do
|
23
|
+
context 'saga is handled by the server' do
|
24
|
+
let(:channel) { spy }
|
25
|
+
|
26
|
+
before do
|
27
|
+
stub_const 'Aggro::Channel', double(new: channel)
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'channel understands command type' do
|
31
|
+
it 'should return OK' do
|
32
|
+
expect(handler.call).to be_a Message::OK
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should send :start to the channel' do
|
36
|
+
handler.call
|
37
|
+
|
38
|
+
expect(channel).to have_received(:forward_command).with \
|
39
|
+
SagaRunner::StartSaga
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'saga is not handled by the server' do
|
44
|
+
let(:node_id) { SecureRandom.uuid }
|
45
|
+
let(:client) { spy post: Message::OK.new }
|
46
|
+
let(:node) { double id: node_id, client: client }
|
47
|
+
let(:local) { false }
|
48
|
+
|
49
|
+
it 'should forward the request to the correct node and return reply' do
|
50
|
+
expect(handler.call).to be_a Message::OK
|
51
|
+
expect(client).to have_received(:post)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'local system does not know the command' do
|
57
|
+
it 'should return an UnknownOperation' do
|
58
|
+
allow(message).to receive(:name).and_return('NotReal')
|
59
|
+
|
60
|
+
expect(handler.call).to be_a Message::UnknownOperation
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
RSpec.describe LocalNode do
|
2
|
+
subject(:node) { LocalNode.new('flashing-sparkle') }
|
3
|
+
|
4
|
+
let(:fake_server) { spy(handle_message: 'OK') }
|
5
|
+
|
6
|
+
describe '#client' do
|
7
|
+
before do
|
8
|
+
allow(Aggro).to receive(:server).and_return(fake_server)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should return a Client-like object which locally routes messages' do
|
12
|
+
node.client.post 'MSG'
|
13
|
+
expect(fake_server).to have_received(:handle_message).with('MSG')
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#endpoint' do
|
18
|
+
before do
|
19
|
+
allow(Aggro).to receive(:port).and_return 6000
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should have a local TCP endpoint with the correct port' do
|
23
|
+
expect(node.endpoint).to eq 'tcp://127.0.0.1:6000'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#publisher_endpoint' do
|
28
|
+
before do
|
29
|
+
allow(Aggro).to receive(:publisher_port).and_return 7000
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should have a local TCP endpoint with the correct port' do
|
33
|
+
expect(node.publisher_endpoint).to eq 'tcp://127.0.0.1:7000'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe '#to_s' do
|
38
|
+
let(:moved_node) { LocalNode.new('flashing-sparkle') }
|
39
|
+
let(:other_node) { LocalNode.new('dancing-sparkle') }
|
40
|
+
|
41
|
+
let(:ring) { ConsistentHashing::Ring.new }
|
42
|
+
let(:hasher) { ring.method(:hash_key) }
|
43
|
+
|
44
|
+
it 'should be consistently hashed the same if id matches' do
|
45
|
+
expect(hasher.call(node)).to eq hasher.call(moved_node)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should be consistently hashed differently if id differs' do
|
49
|
+
expect(hasher.call(node)).to_not eq hasher.call(other_node)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
RSpec.describe Locator do
|
2
|
+
subject(:locator) { Locator.new id }
|
3
|
+
|
4
|
+
let(:id) { SecureRandom.uuid }
|
5
|
+
let(:node) { Node.new('flashing-sparkle', '10.0.0.70') }
|
6
|
+
let(:other_node) { Node.new('winking-tiger', '10.0.0.90') }
|
7
|
+
let(:nodes) { [node, other_node] }
|
8
|
+
let(:node_list) { spy(nodes_for: nodes, state: 'initial') }
|
9
|
+
|
10
|
+
before do
|
11
|
+
allow(Aggro).to receive(:node_list).and_return node_list
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#local?' do
|
15
|
+
context 'primary node is a LocalNode' do
|
16
|
+
let(:node) { LocalNode.new('flashing-sparkle') }
|
17
|
+
|
18
|
+
it 'should return true' do
|
19
|
+
expect(locator.local?).to be_truthy
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'primary node is not a LocalNode' do
|
24
|
+
it 'should return true' do
|
25
|
+
expect(locator.local?).to be_falsey
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#nodes' do
|
31
|
+
it 'should return the nodes on which the aggregate should persist' do
|
32
|
+
expect(locator.nodes.first.endpoint).to eq '10.0.0.70'
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should memorize the lookup to reduce hashing' do
|
36
|
+
5.times { locator.nodes }
|
37
|
+
|
38
|
+
expect(node_list).to have_received(:nodes_for).once
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should forget memorized servers if ring state changes' do
|
42
|
+
locator.nodes
|
43
|
+
allow(node_list).to receive(:state).and_return('changed')
|
44
|
+
locator.nodes
|
45
|
+
|
46
|
+
expect(node_list).to have_received(:nodes_for).twice
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#primary_node' do
|
51
|
+
it 'should return the first associated node' do
|
52
|
+
expect(locator.primary_node).to eq node
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
describe '#secondary_nodes' do
|
57
|
+
it 'should return the rest of the associated nodes' do
|
58
|
+
expect(locator.secondary_nodes).to eq [other_node]
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|