chore-core 1.5.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +15 -0
  2. data/LICENSE.txt +20 -0
  3. data/README.md +260 -0
  4. data/Rakefile +32 -0
  5. data/bin/chore +34 -0
  6. data/chore-core.gemspec +46 -0
  7. data/lib/chore/cli.rb +232 -0
  8. data/lib/chore/configuration.rb +13 -0
  9. data/lib/chore/consumer.rb +52 -0
  10. data/lib/chore/duplicate_detector.rb +56 -0
  11. data/lib/chore/fetcher.rb +31 -0
  12. data/lib/chore/hooks.rb +25 -0
  13. data/lib/chore/job.rb +103 -0
  14. data/lib/chore/json_encoder.rb +18 -0
  15. data/lib/chore/manager.rb +47 -0
  16. data/lib/chore/publisher.rb +29 -0
  17. data/lib/chore/queues/filesystem/consumer.rb +128 -0
  18. data/lib/chore/queues/filesystem/filesystem_queue.rb +49 -0
  19. data/lib/chore/queues/filesystem/publisher.rb +45 -0
  20. data/lib/chore/queues/sqs/consumer.rb +121 -0
  21. data/lib/chore/queues/sqs/publisher.rb +55 -0
  22. data/lib/chore/queues/sqs.rb +38 -0
  23. data/lib/chore/railtie.rb +18 -0
  24. data/lib/chore/signal.rb +175 -0
  25. data/lib/chore/strategies/consumer/batcher.rb +76 -0
  26. data/lib/chore/strategies/consumer/single_consumer_strategy.rb +34 -0
  27. data/lib/chore/strategies/consumer/threaded_consumer_strategy.rb +81 -0
  28. data/lib/chore/strategies/worker/forked_worker_strategy.rb +221 -0
  29. data/lib/chore/strategies/worker/single_worker_strategy.rb +39 -0
  30. data/lib/chore/tasks/queues.task +11 -0
  31. data/lib/chore/unit_of_work.rb +17 -0
  32. data/lib/chore/util.rb +18 -0
  33. data/lib/chore/version.rb +9 -0
  34. data/lib/chore/worker.rb +117 -0
  35. data/lib/chore-core.rb +1 -0
  36. data/lib/chore.rb +218 -0
  37. data/spec/chore/cli_spec.rb +182 -0
  38. data/spec/chore/consumer_spec.rb +36 -0
  39. data/spec/chore/duplicate_detector_spec.rb +62 -0
  40. data/spec/chore/fetcher_spec.rb +38 -0
  41. data/spec/chore/hooks_spec.rb +44 -0
  42. data/spec/chore/job_spec.rb +80 -0
  43. data/spec/chore/json_encoder_spec.rb +11 -0
  44. data/spec/chore/manager_spec.rb +39 -0
  45. data/spec/chore/queues/filesystem/filesystem_consumer_spec.rb +71 -0
  46. data/spec/chore/queues/sqs/consumer_spec.rb +136 -0
  47. data/spec/chore/queues/sqs/publisher_spec.rb +74 -0
  48. data/spec/chore/queues/sqs_spec.rb +37 -0
  49. data/spec/chore/signal_spec.rb +244 -0
  50. data/spec/chore/strategies/consumer/batcher_spec.rb +93 -0
  51. data/spec/chore/strategies/consumer/single_consumer_strategy_spec.rb +23 -0
  52. data/spec/chore/strategies/consumer/threaded_consumer_strategy_spec.rb +105 -0
  53. data/spec/chore/strategies/worker/forked_worker_strategy_spec.rb +281 -0
  54. data/spec/chore/strategies/worker/single_worker_strategy_spec.rb +36 -0
  55. data/spec/chore/worker_spec.rb +134 -0
  56. data/spec/chore_spec.rb +108 -0
  57. data/spec/spec_helper.rb +58 -0
  58. data/spec/test_job.rb +7 -0
  59. metadata +194 -0
