delayed_job_hooked 2.1.5
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.
- data/MIT-LICENSE +20 -0
- data/README.textile +243 -0
- data/contrib/delayed_job.monitrc +14 -0
- data/contrib/delayed_job_multiple.monitrc +23 -0
- data/lib/delayed/backend/active_record.rb +82 -0
- data/lib/delayed/backend/base.rb +128 -0
- data/lib/delayed/backend/shared_spec.rb +485 -0
- data/lib/delayed/command.rb +112 -0
- data/lib/delayed/deserialization_error.rb +4 -0
- data/lib/delayed/message_sending.rb +54 -0
- data/lib/delayed/performable_mailer.rb +21 -0
- data/lib/delayed/performable_method.rb +31 -0
- data/lib/delayed/railtie.rb +18 -0
- data/lib/delayed/recipes.rb +50 -0
- data/lib/delayed/serialization/active_record.rb +13 -0
- data/lib/delayed/tasks.rb +11 -0
- data/lib/delayed/worker.rb +182 -0
- data/lib/delayed/yaml_ext.rb +41 -0
- data/lib/delayed_job.rb +13 -0
- data/lib/generators/delayed_job/delayed_job_generator.rb +34 -0
- data/lib/generators/delayed_job/templates/migration.rb +21 -0
- data/lib/generators/delayed_job/templates/script +5 -0
- data/recipes/delayed_job.rb +1 -0
- data/spec/active_record_job_spec.rb +36 -0
- data/spec/autoloaded/clazz.rb +7 -0
- data/spec/autoloaded/struct.rb +7 -0
- data/spec/database.yml +4 -0
- data/spec/message_sending_spec.rb +116 -0
- data/spec/performable_mailer_spec.rb +46 -0
- data/spec/performable_method_spec.rb +64 -0
- data/spec/sample_jobs.rb +73 -0
- data/spec/spec_helper.rb +58 -0
- data/spec/worker_spec.rb +37 -0
- data/spec/yaml_ext_spec.rb +31 -0
- metadata +221 -0
@@ -0,0 +1,485 @@
|
|
1
|
+
require File.expand_path('../../../../spec/sample_jobs', __FILE__)
|
2
|
+
|
3
|
+
shared_examples_for 'a delayed_job backend' do
|
4
|
+
let(:worker) { Delayed::Worker.new }
|
5
|
+
|
6
|
+
def create_job(opts = {})
|
7
|
+
described_class.create(opts.merge(:payload_object => SimpleJob.new))
|
8
|
+
end
|
9
|
+
|
10
|
+
before do
|
11
|
+
Delayed::Worker.max_priority = nil
|
12
|
+
Delayed::Worker.min_priority = nil
|
13
|
+
Delayed::Worker.default_priority = 99
|
14
|
+
Delayed::Worker.delay_jobs = true
|
15
|
+
SimpleJob.runs = 0
|
16
|
+
described_class.delete_all
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should set run_at automatically if not set" do
|
20
|
+
described_class.create(:payload_object => ErrorJob.new ).run_at.should_not be_nil
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should not set run_at automatically if already set" do
|
24
|
+
later = described_class.db_time_now + 5.minutes
|
25
|
+
job = described_class.create(:payload_object => ErrorJob.new, :run_at => later)
|
26
|
+
job.run_at.should be_within(1).of(later)
|
27
|
+
end
|
28
|
+
|
29
|
+
describe "enqueue" do
|
30
|
+
context "with a hash" do
|
31
|
+
it "should raise ArgumentError when handler doesn't respond_to :perform" do
|
32
|
+
lambda { described_class.enqueue(:payload_object => Object.new) }.should raise_error(ArgumentError)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should be able to set priority" do
|
36
|
+
job = described_class.enqueue :payload_object => SimpleJob.new, :priority => 5
|
37
|
+
job.priority.should == 5
|
38
|
+
end
|
39
|
+
|
40
|
+
it "should use default priority" do
|
41
|
+
job = described_class.enqueue :payload_object => SimpleJob.new
|
42
|
+
job.priority.should == 99
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should be able to set run_at" do
|
46
|
+
later = described_class.db_time_now + 5.minutes
|
47
|
+
job = described_class.enqueue :payload_object => SimpleJob.new, :run_at => later
|
48
|
+
job.run_at.should be_within(1).of(later)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with multiple arguments" do
|
53
|
+
it "should raise ArgumentError when handler doesn't respond_to :perform" do
|
54
|
+
lambda { described_class.enqueue(Object.new) }.should raise_error(ArgumentError)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should increase count after enqueuing items" do
|
58
|
+
described_class.enqueue SimpleJob.new
|
59
|
+
described_class.count.should == 1
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should not increase count after enqueuing items when delay_jobs is false" do
|
63
|
+
Delayed::Worker.delay_jobs = false
|
64
|
+
described_class.enqueue SimpleJob.new
|
65
|
+
described_class.count.should == 0
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should be able to set priority [DEPRECATED]" do
|
69
|
+
silence_warnings do
|
70
|
+
job = described_class.enqueue SimpleJob.new, 5
|
71
|
+
job.priority.should == 5
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should use default priority when it is not set" do
|
76
|
+
@job = described_class.enqueue SimpleJob.new
|
77
|
+
@job.priority.should == 99
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should be able to set run_at [DEPRECATED]" do
|
81
|
+
silence_warnings do
|
82
|
+
later = described_class.db_time_now + 5.minutes
|
83
|
+
@job = described_class.enqueue SimpleJob.new, 5, later
|
84
|
+
@job.run_at.should be_within(1).of(later)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should work with jobs in modules" do
|
89
|
+
M::ModuleJob.runs = 0
|
90
|
+
job = described_class.enqueue M::ModuleJob.new
|
91
|
+
lambda { job.invoke_job }.should change { M::ModuleJob.runs }.from(0).to(1)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
describe "callbacks" do
|
97
|
+
before(:each) do
|
98
|
+
CallbackJob.messages = []
|
99
|
+
end
|
100
|
+
|
101
|
+
%w(before success after).each do |callback|
|
102
|
+
it "should call #{callback} with job" do
|
103
|
+
job = described_class.enqueue(CallbackJob.new)
|
104
|
+
job.payload_object.should_receive(callback).with(job)
|
105
|
+
job.invoke_job
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
it "should call before and after callbacks" do
|
110
|
+
job = described_class.enqueue(CallbackJob.new)
|
111
|
+
CallbackJob.messages.should == ["enqueue"]
|
112
|
+
job.invoke_job
|
113
|
+
CallbackJob.messages.should == ["enqueue", "before", "perform", "success", "after", "completed"]
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should call the after callback with an error" do
|
117
|
+
job = described_class.enqueue(CallbackJob.new)
|
118
|
+
job.payload_object.should_receive(:perform).and_raise(RuntimeError.new("fail"))
|
119
|
+
|
120
|
+
lambda { job.invoke_job }.should raise_error
|
121
|
+
CallbackJob.messages.should == ["enqueue", "before", "error: RuntimeError", "after"]
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should call error when before raises an error" do
|
125
|
+
job = described_class.enqueue(CallbackJob.new)
|
126
|
+
job.payload_object.should_receive(:before).and_raise(RuntimeError.new("fail"))
|
127
|
+
lambda { job.invoke_job }.should raise_error(RuntimeError)
|
128
|
+
CallbackJob.messages.should == ["enqueue", "error: RuntimeError", "after"]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "payload_object" do
|
133
|
+
it "should raise a DeserializationError when the job class is totally unknown" do
|
134
|
+
job = described_class.new :handler => "--- !ruby/object:JobThatDoesNotExist {}"
|
135
|
+
lambda { job.payload_object }.should raise_error(Delayed::DeserializationError)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should raise a DeserializationError when the job struct is totally unknown" do
|
139
|
+
job = described_class.new :handler => "--- !ruby/struct:StructThatDoesNotExist {}"
|
140
|
+
lambda { job.payload_object }.should raise_error(Delayed::DeserializationError)
|
141
|
+
end
|
142
|
+
|
143
|
+
it "should raise a DeserializationError when the YAML.load raises argument error" do
|
144
|
+
job = described_class.find(create_job.id)
|
145
|
+
YAML.should_receive(:load).and_raise(ArgumentError)
|
146
|
+
lambda { job.payload_object }.should raise_error(Delayed::DeserializationError)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe "reserve" do
|
151
|
+
before do
|
152
|
+
Delayed::Worker.max_run_time = 2.minutes
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should not reserve failed jobs" do
|
156
|
+
create_job :attempts => 50, :failed_at => described_class.db_time_now
|
157
|
+
described_class.reserve(worker).should be_nil
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should not reserve jobs scheduled for the future" do
|
161
|
+
create_job :run_at => described_class.db_time_now + 1.minute
|
162
|
+
described_class.reserve(worker).should be_nil
|
163
|
+
end
|
164
|
+
|
165
|
+
it "should reserve jobs scheduled for the past" do
|
166
|
+
job = create_job :run_at => described_class.db_time_now - 1.minute
|
167
|
+
described_class.reserve(worker).should == job
|
168
|
+
end
|
169
|
+
|
170
|
+
it "should reserve jobs scheduled for the past when time zones are involved" do
|
171
|
+
Time.zone = 'US/Eastern'
|
172
|
+
job = create_job :run_at => described_class.db_time_now - 1.minute.ago.in_time_zone
|
173
|
+
described_class.reserve(worker).should == job
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should not reserve jobs locked by other workers" do
|
177
|
+
job = create_job
|
178
|
+
other_worker = Delayed::Worker.new
|
179
|
+
other_worker.name = 'other_worker'
|
180
|
+
described_class.reserve(other_worker).should == job
|
181
|
+
described_class.reserve(worker).should be_nil
|
182
|
+
end
|
183
|
+
|
184
|
+
it "should reserve open jobs" do
|
185
|
+
job = create_job
|
186
|
+
described_class.reserve(worker).should == job
|
187
|
+
end
|
188
|
+
|
189
|
+
it "should reserve expired jobs" do
|
190
|
+
job = create_job(:locked_by => worker.name, :locked_at => described_class.db_time_now - 3.minutes)
|
191
|
+
described_class.reserve(worker).should == job
|
192
|
+
end
|
193
|
+
|
194
|
+
it "should reserve own jobs" do
|
195
|
+
job = create_job(:locked_by => worker.name, :locked_at => (described_class.db_time_now - 1.minutes))
|
196
|
+
described_class.reserve(worker).should == job
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
context "#name" do
|
201
|
+
it "should be the class name of the job that was enqueued" do
|
202
|
+
described_class.create(:payload_object => ErrorJob.new ).name.should == 'ErrorJob'
|
203
|
+
end
|
204
|
+
|
205
|
+
it "should be the method that will be called if its a performable method object" do
|
206
|
+
job = described_class.new(:payload_object => NamedJob.new)
|
207
|
+
job.name.should == 'named_job'
|
208
|
+
end
|
209
|
+
|
210
|
+
it "should be the instance method that will be called if its a performable method object" do
|
211
|
+
@job = Story.create(:text => "...").delay.save
|
212
|
+
@job.name.should == 'Story#save'
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should parse from handler on deserialization error" do
|
216
|
+
job = Story.create(:text => "...").delay.text
|
217
|
+
job.payload_object.object.destroy
|
218
|
+
job = described_class.find(job.id)
|
219
|
+
job.name.should == 'Delayed::PerformableMethod'
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
context "worker prioritization" do
|
224
|
+
before(:each) do
|
225
|
+
Delayed::Worker.max_priority = nil
|
226
|
+
Delayed::Worker.min_priority = nil
|
227
|
+
end
|
228
|
+
|
229
|
+
it "should fetch jobs ordered by priority" do
|
230
|
+
10.times { described_class.enqueue SimpleJob.new, :priority => rand(10) }
|
231
|
+
jobs = []
|
232
|
+
10.times { jobs << described_class.reserve(worker) }
|
233
|
+
jobs.size.should == 10
|
234
|
+
jobs.each_cons(2) do |a, b|
|
235
|
+
a.priority.should <= b.priority
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
it "should only find jobs greater than or equal to min priority" do
|
240
|
+
min = 5
|
241
|
+
Delayed::Worker.min_priority = min
|
242
|
+
10.times {|i| described_class.enqueue SimpleJob.new, :priority => i }
|
243
|
+
5.times { described_class.reserve(worker).priority.should >= min }
|
244
|
+
end
|
245
|
+
|
246
|
+
it "should only find jobs less than or equal to max priority" do
|
247
|
+
max = 5
|
248
|
+
Delayed::Worker.max_priority = max
|
249
|
+
10.times {|i| described_class.enqueue SimpleJob.new, :priority => i }
|
250
|
+
5.times { described_class.reserve(worker).priority.should <= max }
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context "clear_locks!" do
|
255
|
+
before do
|
256
|
+
@job = create_job(:locked_by => 'worker1', :locked_at => described_class.db_time_now)
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should clear locks for the given worker" do
|
260
|
+
described_class.clear_locks!('worker1')
|
261
|
+
described_class.reserve(worker).should == @job
|
262
|
+
end
|
263
|
+
|
264
|
+
it "should not clear locks for other workers" do
|
265
|
+
described_class.clear_locks!('different_worker')
|
266
|
+
described_class.reserve(worker).should_not == @job
|
267
|
+
end
|
268
|
+
end
|
269
|
+
|
270
|
+
context "unlock" do
|
271
|
+
before do
|
272
|
+
@job = create_job(:locked_by => 'worker', :locked_at => described_class.db_time_now)
|
273
|
+
end
|
274
|
+
|
275
|
+
it "should clear locks" do
|
276
|
+
@job.unlock
|
277
|
+
@job.locked_by.should be_nil
|
278
|
+
@job.locked_at.should be_nil
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
context "large handler" do
|
283
|
+
before do
|
284
|
+
text = "Lorem ipsum dolor sit amet. " * 1000
|
285
|
+
@job = described_class.enqueue Delayed::PerformableMethod.new(text, :length, {})
|
286
|
+
end
|
287
|
+
|
288
|
+
it "should have an id" do
|
289
|
+
@job.id.should_not be_nil
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
context "max_attempts" do
|
294
|
+
before(:each) do
|
295
|
+
@job = described_class.enqueue SimpleJob.new
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'should not be defined' do
|
299
|
+
@job.max_attempts.should be_nil
|
300
|
+
end
|
301
|
+
|
302
|
+
it 'should use the max_retries value on the payload when defined' do
|
303
|
+
@job.payload_object.stub!(:max_attempts).and_return(99)
|
304
|
+
@job.max_attempts.should == 99
|
305
|
+
end
|
306
|
+
end
|
307
|
+
|
308
|
+
describe "yaml serialization" do
|
309
|
+
it "should reload changed attributes" do
|
310
|
+
story = Story.create(:text => 'hello')
|
311
|
+
job = story.delay.tell
|
312
|
+
story.update_attributes :text => 'goodbye'
|
313
|
+
described_class.find(job.id).payload_object.object.text.should == 'goodbye'
|
314
|
+
end
|
315
|
+
|
316
|
+
it "should raise deserialization error for destroyed records" do
|
317
|
+
story = Story.create(:text => 'hello')
|
318
|
+
job = story.delay.tell
|
319
|
+
story.destroy
|
320
|
+
lambda {
|
321
|
+
described_class.find(job.id).payload_object
|
322
|
+
}.should raise_error(Delayed::DeserializationError)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
describe "worker integration" do
|
327
|
+
before do
|
328
|
+
Delayed::Job.delete_all
|
329
|
+
SimpleJob.runs = 0
|
330
|
+
end
|
331
|
+
|
332
|
+
describe "running a job" do
|
333
|
+
it "should fail after Worker.max_run_time" do
|
334
|
+
begin
|
335
|
+
old_max_run_time = Delayed::Worker.max_run_time
|
336
|
+
Delayed::Worker.max_run_time = 1.second
|
337
|
+
@job = Delayed::Job.create :payload_object => LongRunningJob.new
|
338
|
+
worker.run(@job)
|
339
|
+
@job.reload.last_error.should =~ /expired/
|
340
|
+
@job.attempts.should == 1
|
341
|
+
ensure
|
342
|
+
Delayed::Worker.max_run_time = old_max_run_time
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
context "when the job raises a deserialization error" do
|
347
|
+
it "should mark the job as failed" do
|
348
|
+
Delayed::Worker.destroy_failed_jobs = false
|
349
|
+
job = described_class.create! :handler => "--- !ruby/object:JobThatDoesNotExist {}"
|
350
|
+
worker.work_off
|
351
|
+
job.reload
|
352
|
+
job.failed_at.should_not be_nil
|
353
|
+
end
|
354
|
+
end
|
355
|
+
end
|
356
|
+
|
357
|
+
describe "failed jobs" do
|
358
|
+
before do
|
359
|
+
# reset defaults
|
360
|
+
Delayed::Worker.destroy_failed_jobs = true
|
361
|
+
Delayed::Worker.max_attempts = 25
|
362
|
+
|
363
|
+
@job = Delayed::Job.enqueue(ErrorJob.new)
|
364
|
+
end
|
365
|
+
|
366
|
+
it "should record last_error when destroy_failed_jobs = false, max_attempts = 1" do
|
367
|
+
Delayed::Worker.destroy_failed_jobs = false
|
368
|
+
Delayed::Worker.max_attempts = 1
|
369
|
+
worker.run(@job)
|
370
|
+
@job.reload
|
371
|
+
@job.last_error.should =~ /did not work/
|
372
|
+
@job.attempts.should == 1
|
373
|
+
@job.failed_at.should_not be_nil
|
374
|
+
end
|
375
|
+
|
376
|
+
it "should re-schedule jobs after failing" do
|
377
|
+
worker.work_off
|
378
|
+
@job.reload
|
379
|
+
@job.last_error.should =~ /did not work/
|
380
|
+
@job.last_error.should =~ /sample_jobs.rb:\d+:in `perform'/
|
381
|
+
@job.attempts.should == 1
|
382
|
+
@job.run_at.should > Delayed::Job.db_time_now - 10.minutes
|
383
|
+
@job.run_at.should < Delayed::Job.db_time_now + 10.minutes
|
384
|
+
@job.locked_by.should be_nil
|
385
|
+
@job.locked_at.should be_nil
|
386
|
+
end
|
387
|
+
|
388
|
+
it 'should re-schedule with handler provided time if present' do
|
389
|
+
@job = Delayed::Job.enqueue(CustomRescheduleJob.new(99.minutes))
|
390
|
+
worker.run(@job)
|
391
|
+
@job.reload
|
392
|
+
|
393
|
+
(Delayed::Job.db_time_now + 99.minutes - @job.run_at).abs.should < 1
|
394
|
+
end
|
395
|
+
|
396
|
+
it "should not fail when the triggered error doesn't have a message" do
|
397
|
+
error_with_nil_message = StandardError.new
|
398
|
+
error_with_nil_message.stub!(:message).and_return nil
|
399
|
+
@job.stub!(:invoke_job).and_raise error_with_nil_message
|
400
|
+
lambda{worker.run(@job)}.should_not raise_error
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
context "reschedule" do
|
405
|
+
before do
|
406
|
+
@job = Delayed::Job.create :payload_object => SimpleJob.new
|
407
|
+
end
|
408
|
+
|
409
|
+
share_examples_for "any failure more than Worker.max_attempts times" do
|
410
|
+
context "when the job's payload has a #failure hook" do
|
411
|
+
before do
|
412
|
+
@job = Delayed::Job.create :payload_object => OnPermanentFailureJob.new
|
413
|
+
@job.payload_object.should respond_to :failure
|
414
|
+
end
|
415
|
+
|
416
|
+
it "should run that hook" do
|
417
|
+
@job.payload_object.should_receive :failure
|
418
|
+
worker.reschedule(@job)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
context "when the job's payload has no #failure hook" do
|
423
|
+
# It's a little tricky to test this in a straightforward way,
|
424
|
+
# because putting a should_not_receive expectation on
|
425
|
+
# @job.payload_object.failure makes that object
|
426
|
+
# incorrectly return true to
|
427
|
+
# payload_object.respond_to? :failure, which is what
|
428
|
+
# reschedule uses to decide whether to call failure.
|
429
|
+
# So instead, we just make sure that the payload_object as it
|
430
|
+
# already stands doesn't respond_to? failure, then
|
431
|
+
# shove it through the iterated reschedule loop and make sure we
|
432
|
+
# don't get a NoMethodError (caused by calling that nonexistent
|
433
|
+
# failure method).
|
434
|
+
|
435
|
+
before do
|
436
|
+
@job.payload_object.should_not respond_to(:failure)
|
437
|
+
end
|
438
|
+
|
439
|
+
it "should not try to run that hook" do
|
440
|
+
lambda do
|
441
|
+
Delayed::Worker.max_attempts.times { worker.reschedule(@job) }
|
442
|
+
end.should_not raise_exception(NoMethodError)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
end
|
446
|
+
|
447
|
+
context "and we want to destroy jobs" do
|
448
|
+
before do
|
449
|
+
Delayed::Worker.destroy_failed_jobs = true
|
450
|
+
end
|
451
|
+
|
452
|
+
it_should_behave_like "any failure more than Worker.max_attempts times"
|
453
|
+
|
454
|
+
it "should be destroyed if it failed more than Worker.max_attempts times" do
|
455
|
+
@job.should_receive(:destroy)
|
456
|
+
Delayed::Worker.max_attempts.times { worker.reschedule(@job) }
|
457
|
+
end
|
458
|
+
|
459
|
+
it "should not be destroyed if failed fewer than Worker.max_attempts times" do
|
460
|
+
@job.should_not_receive(:destroy)
|
461
|
+
(Delayed::Worker.max_attempts - 1).times { worker.reschedule(@job) }
|
462
|
+
end
|
463
|
+
end
|
464
|
+
|
465
|
+
context "and we don't want to destroy jobs" do
|
466
|
+
before do
|
467
|
+
Delayed::Worker.destroy_failed_jobs = false
|
468
|
+
end
|
469
|
+
|
470
|
+
it_should_behave_like "any failure more than Worker.max_attempts times"
|
471
|
+
|
472
|
+
it "should be failed if it failed more than Worker.max_attempts times" do
|
473
|
+
@job.reload.failed_at.should == nil
|
474
|
+
Delayed::Worker.max_attempts.times { worker.reschedule(@job) }
|
475
|
+
@job.reload.failed_at.should_not == nil
|
476
|
+
end
|
477
|
+
|
478
|
+
it "should not be failed if it failed fewer than Worker.max_attempts times" do
|
479
|
+
(Delayed::Worker.max_attempts - 1).times { worker.reschedule(@job) }
|
480
|
+
@job.reload.failed_at.should == nil
|
481
|
+
end
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
end
|