delayed 2.2.0 → 3.0.0

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.
@@ -10,9 +10,29 @@ RSpec.describe Delayed::ActiveJobAdapter do
10
10
  def perform; end
11
11
  end
12
12
  end
13
+ let(:enqueued_delayed_jobs) { [] }
13
14
 
14
15
  before do
15
16
  stub_const 'JobClass', job_class
17
+
18
+ next_id = 0
19
+ allow(Delayed::Job).to receive(:enqueue_job) do |options|
20
+ delayed_job = Delayed::Job.new(options)
21
+ next_id += 1
22
+ delayed_job.id = next_id
23
+ enqueued_delayed_jobs << delayed_job
24
+ delayed_job
25
+ end
26
+ allow(Delayed::Job).to receive(:enqueue_all) do |delayed_jobs|
27
+ if Delayed::Job.connection.supports_insert_returning?
28
+ delayed_jobs.each do |delayed_job|
29
+ next_id += 1
30
+ delayed_job.id = next_id
31
+ end
32
+ end
33
+ enqueued_delayed_jobs.concat(delayed_jobs)
34
+ delayed_jobs.size
35
+ end
16
36
  end
17
37
 
18
38
  around do |example|
@@ -38,7 +58,7 @@ RSpec.describe Delayed::ActiveJobAdapter do
38
58
  JobClass.perform_later
39
59
  end
40
60
 
