distribot 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +3 -0
  5. data/.travis.yml +10 -0
  6. data/Dockerfile +9 -0
  7. data/Gemfile +6 -0
  8. data/Gemfile.lock +153 -0
  9. data/LICENSE +201 -0
  10. data/README.md +107 -0
  11. data/Rakefile +16 -0
  12. data/bin/distribot.flow-created +6 -0
  13. data/bin/distribot.flow-finished +6 -0
  14. data/bin/distribot.handler-finished +6 -0
  15. data/bin/distribot.phase-finished +6 -0
  16. data/bin/distribot.phase-started +6 -0
  17. data/bin/distribot.task-finished +6 -0
  18. data/distribot.gemspec +35 -0
  19. data/docker-compose.yml +29 -0
  20. data/examples/controller +168 -0
  21. data/examples/distribot.eye +49 -0
  22. data/examples/status +38 -0
  23. data/examples/worker +135 -0
  24. data/lib/distribot/connector.rb +162 -0
  25. data/lib/distribot/flow.rb +200 -0
  26. data/lib/distribot/flow_created_handler.rb +12 -0
  27. data/lib/distribot/flow_finished_handler.rb +12 -0
  28. data/lib/distribot/handler.rb +40 -0
  29. data/lib/distribot/handler_finished_handler.rb +29 -0
  30. data/lib/distribot/phase.rb +46 -0
  31. data/lib/distribot/phase_finished_handler.rb +19 -0
  32. data/lib/distribot/phase_handler.rb +15 -0
  33. data/lib/distribot/phase_started_handler.rb +69 -0
  34. data/lib/distribot/task_finished_handler.rb +37 -0
  35. data/lib/distribot/worker.rb +148 -0
  36. data/lib/distribot.rb +108 -0
  37. data/provision/nodes.sh +80 -0
  38. data/provision/templates/fluentd.conf +27 -0
  39. data/spec/distribot/bunny_connector_spec.rb +196 -0
  40. data/spec/distribot/connection_sharer_spec.rb +34 -0
  41. data/spec/distribot/connector_spec.rb +63 -0
  42. data/spec/distribot/flow_created_handler_spec.rb +32 -0
  43. data/spec/distribot/flow_finished_handler_spec.rb +32 -0
  44. data/spec/distribot/flow_spec.rb +661 -0
  45. data/spec/distribot/handler_finished_handler_spec.rb +112 -0
  46. data/spec/distribot/handler_spec.rb +32 -0
  47. data/spec/distribot/module_spec.rb +163 -0
  48. data/spec/distribot/multi_subscription_spec.rb +37 -0
  49. data/spec/distribot/phase_finished_handler_spec.rb +61 -0
  50. data/spec/distribot/phase_started_handler_spec.rb +150 -0
  51. data/spec/distribot/subscription_spec.rb +40 -0
  52. data/spec/distribot/task_finished_handler_spec.rb +71 -0
  53. data/spec/distribot/worker_spec.rb +281 -0
  54. data/spec/fixtures/simple_flow.json +49 -0
  55. data/spec/spec_helper.rb +74 -0
  56. metadata +371 -0
