chore-core 1.5.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.
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