41
- Delayed::Job.last.tap do |dj|
61
+ enqueued_delayed_jobs.last.tap do |dj|
42
62
  expect(dj.handler.lines).to match [
43
63
  "--- !ruby/object:Delayed::JobWrapper\n",
44
64
  "job_data:\n",
@@ -58,49 +78,79 @@ RSpec.describe Delayed::ActiveJobAdapter do
58
78
  end
59
79
  end
60
80
 
61
- it 'deserializes even if the underlying job class is not defined' do
62
- JobClass.perform_later
63
-
64
- Delayed::Job.last.tap do |dj|
65
- dj.update!(handler: dj.handler.gsub('JobClass', 'MissingJobClass'))
66
- expect { dj.payload_object }.not_to raise_error
67
- expect { dj.payload_object.job_id }.to raise_error(NameError, 'uninitialized constant MissingJobClass')
81
+ it 'bubbles out an error if the job fails to serialize' do
82
+ JobClass.class_eval do
83
+ def serialize(*)
84
+ raise "uh oh, serialize failed!"
85
+ end
68
86
  end
69
- expect(Delayed::Worker.new.work_off).to eq([0, 1])
70
- expect(Delayed::Job.last.last_error).to match(/uninitialized constant MissingJobClass/)
87
+
88
+ expect { JobClass.perform_later }.to raise_error(RuntimeError, "uh oh, serialize failed!")
71
89
  end
72
90
 
73
- it 'deserializes even if an underlying argument gid is not defined' do
74
- ActiveJobJob.perform_later(story: Story.create!)
75
- Delayed::Job.last.tap do |dj|
76
- dj.update!(handler: dj.handler.gsub('Story', 'MissingArgumentClass'))
77
- expect { dj.payload_object }.not_to raise_error
78
- expect { dj.payload_object.perform_now }.to raise_error(ActiveJob::DeserializationError)
91
+ it 'bubbles out an error if Delayed::Job.enqueue_job raises (single-job path)' do
92
+ allow(Delayed::Job).to receive(:enqueue_all).and_raise('uh oh, enqueue failed!')
93
+
94
+ expect { JobClass.perform_later }.to raise_error(RuntimeError, 'uh oh, enqueue failed!')
95
+ end
96
+
97
+ it 'bubbles out an error if Delayed::Job.enqueue_all raises (bulk path)' do
98
+ allow(Delayed::Job).to receive(:enqueue_all).and_raise('uh oh, enqueue failed!')
99
+
100
+ expect { ActiveJob::Base.queue_adapter.enqueue_all([JobClass.new]) }
101
+ .to raise_error(RuntimeError, 'uh oh, enqueue failed!')
102
+ end
103
+
104
+ context 'when integrated end-to-end (and_call_original)' do
105
+ before do
106
+ allow(Delayed::Job).to receive(:enqueue_job).and_call_original
107
+ allow(Delayed::Job).to receive(:enqueue_all).and_call_original
108
+ end
109
+
110
+ it 'deserializes even if the underlying job class is not defined' do
111
+ JobClass.perform_later
112
+
113
+ Delayed::Job.last.tap do |dj|
114
+ dj.update!(handler: dj.handler.gsub('JobClass', 'MissingJobClass'))
115
+ expect { dj.payload_object }.not_to raise_error
116
+ expect { dj.payload_object.job_id }.to raise_error(NameError, 'uninitialized constant MissingJobClass')
117
+ end
118
+ expect(Delayed::Worker.new.work_off).to eq([0, 1])
119
+ expect(Delayed::Job.last.last_error).to match(/uninitialized constant MissingJobClass/)
120
+ end
121
+
122
+ it 'deserializes even if an underlying argument gid is not defined' do
123
+ ActiveJobJob.perform_later(story: Story.create!)
124
+ Delayed::Job.last.tap do |dj|
125
+ dj.update!(handler: dj.handler.gsub('Story', 'MissingArgumentClass'))
126
+ expect { dj.payload_object }.not_to raise_error
127
+ expect { dj.payload_object.perform_now }.to raise_error(ActiveJob::DeserializationError)
128
+ end
129
+ expect(Delayed::Worker.new.work_off).to eq([0, 1])
130
+ expect(Delayed::Job.last.last_error).to match(/Error while trying to deserialize arguments/)
79
131
  end
80
- expect(Delayed::Worker.new.work_off).to eq([0, 1])
81
- expect(Delayed::Job.last.last_error).to match(/Error while trying to deserialize arguments/)
82
132
  end
83
133
 
84
134
  describe '.set' do
85
135
  it 'supports priority as an integer' do
86
136
  JobClass.set(priority: 43).perform_later
87
137
 
88
- expect(Delayed::Job.last.priority).to be_reporting
89
- expect(Delayed::Job.last.priority).to eq(43)
138
+ expect(enqueued_delayed_jobs.last.priority).to be_reporting
139
+ expect(enqueued_delayed_jobs.last.priority).to eq(43)
90
140
  end
91
141
 
92
142
  it 'supports priority as a Delayed::Priority' do
93
143
  JobClass.set(priority: Delayed::Priority.eventual).perform_later
94
144
 
95
- expect(Delayed::Job.last.priority).to be_eventual
96
- expect(Delayed::Job.last.priority).to eq(20)
145
+ expect(enqueued_delayed_jobs.last.priority).to be_eventual
146
+ expect(enqueued_delayed_jobs.last.priority).to eq(20)
97
147
  end
98
148
 
99
149
  it 'supports priority as a symbol' do
100
150
  JobClass.set(priority: :eventual).perform_later
101
151
 
102
- expect(Delayed::Job.last.priority).to be_eventual
103
- expect(Delayed::Job.last.priority).to eq(20)
152
+ expect(enqueued_delayed_jobs.last.priority).to be_eventual
153
+ expect(enqueued_delayed_jobs.last.priority).to eq(20)
104
154
  end
105
155
 
106
156
  it 'raises an error when run_at is used' do
@@ -111,7 +161,7 @@ RSpec.describe Delayed::ActiveJobAdapter do
111
161
  it 'converts wait_until to run_at' do
112
162
  JobClass.set(wait_until: arbitrary_time).perform_later
113
163
 
114
- expect(Delayed::Job.last.run_at).to eq('2021-01-05 03:34:33 UTC')
164
+ expect(enqueued_delayed_jobs.last.run_at).to eq('2021-01-05 03:34:33 UTC')
115
165
  end
116
166
 
117
167
  context 'when running at a specific time' do
@@ -122,7 +172,7 @@ RSpec.describe Delayed::ActiveJobAdapter do
122
172
  it 'adds wait input to current time' do
123
173
  JobClass.set(wait: (1.day + 1.hour + 1.minute)).perform_later
124
174
 
125
- expect(Delayed::Job.last.run_at).to eq('2021-01-06 04:35:33 UTC')
175
+ expect(enqueued_delayed_jobs.last.run_at).to eq('2021-01-06 04:35:33 UTC')
126
176
  end
127
177
  end
128
178
 
@@ -142,7 +192,7 @@ RSpec.describe Delayed::ActiveJobAdapter do
142
192
  it 'calls the expected setter' do
143
193
  JobClass.set(foo: 'bar').perform_later
144
194
 
145
- expect(Delayed::Job.last.queue).to eq('foo-bar')
195
+ expect(enqueued_delayed_jobs.last.queue).to eq('foo-bar')
146
196
  end
147
197
  end
148
198
 
@@ -160,7 +210,7 @@ RSpec.describe Delayed::ActiveJobAdapter do
160
210
  it 'surfaces max_attempts on the JobWrapper' do
161
211
  JobClass.perform_later
162
212
 
163
- expect(Delayed::Job.last.max_attempts).to eq 3
213
+ expect(enqueued_delayed_jobs.last.max_attempts).to eq 3
164
214
  end
165
215
  end
166
216
 
@@ -178,7 +228,7 @@ RSpec.describe Delayed::ActiveJobAdapter do
178
228
  it 'surfaces arbitrary_method on the JobWrapper' do
179
229
  JobClass.perform_later
180
230
 
181
- expect(Delayed::Job.last.payload_object.arbitrary_method).to eq 'hello'
231
+ expect(enqueued_delayed_jobs.last.payload_object.arbitrary_method).to eq 'hello'
182
232
  end
183
233
  end
184
234
  end
@@ -187,15 +237,15 @@ RSpec.describe Delayed::ActiveJobAdapter do
187
237
  it 'applies the default ActiveJob queue and priority' do
188
238
  JobClass.perform_later
189
239
 
190
- expect(Delayed::Job.last.queue).to eq('default')
191
- expect(Delayed::Job.last.priority).to eq(10)
240
+ expect(enqueued_delayed_jobs.last.queue).to eq('default')
241
+ expect(enqueued_delayed_jobs.last.priority).to eq(10)
192
242
  end
193
243
 
194
244
  it 'supports overriding queue and priority' do
195
245
  JobClass.set(queue: 'a', priority: 3).perform_later
196
246
 
197
- expect(Delayed::Job.last.queue).to eq('a')
198
- expect(Delayed::Job.last.priority).to eq(3)
247
+ expect(enqueued_delayed_jobs.last.queue).to eq('a')
248
+ expect(enqueued_delayed_jobs.last.priority).to eq(3)
199
249
  end
200
250
 
201
251
  context 'when all default queues and priorities are nil' do
@@ -209,15 +259,15 @@ RSpec.describe Delayed::ActiveJobAdapter do
209
259
  it 'applies no queue or priority' do
210
260
  JobClass.perform_later
211
261
 
212
- expect(Delayed::Job.last.queue).to be_nil
213
- expect(Delayed::Job.last.priority).to eq(0)
262
+ expect(enqueued_delayed_jobs.last.queue).to be_nil
263
+ expect(enqueued_delayed_jobs.last.priority).to eq(0)
214
264
  end
215
265
 
216
266
  it 'supports overriding queue and priority' do
217
267
  JobClass.set(queue: 'a', priority: 3).perform_later
218
268
 
219
- expect(Delayed::Job.last.queue).to eq('a')
220
- expect(Delayed::Job.last.priority).to eq(3)
269
+ expect(enqueued_delayed_jobs.last.queue).to eq('a')
270
+ expect(enqueued_delayed_jobs.last.priority).to eq(3)
221
271
  end
222
272
  end
223
273
 
@@ -232,15 +282,15 @@ RSpec.describe Delayed::ActiveJobAdapter do
232
282
  it 'applies the default Delayed queue and priority' do
233
283
  JobClass.perform_later
234
284
 
235
- expect(Delayed::Job.last.queue).to eq('dj_default')
236
- expect(Delayed::Job.last.priority).to eq(99)
285
+ expect(enqueued_delayed_jobs.last.queue).to eq('dj_default')
286
+ expect(enqueued_delayed_jobs.last.priority).to eq(99)
237
287
  end
238
288
 
239
289
  it 'supports overriding queue and priority' do
240
290
  JobClass.set(queue: 'a', priority: 3).perform_later
241
291
 
242
- expect(Delayed::Job.last.queue).to eq('a')
243
- expect(Delayed::Job.last.priority).to eq(3)
292
+ expect(enqueued_delayed_jobs.last.queue).to eq('a')
293
+ expect(enqueued_delayed_jobs.last.priority).to eq(3)
244
294
  end
245
295
  end
246
296
 
@@ -253,15 +303,15 @@ RSpec.describe Delayed::ActiveJobAdapter do
253
303
  it 'applies the default ActiveJob queue and priority' do
254
304
  JobClass.perform_later
255
305
 
256
- expect(Delayed::Job.last.queue).to eq('aj_default')
257
- expect(Delayed::Job.last.priority).to eq(11)
306
+ expect(enqueued_delayed_jobs.last.queue).to eq('aj_default')
307
+ expect(enqueued_delayed_jobs.last.priority).to eq(11)
258
308
  end
259
309
 
260
310
  it 'supports overriding queue and priority' do
261
311
  JobClass.set(queue: 'a', priority: 3).perform_later
262
312
 
263
- expect(Delayed::Job.last.queue).to eq('a')
264
- expect(Delayed::Job.last.priority).to eq(3)
313
+ expect(enqueued_delayed_jobs.last.queue).to eq('a')
314
+ expect(enqueued_delayed_jobs.last.priority).to eq(3)
265
315
  end
266
316
  end
267
317
 
@@ -277,7 +327,7 @@ RSpec.describe Delayed::ActiveJobAdapter do
277
327
  it 'applies the specified priority' do
278
328
  JobClass.perform_later
279
329
 
280
- expect(Delayed::Job.last.priority).to eq(30)
330
+ expect(enqueued_delayed_jobs.last.priority).to eq(30)
281
331
  end
282
332
  end
283
333
 
@@ -292,6 +342,11 @@ RSpec.describe Delayed::ActiveJobAdapter do
292
342
  end
293
343
  end
294
344
 
345
+ before do
346
+ allow(Delayed::Job).to receive(:enqueue_job).and_call_original
347
+ allow(Delayed::Job).to receive(:enqueue_all).and_call_original
348
+ end
349
+
295
350
  it 'passes arguments through to the perform method' do
296
351
  JobClass.perform_later('foo', kwarg: 'bar')
297
352
 
@@ -381,4 +436,188 @@ RSpec.describe Delayed::ActiveJobAdapter do
381
436
  end
382
437
  end
383
438
  end
439
+
440
+ describe '.enqueue_all' do # rubocop:disable Metrics/BlockLength
441
+ let(:adapter) { ActiveJob::Base.queue_adapter }
442
+
443
+ it 'returns 0 when given no jobs' do
444
+ expect(adapter.enqueue_all([])).to eq(0)
445
+ end
446
+
447
+ it 'does not delegate to Delayed::Job.enqueue_all for empty input' do
448
+ adapter.enqueue_all([])
449
+ expect(enqueued_delayed_jobs).to be_empty
450
+ end
451
+
452
+ it 'delegates to Delayed::Job.enqueue_all with an Array<Delayed::Job>' do
453
+ jobs = Array.new(3) { JobClass.new }
454
+ adapter.enqueue_all(jobs)
455
+
456
+ expect(Delayed::Job).to have_received(:enqueue_all).once
457
+ expect(enqueued_delayed_jobs.size).to eq(3)
458
+ expect(enqueued_delayed_jobs).to all(be_a(Delayed::Job))
459
+ end
460
+
461
+ it 'returns the count of jobs delegated' do
462
+ jobs = Array.new(3) { JobClass.new }
463
+ expect(adapter.enqueue_all(jobs)).to eq(3)
464
+ end
465
+
466
+ it 'honors per-job scheduled_at on the delegated Delayed::Job' do
467
+ job = JobClass.new
468
+ job.scheduled_at = arbitrary_time
469
+ adapter.enqueue_all([JobClass.new, job])
470
+
471
+ expect(enqueued_delayed_jobs[1].run_at).to eq(arbitrary_time)
472
+ end
473
+
474
+ it 'applies db_time_now to run_at when no scheduled_at is set' do
475
+ Timecop.freeze(arbitrary_time) do
476
+ adapter.enqueue_all([JobClass.new])
477
+ end
478
+
479
+ expect(enqueued_delayed_jobs.last.run_at).to eq(arbitrary_time)
480
+ end
481
+
482
+ it 'honors per-job queue and priority overrides on the delegated Delayed::Job' do
483
+ a = JobClass.new.tap do |j|
484
+ j.queue_name = 'q-a'
485
+ j.priority = 3
486
+ end
487
+ b = JobClass.new.tap do |j|
488
+ j.queue_name = 'q-b'
489
+ j.priority = 7
490
+ end
491
+
492
+ adapter.enqueue_all([a, b])
493
+
494
+ expect(enqueued_delayed_jobs[0]).to have_attributes(queue: 'q-a', priority: 3)
495
+ expect(enqueued_delayed_jobs[1]).to have_attributes(queue: 'q-b', priority: 7)
496
+ end
497
+
498
+ it 'supports a mix of job classes in one call' do
499
+ other_class = Class.new(ActiveJob::Base) do # rubocop:disable Rails/ApplicationJob
500
+ def perform; end
501
+ end
502
+ stub_const('OtherJobClass', other_class)
503
+
504
+ adapter.enqueue_all([JobClass.new, OtherJobClass.new])
505
+
506
+ expect(enqueued_delayed_jobs.map(&:name)).to eq(%w(JobClass OtherJobClass))
507
+ end
508
+
509
+ it 'sets the name on the delegated Delayed::Job from display_name' do
510
+ adapter.enqueue_all([JobClass.new])
511
+ expect(enqueued_delayed_jobs.last.name).to eq('JobClass')
512
+ end
513
+
514
+ it 'copies the id from each delegated Delayed::Job onto the AJ input as provider_job_id' do
515
+ skip 'requires INSERT ... RETURNING support' unless Delayed::Job.connection.supports_insert_returning?
516
+
517
+ jobs = Array.new(3) { JobClass.new }
518
+ adapter.enqueue_all(jobs)
519
+
520
+ expect(jobs.map(&:provider_job_id)).to eq(enqueued_delayed_jobs.map(&:id))
521
+ expect(jobs.map(&:provider_job_id)).to all(be_a(Integer))
522
+ end
523
+
524
+ if ActiveJob.gem_version.release >= Gem::Version.new('7.1')
525
+ it 'marks each AJ input as successfully_enqueued' do
526
+ jobs = Array.new(2) { JobClass.new }
527
+ adapter.enqueue_all(jobs)
528
+ expect(jobs).to all(be_successfully_enqueued)
529
+ end
530
+ end
531
+
532
+ context 'when the database adapter does not support INSERT RETURNING (e.g. MySQL)' do
533
+ before do
534
+ allow(Delayed::Job.connection).to receive(:supports_insert_returning?).and_return(false)
535
+ end
536
+
537
+ it 'leaves provider_job_id nil on each AJ input' do
538
+ jobs = Array.new(2) { JobClass.new }
539
+ adapter.enqueue_all(jobs)
540
+ expect(jobs.map(&:provider_job_id)).to all(be_nil)
541
+ end
542
+ end
543
+
544
+ it 'does not fire ActiveJob before/around/after_enqueue callbacks' do
545
+ fires = []
546
+ JobClass.before_enqueue { fires << :before }
547
+ JobClass.around_enqueue do |_j, block|
548
+ fires << :around_before
549
+ block.call
550
+ fires << :around_after
551
+ end
552
+ JobClass.after_enqueue { fires << :after }
553
+
554
+ adapter.enqueue_all([JobClass.new, JobClass.new])
555
+
556
+ expect(fires).to be_empty
557
+ end
558
+
559
+ if ActiveJob.gem_version.release >= Gem::Version.new('7.2')
560
+ context 'when a job sets enqueue_after_transaction_commit to :always' do
561
+ before do
562
+ JobClass.include ActiveJob::EnqueueAfterTransactionCommit
563
+ JobClass.enqueue_after_transaction_commit = :always
564
+ end
565
+
566
+ it 'raises UnsafeEnqueueError and does not delegate to Delayed::Job.enqueue_all' do
567
+ ActiveJob.deprecator.silence do
568
+ expect { adapter.enqueue_all([JobClass.new]) }.to raise_error(Delayed::ActiveJobAdapter::UnsafeEnqueueError)
569
+ end
570
+ expect(enqueued_delayed_jobs).to be_empty
571
+ end
572
+ end
573
+ end
574
+
575
+ context 'when a job has a stale run_at and deny_stale_enqueues is enabled' do
576
+ around do |example|
577
+ was = Delayed::Worker.deny_stale_enqueues
578
+ Delayed::Worker.deny_stale_enqueues = true
579
+ example.run
580
+ ensure
581
+ Delayed::Worker.deny_stale_enqueues = was
582
+ end
583
+
584
+ it 'raises StaleEnqueueError and does not delegate to Delayed::Job.enqueue_all' do
585
+ job = JobClass.new
586
+ job.scheduled_at = Time.now.utc - 1.day
587
+ expect { adapter.enqueue_all([JobClass.new, job]) }.to raise_error(Delayed::StaleEnqueueError)
588
+ expect(enqueued_delayed_jobs).to be_empty
589
+ end
590
+ end
591
+ end
592
+
593
+ describe 'single-job perform_later routes through Delayed::Job.enqueue_all' do
594
+ it 'invokes Delayed::Job.enqueue_all (not Delayed::Job.enqueue or Delayed::Job.enqueue_job)' do
595
+ expect(Delayed::Job).not_to receive(:enqueue) # rubocop:disable RSpec/MessageSpies
596
+ expect(Delayed::Job).not_to receive(:enqueue_job) # rubocop:disable RSpec/MessageSpies
597
+
598
+ JobClass.perform_later
599
+
600
+ expect(Delayed::Job).to have_received(:enqueue_all).once
601
+ end
602
+
603
+ it 'delegates exactly one Delayed::Job' do
604
+ JobClass.perform_later
605
+ expect(enqueued_delayed_jobs.size).to eq(1)
606
+ end
607
+ end
608
+
609
+ if ActiveJob.gem_version.release >= Gem::Version.new('7.1')
610
+ describe 'ActiveJob.perform_all_later' do
611
+ it 'delegates all jobs to Delayed::Job.enqueue_all in a single call' do
612
+ ActiveJob.perform_all_later([JobClass.new, JobClass.new, JobClass.new])
613
+
614
+ expect(Delayed::Job).to have_received(:enqueue_all).once
615
+ expect(enqueued_delayed_jobs.size).to eq(3)
616
+ end
617
+
618
+ it 'returns nil' do
619
+ expect(ActiveJob.perform_all_later([JobClass.new])).to be_nil
620
+ end
621
+ end
622
+ end
384
623
  end