@@ -0,0 +1,136 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chore::Queues::SQS::Consumer do
4
+ let(:queue_name) { "test" }
5
+ let(:queue_url) { "test_url" }
6
+ let(:queues) { double("queues") }
7
+ let(:queue) { double("test_queue") }
8
+ let(:options) { {} }
9
+ let(:consumer) { Chore::Queues::SQS::Consumer.new(queue_name) }
10
+ let(:message) { TestMessage.new("handle",queue_name,"message body", 1) }
11
+ let(:pool) { double("pool") }
12
+ let(:sqs) { double('AWS::SQS') }
13
+
14
+ before do
15
+ AWS::SQS.stub(:new).and_return(sqs)
16
+ sqs.stub(:queues).and_return { queues }
17
+
18
+ queues.stub(:url_for) { queue_url }
19
+ queues.stub(:[]) { queue }
20
+ queue.stub(:receive_message) { message }
21
+ queue.stub(:visibility_timeout) { 10 }
22
+ pool.stub(:empty!) { nil }
23
+ end
24
+
25
+ describe "consuming messages" do
26
+ let!(:consumer_run_for_one_message) { consumer.stub(:running?).and_return(true, false) }
27
+ let!(:messages_be_unique) { Chore::DuplicateDetector.any_instance.stub(:found_duplicate?).and_return(false) }
28
+ let!(:queue_contain_messages) { queue.stub(:receive_messages).and_return(message) }
29
+
30
+ it 'should configure sqs' do
31
+ Chore.config.stub(:aws_access_key).and_return('key')
32
+ Chore.config.stub(:aws_secret_key).and_return('secret')
33
+
34
+ AWS::SQS.should_receive(:new).with(
35
+ :access_key_id => 'key',
36
+ :secret_access_key => 'secret',
37
+ :logger => Chore.logger,
38
+ :log_level => :debug
39
+ ).and_return(sqs)
40
+ consumer.consume
41
+ end
42
+
43
+ it 'should not configure sqs multiple times' do
44
+ consumer.stub(:running?).and_return(true, true, false)
45
+
46
+ AWS::SQS.should_receive(:new).once.and_return(sqs)
47
+ consumer.consume
48
+ end
49
+
50
+ it 'should look up the queue url based on the queue name' do
51
+ queues.should_receive(:url_for).with('test').and_return(queue_url)
52
+ consumer.consume
53
+ end
54
+
55
+ it 'should look up the queue based on the queue url' do
56
+ queues.should_receive(:[]).with(queue_url).and_return(queue)
57
+ consumer.consume
58
+ end
59
+
60
+ context "should receive a message from the queue" do
61
+
62
+ it 'should use the default size of 10 when no queue_polling_size is specified' do
63
+ queue.should_receive(:receive_messages).with(:limit => 10, :attributes => [:receive_count])
64
+ consumer.consume
65
+ end
66
+
67
+ it 'should respect the queue_polling_size when specified' do
68
+ Chore.config.stub(:queue_polling_size).and_return(5)
69
+ queue.should_receive(:receive_messages).with(:limit => 5, :attributes => [:receive_count])
70
+ consumer.consume
71
+ end
72
+ end
73
+
74
+ it "should check the uniqueness of the message" do
75
+ Chore::DuplicateDetector.any_instance.should_receive(:found_duplicate?).with(message).and_return(false)
76
+ consumer.consume
77
+ end
78
+
79
+ it "should yield the message to the handler block" do
80
+ expect { |b| consumer.consume(&b) }.to yield_with_args('handle', queue_name, 10, 'message body', 0)
81
+ end
82
+
83
+ it 'should not yield for a dupe message' do
84
+ Chore::DuplicateDetector.any_instance.should_receive(:found_duplicate?).with(message).and_return(true)
85
+ expect {|b| consumer.consume(&b) }.not_to yield_control
86
+ end
87
+
88
+ context 'with no messages' do
89
+ let!(:consumer_run_for_one_message) { consumer.stub(:running?).and_return(true, true, false) }
90
+ let!(:queue_contain_messages) { queue.stub(:receive_messages).and_return(message, nil) }
91
+
92
+ it 'should sleep' do
93
+ consumer.should_receive(:sleep).with(1)
94
+ consumer.consume
95
+ end
96
+ end
97
+
98
+ context 'with messages' do
99
+ let!(:consumer_run_for_one_message) { consumer.stub(:running?).and_return(true, true, false) }
100
+ let!(:queue_contain_messages) { queue.stub(:receive_messages).and_return(message, message) }
101
+
102
+ it 'should not sleep' do
103
+ consumer.should_not_receive(:sleep)
104
+ consumer.consume
105
+ end
106
+ end
107
+ end
108
+
109
+ describe '#reset_connection!' do
110
+ it 'should reset the connection after a call to reset_connection!' do
111
+ AWS::Core::Http::ConnectionPool.stub(:pools).and_return([pool])
112
+ pool.should_receive(:empty!)
113
+ Chore::Queues::SQS::Consumer.reset_connection!
114
+ consumer.send(:queue)
115
+ end
116
+
117
+ it 'should not reset the connection between calls' do
118
+ sqs = consumer.send(:queue)
119
+ sqs.should be consumer.send(:queue)
120
+ end
121
+
122
+ it 'should reconfigure sqs' do
123
+ consumer.stub(:running?).and_return(true, false)
124
+ Chore::DuplicateDetector.any_instance.stub(:found_duplicate?).and_return(false)
125
+
126
+ queue.stub(:receive_messages).and_return(message)
127
+ consumer.consume
128
+
129
+ Chore::Queues::SQS::Consumer.reset_connection!
130
+ AWS::SQS.should_receive(:new).and_return(sqs)
131
+
132
+ consumer.stub(:running?).and_return(true, false)
133
+ consumer.consume
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,74 @@
1
+ require 'spec_helper'
2
+
3
+ module Chore
4
+ describe Queues::SQS::Publisher do
5
+ let(:job) { {'class' => 'TestJob', 'args'=>[1,2,'3']}}
6
+ let(:queue_name) { 'test_queue' }
7
+ let(:queue_url) {"http://www.queue_url.com/test_queue"}
8
+ let(:queue) { double('queue', :send_message => nil) }
9
+ let(:sqs) do
10
+ double('sqs', :queues => double('queues', :named => queue, :url_for => queue_url, :[] => queue))
11
+ end
12
+ let(:publisher) { Queues::SQS::Publisher.new }
13
+ let(:pool) { double("pool") }
14
+
15
+ before(:each) do
16
+ AWS::SQS.stub(:new).and_return(sqs)
17
+ end
18
+
19
+ it 'should configure sqs' do
20
+ Chore.config.stub(:aws_access_key).and_return('key')
21
+ Chore.config.stub(:aws_secret_key).and_return('secret')
22
+
23
+ AWS::SQS.should_receive(:new).with(
24
+ :access_key_id => 'key',
25
+ :secret_access_key => 'secret',
26
+ :logger => Chore.logger,
27
+ :log_level => :debug
28
+ )
29
+ publisher.publish(queue_name,job)
30
+ end
31
+
32
+ it 'should create send an encoded message to the specified queue' do
33
+ queue.should_receive(:send_message).with(job.to_json)
34
+ publisher.publish(queue_name,job)
35
+ end
36
+
37
+ it 'should lookup the queue when publishing' do
38
+ sqs.queues.should_receive(:url_for).with('test_queue')
39
+ publisher.publish('test_queue', job)
40
+ end
41
+
42
+ it 'should lookup multiple queues if specified' do
43
+ sqs.queues.should_receive(:url_for).with('test_queue')
44
+ sqs.queues.should_receive(:url_for).with('test_queue2')
45
+ publisher.publish('test_queue', job)
46
+ publisher.publish('test_queue2', job)
47
+ end
48
+
49
+ it 'should only lookup a named queue once' do
50
+ sqs.queues.should_receive(:url_for).with('test_queue').once
51
+ 2.times { publisher.publish('test_queue', job) }
52
+ end
53
+
54
+ describe '#reset_connection!' do
55
+ it 'should reset the connection after a call to reset_connection!' do
56
+ AWS::Core::Http::ConnectionPool.stub(:pools).and_return([pool])
57
+ pool.should_receive(:empty!)
58
+ Chore::Queues::SQS::Publisher.reset_connection!
59
+ publisher.queue(queue_name)
60
+ end
61
+
62
+ it 'should not reset the connection between calls' do
63
+ sqs = publisher.queue(queue_name)
64
+ sqs.should be publisher.queue(queue_name)
65
+ end
66
+
67
+ it 'should reconfigure sqs' do
68
+ Chore::Queues::SQS::Publisher.reset_connection!
69
+ AWS::SQS.should_receive(:new)
70
+ publisher.queue(queue_name)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,37 @@
1
+ module Chore
2
+ describe Queues::SQS do
3
+ context "when managing queues" do
4
+ let(:fake_sqs) {double(Object)}
5
+ let(:fake_queue_collection) {double(Object)}
6
+ let(:queue_name) {"test"}
7
+ let(:queue_url) {"http://amazon.sqs.url/queues/#{queue_name}"}
8
+ let(:fake_queue) {double(Object)}
9
+
10
+ before(:each) do
11
+ AWS::SQS.stub(:new).and_return(fake_sqs)
12
+ Chore.stub(:prefixed_queue_names) {[queue_name]}
13
+ fake_queue.stub(:delete)
14
+
15
+ fake_queue_collection.stub(:[]) do |key|
16
+ fake_queue
17
+ end
18
+
19
+ fake_queue_collection.stub(:create)
20
+ fake_sqs.stub(:queues).and_return(fake_queue_collection)
21
+ fake_queue_collection.stub(:url_for).with(queue_name).and_return(queue_url)
22
+ end
23
+
24
+ it 'should create queues that are defined in its internal job name list' do
25
+ #Only one job defined in the spec suite
26
+ fake_queue_collection.should_receive(:create)
27
+ Chore::Queues::SQS.create_queues!
28
+ end
29
+
30
+ it 'should delete queues that are defined in its internal job name list' do
31
+ #Only one job defined in the spec suite
32
+ fake_queue.should_receive(:delete)
33
+ Chore::Queues::SQS.delete_queues!
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,244 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe Chore::Signal do
4
+ let(:signal_handlers) {{}}
5
+ before :each do
6
+ ::Signal.stub(:trap) do |signal, command=nil, &block|
7
+ if command
8
+ signal_handlers.delete(signal)
9
+ else
10
+ signal_handlers[signal] = block
11
+ end
12
+ end
13
+
14
+ Process.stub(:kill) do |signal, process_id|
15
+ signal_handlers[signal].call if signal_handlers[signal]
16
+ sleep 0.1
17
+ end
18
+ end
19
+
20
+ after(:each) do
21
+ described_class.reset
22
+ end
23
+
24
+ describe 'trap' do
25
+ context 'without any handlers' do
26
+ it 'should not be intercepted by Chore::Signal' do
27
+ described_class.should_not_receive(:process).with("SIG1")
28
+ lambda { Process.kill('SIG1', Process.pid) }
29
+ end
30
+ end
31
+
32
+ context 'with a command' do
33
+ before(:each) do
34
+ described_class.trap('SIG1', 'DEFAULT')
35
+ end
36
+
37
+ it 'should result in Rubys default behavior for the signal' do
38
+ lambda { Process.kill('SIG1', Process.pid) }.should_not raise_error(SignalException)
39
+ end
40
+ end
41
+
42
+ context 'with a command and handler' do
43
+ before(:each) do
44
+ @callbacks = []
45
+ described_class.trap('SIG1', 'DEFAULT') do
46
+ @callbacks << :usr2
47
+ end
48
+ end
49
+
50
+ it 'should result in Rubys default behavior for the signal' do
51
+ lambda { Process.kill('SIG1', Process.pid) }.should_not raise_error(SignalException)
52
+ end
53
+
54
+ it 'should not call the handler' do
55
+ begin
56
+ Process.kill('SIG1', Process.pid)
57
+ rescue SignalException => e
58
+
59
+ end
60
+ @callbacks.should match_array([])
61
+ end
62
+ end
63
+
64
+ context 'with a single handler' do
65
+ before(:each) do
66
+ @callbacks = []
67
+ end
68
+
69
+ context 'without exceptions' do
70
+ before(:each) do
71
+ described_class.trap('SIG1') do
72
+ @callbacks << :sig1
73
+ end
74
+ end
75
+
76
+ it 'should not call the handler if signal does not match' do
77
+ lambda { Process.kill('SIG2', Process.pid) }.should_not raise_error(SignalException)
78
+ @callbacks.should match_array([])
79
+ end
80
+
81
+ it 'should call the handler if the signal matches' do
82
+ Process.kill('SIG1', Process.pid)
83
+ @callbacks.should match_array([:sig1])
84
+ end
85
+ end
86
+
87
+ context 'with exceptions' do
88
+ before(:each) do
89
+ @count = 0
90
+ @callbacks = []
91
+ described_class.trap('SIG1') do
92
+ @count += 1
93
+ raise ArgumentError if @count == 1
94
+ @callbacks << :sig1
95
+ end
96
+ end
97
+
98
+ it 'should not retry the callback' do
99
+ Process.kill('SIG1', Process.pid)
100
+ @callbacks.should match_array([])
101
+ end
102
+
103
+ it 'should still continue processing' do
104
+ 2.times { Process.kill('SIG1', Process.pid) }
105
+ @callbacks.should match_array([:sig1])
106
+ end
107
+ end
108
+ end
109
+
110
+ context 'with multiple handlers' do
111
+ before(:each) do
112
+ @callbacks = []
113
+ described_class.trap('SIG1') do
114
+ @callbacks << :first
115
+ end
116
+ described_class.trap('SIG1') do
117
+ @callbacks << :second
118
+ end
119
+ end
120
+
121
+ it 'should only call the last recorded handler' do
122
+ Process.kill('SIG1', Process.pid)
123
+ @callbacks.should match_array([:second])
124
+ end
125
+ end
126
+
127
+ context 'with reset handler' do
128
+ before(:each) do
129
+ @callbacks = []
130
+ described_class.trap('SIG1') do
131
+ @callbacks << :first
132
+ end
133
+ described_class.trap('SIG1', 'DEFAULT')
134
+ end
135
+
136
+ it 'should not call the original handler' do
137
+ Process.kill('SIG1', Process.pid)
138
+ @callbacks.should match_array([])
139
+ end
140
+ end
141
+
142
+ context 'with multiple signals' do
143
+ before(:each) do
144
+ @callbacks = []
145
+ described_class.trap('SIG1') do
146
+ @callbacks << :sig1
147
+ end
148
+ described_class.trap('SIG2') do
149
+ @callbacks << :sig2
150
+ end
151
+ end
152
+
153
+ it 'should handle each one' do
154
+ Process.kill('SIG1', Process.pid)
155
+ Process.kill('SIG2', Process.pid)
156
+ @callbacks.should match_array([:sig1, :sig2])
157
+ end
158
+
159
+ it 'should process most recent signals first' do
160
+ mutex = Mutex.new
161
+ described_class.trap('SIG1') do
162
+ @callbacks << :sig1
163
+ mutex.lock
164
+ end
165
+ described_class.trap('SIG3') do
166
+ @callbacks << :sig3
167
+ end
168
+
169
+ mutex.lock
170
+ Process.kill('SIG1', Process.pid)
171
+ Process.kill('SIG1', Process.pid)
172
+ Process.kill('SIG2', Process.pid)
173
+ Process.kill('SIG3', Process.pid)
174
+ mutex.unlock
175
+ sleep 0.1
176
+
177
+ @callbacks.should match_array([:sig1, :sig3, :sig2, :sig1])
178
+ end
179
+
180
+ it 'should process primary signals first' do
181
+ mutex = Mutex.new
182
+ described_class.trap('SIG1') do
183
+ @callbacks << :sig1
184
+ mutex.lock
185
+ end
186
+ described_class.trap('SIG3') do
187
+ @callbacks << :sig3
188
+ end
189
+
190
+ mutex.lock
191
+ Process.kill('SIG1', Process.pid)
192
+ Process.kill('SIG2', Process.pid)
193
+ Process.kill('SIG3', Process.pid)
194
+ mutex.unlock
195
+ sleep 0.1
196
+
197
+ @callbacks.should match_array([:sig1, :sig2, :sig3])
198
+ end
199
+ end
200
+ end
201
+
202
+ describe 'reset' do
203
+ it 'should clear existing handlers' do
204
+ callbacks = []
205
+ described_class.trap('SIG1') do
206
+ callbacks << :sig1
207
+ end
208
+ described_class.reset
209
+ Process.kill('SIG1', Process.pid)
210
+ callbacks.should match_array([])
211
+ end
212
+
213
+ it 'should not run unprocessed signals' do
214
+ callbacks = []
215
+ mutex = Mutex.new
216
+ described_class.trap('SIG1') do
217
+ callbacks << :sig1
218
+ mutex.lock
219
+ end
220
+ described_class.trap('SIG2') do
221
+ callbacks << :sig2
222
+ end
223
+ mutex.lock
224
+ Process.kill('SIG1', Process.pid)
225
+ Process.kill('SIG2', Process.pid)
226
+ described_class.reset
227
+ mutex.unlock
228
+ callbacks.should match_array([:sig1])
229
+ end
230
+
231
+ it 'should still listen for new traps' do
232
+ callbacks = []
233
+ described_class.trap('SIG1') do
234
+ callbacks << :sig1
235
+ end
236
+ described_class.reset
237
+ described_class.trap('SIG1') do
238
+ callbacks << :sig1
239
+ end
240
+ Process.kill('SIG1', Process.pid)
241
+ callbacks.should match_array([:sig1])
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,93 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chore::Strategy::Batcher do
4
+ let(:batch_size) { 5 }
5
+ let(:callback) { double("callback") }
6
+ subject do
7
+ batcher = Chore::Strategy::Batcher.new(batch_size)
8
+ batcher.callback = callback
9
+ batcher
10
+ end
11
+
12
+ context 'with no items' do
13
+ it 'should not be ready' do
14
+ subject.should_not be_ready
15
+ end
16
+
17
+ it 'should not invoke the callback when executed' do
18
+ callback.should_not_receive(:call)
19
+ subject.execute
20
+ end
21
+
22
+ it 'should not invoke the callback when force-executed' do
23
+ callback.should_not_receive(:call)
24
+ subject.execute(true)
25
+ end
26
+
27
+ it 'should not invoke callback when adding a new item' do
28
+ callback.should_not_receive(:call)
29
+ subject.add('test')
30
+ end
31
+ end
32
+
33
+ context 'with partial batch completed' do
34
+ let(:batch) { ['test'] * 3 }
35
+
36
+ before(:each) do
37
+ subject.batch = batch.dup
38
+ end
39
+
40
+ it 'should not be ready' do
41
+ subject.should_not be_ready
42
+ end
43
+
44
+ it 'should not invoke callback when executed' do
45
+ callback.should_not_receive(:call)
46
+ subject.execute
47
+ end
48
+
49
+ it 'should invoke callback when force-executed' do
50
+ callback.should_receive(:call).with(batch)
51
+ subject.execute(true)
52
+ end
53
+
54
+ it 'should invoke callback when add completes the batch' do
55
+ subject.add('test')
56
+ callback.should_receive(:call).with(['test'] * 5)
57
+ subject.add('test')
58
+ end
59
+ end
60
+
61
+ context 'with batch completed' do
62
+ let(:batch) { ['test'] * 5 }
63
+
64
+ before(:each) do
65
+ subject.batch = batch.dup
66
+ end
67
+
68
+ it 'should be ready' do
69
+ subject.should be_ready
70
+ end
71
+
72
+ it 'should invoke callback when executed' do
73
+ callback.should_receive(:call).with(batch)
74
+ subject.execute
75
+ end
76
+
77
+ it 'should invoke callback when force-executed' do
78
+ callback.should_receive(:call).with(batch)
79
+ subject.execute(true)
80
+ end
81
+
82
+ it 'should invoke callback with subset-only when added to' do
83
+ callback.should_receive(:call).with(['test'] * 5)
84
+ subject.add('test')
85
+ end
86
+
87
+ it 'should leave remaining batch when added to' do
88
+ callback.stub(:call)
89
+ subject.add('test')
90
+ subject.batch.should == ['test']
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+
3
+ describe Chore::Strategy::SingleConsumerStrategy do
4
+ let(:fetcher) { double("fetcher") }
5
+ let(:manager) { double("manager") }
6
+ let(:consumer) { double("consumer") }
7
+ let(:test_queues) { ["test-queue"] }
8
+ let(:strategy) { Chore::Strategy::SingleConsumerStrategy.new(fetcher) }
9
+
10
+ before do
11
+ fetcher.stub(:manager) { manager }
12
+ Chore.config.stub(:queues).and_return(test_queues)
13
+ Chore.config.stub(:consumer).and_return(consumer)
14
+
15
+ end
16
+
17
+ it "should consume and then assign a message" do
18
+ consumer.should_receive(:new).with(test_queues.first).and_return(consumer)
19
+ consumer.should_receive(:consume).and_yield(1, 'test-queue', 60, "test", 0)
20
+ manager.should_receive(:assign).with(Chore::UnitOfWork.new(1, 'test-queue', 60, "test", 0, consumer))
21
+ strategy.fetch
22
+ end
23
+ end