@@ -0,0 +1,112 @@
1
+ require 'spec_helper'
2
+
3
+ describe Distribot::HandlerFinishedHandler do
4
+ before do
5
+ Distribot.stub(:subscribe)
6
+ end
7
+ describe 'definition' do
8
+ it 'subscribes to the correct queue' do
9
+ expect(Distribot::Handler.queue_for(described_class)).to eq 'distribot.flow.handler.finished'
10
+ end
11
+ it 'declares a valid handler' do
12
+ expect(Distribot::Handler.handler_for(described_class)).to eq :callback
13
+ end
14
+ it 'has a method matching the handler name' do
15
+ expect(described_class.new).to respond_to :callback
16
+ end
17
+ end
18
+
19
+ describe '#callback(message)' do
20
+ before do
21
+ @message = {
22
+ task_queue: 'task-queue',
23
+ flow_id: 'flow-id',
24
+ phase: 'phase1'
25
+ }
26
+ @handler = described_class.new
27
+ end
28
+ context 'when all the handler\'s tasks' do
29
+ context 'are complete' do
30
+ before do
31
+ expect(Distribot::Flow).to receive(:find).with(@message[:flow_id]) do
32
+ Distribot::Flow.new(id: @message[:flow_id], phases: [{name:'phase1', handlers: []}])
33
+ end
34
+ expect(@handler).to receive(:all_phase_handler_tasks_are_complete?){ true }
35
+ end
36
+ it 'announces that this phase has completed and that everyone should stop consuming its task.finished queue' do
37
+ # Describe what 'nothing' looks like:
38
+ expect(Distribot).to receive(:publish!).with('distribot.flow.phase.finished', {
39
+ flow_id: @message[:flow_id],
40
+ phase: 'phase1'
41
+ })
42
+
43
+ # Finally:
44
+ @handler.callback(@message)
45
+ end
46
+ end
47
+ context 'are not yet complete' do
48
+ before do
49
+ expect(Distribot::Flow).to receive(:find).with(@message[:flow_id]) do
50
+ Distribot::Flow.new(id: @message[:flow_id], phases: [{name:'phase1', handlers: []}])
51
+ end
52
+ expect(@handler).to receive(:all_phase_handler_tasks_are_complete?){ false }
53
+ end
54
+ it 'does nothing' do
55
+ # Describe what 'nothing' looks like:
56
+ expect(Distribot).not_to receive(:publish!)
57
+ expect(Distribot).not_to receive(:broadcast!)
58
+
59
+ # Finally:
60
+ @handler.callback(@message)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ describe '#all_phase_handler_tasks_are_complete?(flow, phase)' do
67
+ context 'when all tasks are complete' do
68
+ before do
69
+ @flow = Distribot::Flow.new(id: 123)
70
+ handlers = ['handler1']
71
+ task_counts = {
72
+ "distribot.flow.123.phase1.handler1.tasks" => 0,
73
+ }
74
+ @phase = Distribot::Phase.new(name: 'phase1', handlers: [
75
+ 'handler1'
76
+ ])
77
+ expect(Distribot).to receive(:redis) do
78
+ redis = double('redis')
79
+ expect(redis).to receive(:get).exactly(1).times do |key|
80
+ task_counts[key]
81
+ end
82
+ redis
83
+ end
84
+ end
85
+ it 'returns true' do
86
+ expect(described_class.new.all_phase_handler_tasks_are_complete?(@flow, @phase)).to be_truthy
87
+ end
88
+ end
89
+ context 'when some tasks remain' do
90
+ before do
91
+ @flow = Distribot::Flow.new(id: 123)
92
+ handlers = ['handler1']
93
+ task_counts = {
94
+ "distribot.flow.123.phase1.handler1.tasks" => 1,
95
+ }
96
+ @phase = Distribot::Phase.new(name: 'phase1', handlers: [
97
+ 'handler1'
98
+ ])
99
+ expect(Distribot).to receive(:redis) do
100
+ redis = double('redis')
101
+ expect(redis).to receive(:get).exactly(1).times do |key|
102
+ task_counts[key]
103
+ end
104
+ redis
105
+ end
106
+ end
107
+ it 'returns false' do
108
+ expect(described_class.new.all_phase_handler_tasks_are_complete?(@flow, @phase)).to be_falsey
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe Distribot::Handler do
4
+ before do
5
+ Distribot.stub(:subscribe)
6
+ Distribot.stub(:subscribe_multi)
7
+ end
8
+ describe '.subscribe_to' do
9
+ before do
10
+ @id = SecureRandom.hex(8)
11
+ @queue_name = "queue-#{@id}"
12
+ @klass_name = "Foo#{@id}"
13
+ expect(Distribot).to receive(:subscribe).with(@queue_name, {}) do |queue_name, args, &block|
14
+ block.call(queue_name, args, block)
15
+ end
16
+ end
17
+ it 'subscribes to the queue provided' do
18
+ eval <<-EOF
19
+ class #{@klass_name}
20
+ include Distribot::Handler
21
+ subscribe_to '#{@queue_name}', handler: :callback
22
+ def callback(message)
23
+ end
24
+ end
25
+ EOF
26
+ @klass_ref = Kernel.const_get(@klass_name)
27
+ @klass_ref.new
28
+ expect(@klass_ref.handler).to eq :callback
29
+ expect(@klass_ref.queue).to eq @queue_name
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,163 @@
1
+ require 'spec_helper'
2
+
3
+ describe Distribot do
4
+ describe '.debug' do
5
+ it 'allows true and false values' do
6
+ Distribot.debug = true
7
+ expect(Distribot.debug).to be_truthy
8
+ Distribot.debug = false
9
+ expect(Distribot.debug).to be_falsey
10
+ end
11
+ end
12
+
13
+ describe '.configure' do
14
+ it 'executes the given block and uses the result as the configuration' do
15
+ Distribot.reset_configuration!
16
+ Distribot.configuration
17
+ Distribot.configure do |config|
18
+ config.foo=:bar
19
+ end
20
+ expect(Distribot.configuration.foo).to eq :bar
21
+ end
22
+ end
23
+
24
+ describe '.redis' do
25
+ context 'when configuration.redis_url' do
26
+ context 'is nil' do
27
+ before do
28
+ Distribot.reset_configuration!
29
+ Distribot.configure do |config|
30
+ config.redis_url = nil
31
+ end
32
+ end
33
+ it 'returns a new Redis instance without argument' do
34
+ expect(Redis).to receive(:new).with(no_args){ 'HELLO' }
35
+ expect(Distribot.redis).to eq 'HELLO'
36
+ end
37
+ end
38
+ context 'is given' do
39
+ before do
40
+ @redis_url = 'redis://foo:6379/0'
41
+ Distribot.reset_configuration!
42
+ Distribot.configure do |config|
43
+ config.redis_url = @redis_url
44
+ end
45
+ end
46
+ it 'returns a new Redis instance' do
47
+ expect(Redis).to receive(:new).with(url: @redis_url){ 'HELLO' }
48
+ expect(Distribot.redis).to eq 'HELLO'
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ describe '.connector' do
55
+ before do
56
+ Distribot.configure do |config|
57
+ config.rabbitmq_url = 'fake-rabbit-url'
58
+ end
59
+ end
60
+ it 'returns a BunnyConnector' do
61
+ expect(Distribot::BunnyConnector).to receive(:new).with('fake-rabbit-url'){ 'connector' }
62
+ expect(Distribot.connector).to eq 'connector'
63
+ end
64
+ end
65
+
66
+ describe '.queue_exists?(name)' do
67
+ context 'when the queue exists' do
68
+ before do
69
+ @queue_name = "queue-#{SecureRandom.uuid}"
70
+ expect(Distribot).to receive(:connector) do
71
+ connector = double('connector')
72
+ expect(connector).to receive(:queue_exists?).with(@queue_name){ true }
73
+ connector
74
+ end
75
+ end
76
+ it 'returns true' do
77
+ expect(Distribot.queue_exists?(@queue_name)).to be_truthy
78
+ end
79
+ end
80
+ context 'when the queue does not exist' do
81
+ before do
82
+ @queue_name = "queue-#{SecureRandom.uuid}"
83
+ expect(Distribot).to receive(:connector) do
84
+ connector = double('connector')
85
+ expect(connector).to receive(:queue_exists?).with(@queue_name){ false }
86
+ connector
87
+ end
88
+ end
89
+ it 'returns false' do
90
+ expect(Distribot.queue_exists?(@queue_name)).to be_falsey
91
+ end
92
+ end
93
+ end
94
+
95
+ describe '.subscribe(queue_name, options={}, &block)' do
96
+ before do
97
+ @topic = "queue-#{SecureRandom.uuid}"
98
+ expect(Distribot).to receive(:connector) do
99
+ connector = double('connector')
100
+ expect(connector).to receive(:subscribe).with(@topic, {}) do |&block|
101
+ block.call( hello: 'world' )
102
+ end
103
+ connector
104
+ end
105
+ end
106
+ it 'subscribes properly' do
107
+ Distribot.subscribe(@topic) do |message|
108
+ expect(message).to have_key :hello
109
+ expect(message[:hello]).to eq 'world'
110
+ end
111
+ end
112
+ end
113
+
114
+ describe '.subscribe_multi(topic, options={}, &block)' do
115
+ before do
116
+ @topic = "queue-#{SecureRandom.uuid}"
117
+ expect(Distribot).to receive(:connector) do
118
+ connector = double('connector')
119
+ expect(connector).to receive(:subscribe_multi).with(@topic, {}) do |&block|
120
+ block.call( hello: 'world' )
121
+ end
122
+ connector
123
+ end
124
+ end
125
+ it 'subscribes properly' do
126
+ Distribot.subscribe_multi(@topic) do |message|
127
+ expect(message).to have_key :hello
128
+ expect(message[:hello]).to eq 'world'
129
+ end
130
+ end
131
+ end
132
+
133
+ describe '.publish!(topic, data)' do
134
+ before do
135
+ @topic = SecureRandom.uuid
136
+ @data = { id: SecureRandom.uuid }
137
+ expect(Distribot).to receive(:connector) do
138
+ connector = double('connector')
139
+ expect(connector).to receive(:publish).with(@topic, @data)
140
+ connector
141
+ end
142
+ end
143
+ it 'publishes the message to the topic' do
144
+ Distribot.publish!(@topic, @data)
145
+ end
146
+ end
147
+
148
+ describe '.broadcast!(topic, data)' do
149
+ before do
150
+ @topic = SecureRandom.uuid
151
+ @data = { id: SecureRandom.uuid }
152
+ expect(Distribot).to receive(:connector) do
153
+ connector = double('connector')
154
+ expect(connector).to receive(:broadcast).with(@topic, @data)
155
+ connector
156
+ end
157
+ end
158
+ it 'publishes the message to the topic' do
159
+ Distribot.broadcast!(@topic, @data)
160
+ end
161
+ end
162
+
163
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe Distribot::MultiSubscription do
4
+ describe 'inheritance' do
5
+ let(:subject){ described_class.new(nil) }
6
+ it { should be_a Distribot::ConnectionSharer }
7
+ end
8
+
9
+ describe '#start(topic, options={}, &block)' do
10
+ it 'subscribes to the rabbit queue' do
11
+ # Arrange:
12
+ @topic = 'my.topic'
13
+ subscription = described_class.new(nil)
14
+
15
+ queue = double('queue')
16
+ channel = double('channel')
17
+ expect(channel).to receive(:queue).with('', exclusive: true, auto_delete: true){ queue }
18
+ expect(channel).to receive(:fanout).with(@topic){ 'exchange' }
19
+ expect(queue).to receive(:bind).with('exchange') do
20
+ exchange = double('exchange')
21
+ expect(exchange).to receive(:subscribe) do |&block|
22
+ block.call(nil, nil, {id: :good_message} )
23
+ block.call(nil, nil, {id: :bad_message} )
24
+ end
25
+ exchange
26
+ end
27
+ expect(subscription).to receive(:channel).exactly(2).times{ channel }
28
+
29
+ # Act:
30
+ subscription.start(@topic) do |msg|
31
+ raise "Test error" if msg[:id] == 'bad_message'
32
+ end
33
+
34
+ # Assert:
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe Distribot::PhaseFinishedHandler do
4
+ describe 'definition' do
5
+ it 'subscribes to the correct queue' do
6
+ expect(Distribot::Handler.queue_for(described_class)).to eq 'distribot.flow.phase.finished'
7
+ end
8
+ it 'declares a valid handler' do
9
+ expect(Distribot::Handler.handler_for(described_class)).to eq :callback
10
+ end
11
+ it 'has a method matching the handler name' do
12
+ expect(Distribot).to receive(:subscribe)
13
+ expect(described_class.new).to respond_to :callback
14
+ end
15
+ end
16
+
17
+ describe '#callback( :flow_id, :phase )' do
18
+ before do
19
+ expect(Distribot).to receive(:subscribe)
20
+ end
21
+ context 'when flow is' do
22
+ before do
23
+ @flow = double('flow')
24
+ @flow_id = 'xxx'
25
+ expect(Distribot::Flow).to receive(:find).with(@flow_id) { @flow }
26
+ end
27
+ context 'still in :phase' do
28
+ before do
29
+ expect(@flow).to receive(:current_phase){ 'start' }
30
+ end
31
+ context 'and the flow' do
32
+ context 'has a next phase' do
33
+ before do
34
+ expect(@flow).to receive(:next_phase).exactly(2).times{ 'finish' }
35
+ end
36
+ it 'tells the flow to transition to the next phase' do
37
+ expect(@flow).to receive(:transition_to!).with('finish')
38
+ described_class.new.callback(flow_id: @flow_id, phase: 'start')
39
+ end
40
+ end
41
+ context 'does not have a next phase' do
42
+ before do
43
+ expect(@flow).to receive(:next_phase){ nil }
44
+ expect(@flow).to receive(:id){ @flow_id }
45
+ end
46
+ it 'publishes to distribot.flow.finished' do
47
+ expect(Distribot).to receive(:publish!).with('distribot.flow.finished', {
48
+ flow_id: @flow_id
49
+ })
50
+ described_class.new.callback(flow_id: @flow_id, phase: 'start')
51
+ end
52
+ end
53
+ end
54
+ end
55
+ context 'no longer in :phase' do
56
+ it 'does nothing'
57
+ end
58
+ end
59
+ end
60
+
61
+ end
@@ -0,0 +1,150 @@
1
+ require 'spec_helper'
2
+
3
+ describe Distribot::PhaseStartedHandler do
4
+ describe 'definition' do
5
+ it 'subscribes to the correct queue' do
6
+ expect(Distribot::Handler.queue_for(described_class)).to eq 'distribot.flow.phase.started'
7
+ end
8
+ it 'declares a valid handler' do
9
+ expect(Distribot::Handler.handler_for(described_class)).to eq :callback
10
+ end
11
+ it 'has a method matching the handler name' do
12
+ expect(Distribot).to receive(:subscribe)
13
+ expect(described_class.new).to respond_to :callback
14
+ end
15
+ end
16
+
17
+ describe '#callback' do
18
+ before do
19
+ @flow = Distribot::Flow.new(
20
+ id: 1,
21
+ name: 'test',
22
+ phases: [{
23
+ name: 'phase1',
24
+ is_initial: true,
25
+ }]
26
+ )
27
+ expect(Distribot::Flow).to receive(:find).with(1){ @flow }
28
+ @phase = double('phase')
29
+ expect(@phase).to receive(:handlers).at_least(1).times{ @handlers }
30
+ expect(@flow).to receive(:phase).with('phase1'){ @phase }
31
+ end
32
+ context 'when this phase has' do
33
+ context 'no handlers' do
34
+ before do
35
+ @handlers = [ ]
36
+ expect(@phase).to receive(:name){ 'phase1' }
37
+ end
38
+ it 'considers this phase finished and publishes a message to that effect' do
39
+ expect(Distribot).to receive(:publish!).with('distribot.flow.phase.finished', {
40
+ flow_id: @flow.id,
41
+ phase: 'phase1'
42
+ })
43
+ expect(Distribot).to receive(:subscribe)
44
+ described_class.new.callback(flow_id: @flow.id, phase: 'phase1')
45
+ end
46
+ end
47
+ context 'some handlers' do
48
+ before do
49
+ @handlers = [
50
+ Distribot::PhaseHandler.new( name: 'FooHandler' ),
51
+ Distribot::PhaseHandler.new(
52
+ name: 'BarHandler',
53
+ version: '>= 1.0'
54
+ )
55
+ ]
56
+ expect(Distribot).to receive(:subscribe)
57
+ @worker = described_class.new
58
+ end
59
+ context 'and all the handlers have suitable version matches' do
60
+ before do
61
+ expect(@worker).to receive(:best_version).ordered.with(@handlers[0]){ '1.0' }
62
+ expect(@worker).to receive(:best_version).ordered.with(@handlers[1]){ '2.0' }
63
+ expect(@worker).to receive(:init_handler).ordered.with(
64
+ @flow,
65
+ @phase,
66
+ @handlers[0],
67
+ '1.0'
68
+ )
69
+ expect(@worker).to receive(:init_handler).ordered.with(
70
+ @flow,
71
+ @phase,
72
+ @handlers[1],
73
+ '2.0'
74
+ )
75
+ end
76
+ it 'jumpstarts each handler' do
77
+ @worker.callback(flow_id: @flow.id, phase: 'phase1')
78
+ end
79
+ end
80
+ context 'any of the handlers cannot find a suitable version' do
81
+ before do
82
+ expect(@worker).to receive(:best_version)
83
+ expect(@worker).not_to receive(:init_handler)
84
+ end
85
+ it 'raises an exception' do
86
+ expect{@worker.callback(flow_id: @flow.id, phase: 'phase1')}.to raise_error RuntimeError
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ describe '#best_version(handler)' do
94
+ before do
95
+ expect(Distribot).to receive(:connector) do
96
+ connector = double('connector')
97
+ expect(connector).to receive(:queues) {
98
+ %w(
99
+ distribot.flow.handler.FooHandler.0.9.0.tasks
100
+ distribot.flow.handler.FooHandler.1.0.0.tasks
101
+ distribot.flow.handler.FooHandler.1.0.1.tasks
102
+ distribot.flow.handler.FooHandler.2.0.0.tasks
103
+ distribot.flow.handler.BarHandler.1.0.0.tasks
104
+ )
105
+ }
106
+ connector
107
+ end
108
+ expect(Distribot).to receive(:subscribe)
109
+ @worker = described_class.new
110
+ end
111
+ context 'when the handler version' do
112
+ context 'is specified in the flow ' do
113
+ before do
114
+ @handler = Distribot::PhaseHandler.new(name: 'FooHandler', version: '~> 1.0')
115
+ end
116
+ it 'returns the highest available *matching* version for that handler' do
117
+ expect(@worker.best_version(@handler)).to eq '1.0.1'
118
+ end
119
+ end
120
+ context 'is not specified in the flow' do
121
+ before do
122
+ @handler = Distribot::PhaseHandler.new(name: 'FooHandler')
123
+ end
124
+ it 'returns the highest available version for that handler' do
125
+ expect(@worker.best_version(@handler)).to eq '2.0.0'
126
+ end
127
+ end
128
+ end
129
+ end
130
+
131
+ describe '#init_handler(flow, phase, handler, version)' do
132
+ before do
133
+ @flow = Distribot::Flow.new(id: 'xxx')
134
+ @phase = Distribot::Phase.new(name: 'phase1')
135
+ @handler = Distribot::PhaseHandler.new(name: 'FooHandler', version: '1.0')
136
+ @version = '1.0'
137
+ expect(Distribot).to receive(:subscribe)
138
+ expect(Distribot).to receive(:publish!).with("distribot.flow.handler.#{@handler}.#{@version}.enumerate",
139
+ flow_id: @flow.id,
140
+ phase: @phase.name,
141
+ task_queue: a_string_matching(/\.#{@handler}\.#{@version}\.tasks/),
142
+ task_counter: a_string_matching(/\.#{@flow.id}\.#{@phase.name}\.#{@handler}\.finished/),
143
+ finished_queue: 'distribot.flow.task.finished',
144
+ )
145
+ end
146
+ it 'publishes a message for the handler with everything it needs to begin task enumeration' do
147
+ described_class.new.init_handler(@flow, @phase, @handler, @version)
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Distribot::Subscription do
4
+ describe 'inheritance' do
5
+ let(:subject){ described_class.new(nil) }
6
+ it { should be_a Distribot::ConnectionSharer }
7
+ end
8
+
9
+ describe '#start(topic, options={}, &block)' do
10
+ it 'subscribes to the rabbit queue' do
11
+ # Arrange:
12
+ @topic = 'my.topic'
13
+ subscription = described_class.new(nil)
14
+
15
+ channel = double('channel')
16
+ expect(channel).to receive(:queue).ordered.with(@topic, anything) do
17
+ queue = double('queue')
18
+ expect(queue).to receive(:subscribe).with(hash_including(manual_ack: true)) do |args, &block|
19
+ # Send a good message:
20
+ delivery_info1 = OpenStruct.new(delivery_tag: 'tag1')
21
+ delivery_info2 = OpenStruct.new(delivery_tag: 'tag2')
22
+ block.call(delivery_info1, nil, {id: :good_message}.to_json )
23
+ block.call(delivery_info2, nil, {id: :bad_message}.to_json )
24
+ end
25
+ queue
26
+ end
27
+ expect(channel).to receive(:acknowledge).with('tag1', false)
28
+ expect(channel).to receive(:basic_reject).with('tag2', true)
29
+
30
+ expect(subscription).to receive(:channel).exactly(3).times{ channel }
31
+
32
+ # Act:
33
+ subscription.start(@topic) do |msg|
34
+ raise "Test error" if msg[:id] == 'bad_message'
35
+ end
36
+
37
+ # Assert:
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ describe Distribot::TaskFinishedHandler do
4
+ before do
5
+ Distribot.stub(:subscribe)
6
+ end
7
+ describe 'definition' do
8
+ it 'subscribes to the correct queue' do
9
+ expect(Distribot::Handler.queue_for(described_class)).to eq 'distribot.flow.task.finished'
10
+ end
11
+ it 'declares a valid handler' do
12
+ expect(Distribot::Handler.handler_for(described_class)).to eq :callback
13
+ end
14
+ it 'has a method matching the handler name' do
15
+ expect(described_class.new).to respond_to :callback
16
+ end
17
+ end
18
+
19
+ describe '#callback(message)' do
20
+ before do
21
+ @message = {
22
+ task_queue: 'task-queue',
23
+ finished_queue: 'finished-queue',
24
+ phase: 'the-phase',
25
+ handler: 'the-hander',
26
+ flow_id: 'flow-id'
27
+ }
28
+ end
29
+ context 'when the redis task counter' do
30
+ context 'is nil' do
31
+ before do
32
+ @redis = double('redis')
33
+ expect(@redis).to receive(:get){ nil }
34
+ expect(Distribot).to receive(:redis) do
35
+ @redis
36
+ end
37
+ end
38
+ it 'does nothing, because the handler is not yet finished' do
39
+ # Finally, action:
40
+ handler = Distribot::TaskFinishedHandler.new
41
+ handler.callback(@message)
42
+ end
43
+ end
44
+ context 'is not nil' do
45
+ before do
46
+ @redis = double('redis')
47
+ expect(Distribot).to receive(:redis).at_least(1).times do
48
+ @redis
49
+ end
50
+ end
51
+ context 'when the task count after decrementing' do
52
+ context 'is == 0' do
53
+ before do
54
+ expect(@redis).to receive(:get){ 0 }
55
+ expect(@redis).to receive(:del).ordered
56
+ end
57
+ it 'publishes a message to the handler finished queue' do
58
+ handler = Distribot::TaskFinishedHandler.new
59
+
60
+ expect(Distribot).to receive(:publish!).with("distribot.flow.handler.finished", @message.except(:finished_queue))
61
+
62
+ # Finally, action:
63
+ handler.callback(@message)
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+
71
+ end