joblin 0.1.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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +1 -0
  3. data/app/models/joblin/background_task/api_access.rb +148 -0
  4. data/app/models/joblin/background_task/attachments.rb +47 -0
  5. data/app/models/joblin/background_task/executor.rb +63 -0
  6. data/app/models/joblin/background_task/options.rb +75 -0
  7. data/app/models/joblin/background_task/retention_policy.rb +28 -0
  8. data/app/models/joblin/background_task.rb +72 -0
  9. data/app/models/joblin/concerns/job_working_dirs.rb +21 -0
  10. data/db/migrate/20250903184852_create_background_tasks.rb +12 -0
  11. data/joblin.gemspec +35 -0
  12. data/lib/joblin/batching/batch.rb +537 -0
  13. data/lib/joblin/batching/callback.rb +135 -0
  14. data/lib/joblin/batching/chain_builder.rb +247 -0
  15. data/lib/joblin/batching/compat/active_job.rb +108 -0
  16. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/css/styles.less +182 -0
  17. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/batch_tree.js +108 -0
  18. data/lib/joblin/batching/compat/sidekiq/web/batches_assets/js/util.js +2 -0
  19. data/lib/joblin/batching/compat/sidekiq/web/helpers.rb +41 -0
  20. data/lib/joblin/batching/compat/sidekiq/web/views/_batch_tree.erb +6 -0
  21. data/lib/joblin/batching/compat/sidekiq/web/views/_batches_table.erb +44 -0
  22. data/lib/joblin/batching/compat/sidekiq/web/views/_common.erb +13 -0
  23. data/lib/joblin/batching/compat/sidekiq/web/views/_jobs_table.erb +21 -0
  24. data/lib/joblin/batching/compat/sidekiq/web/views/_pagination.erb +26 -0
  25. data/lib/joblin/batching/compat/sidekiq/web/views/batch.erb +81 -0
  26. data/lib/joblin/batching/compat/sidekiq/web/views/batches.erb +23 -0
  27. data/lib/joblin/batching/compat/sidekiq/web/views/pool.erb +137 -0
  28. data/lib/joblin/batching/compat/sidekiq/web/views/pools.erb +47 -0
  29. data/lib/joblin/batching/compat/sidekiq/web.rb +218 -0
  30. data/lib/joblin/batching/compat/sidekiq.rb +149 -0
  31. data/lib/joblin/batching/compat.rb +20 -0
  32. data/lib/joblin/batching/context_hash.rb +157 -0
  33. data/lib/joblin/batching/hier_batch_ids.lua +25 -0
  34. data/lib/joblin/batching/jobs/base_job.rb +7 -0
  35. data/lib/joblin/batching/jobs/concurrent_batch_job.rb +20 -0
  36. data/lib/joblin/batching/jobs/managed_batch_job.rb +175 -0
  37. data/lib/joblin/batching/jobs/serial_batch_job.rb +20 -0
  38. data/lib/joblin/batching/pool.rb +254 -0
  39. data/lib/joblin/batching/pool_refill.lua +47 -0
  40. data/lib/joblin/batching/schedule_callback.lua +14 -0
  41. data/lib/joblin/batching/status.rb +89 -0
  42. data/lib/joblin/engine.rb +15 -0
  43. data/lib/joblin/lazy_access.rb +72 -0
  44. data/lib/joblin/uniqueness/compat/active_job.rb +75 -0
  45. data/lib/joblin/uniqueness/compat/sidekiq.rb +135 -0
  46. data/lib/joblin/uniqueness/compat.rb +20 -0
  47. data/lib/joblin/uniqueness/configuration.rb +25 -0
  48. data/lib/joblin/uniqueness/job_uniqueness.rb +49 -0
  49. data/lib/joblin/uniqueness/lock_context.rb +199 -0
  50. data/lib/joblin/uniqueness/locksmith.rb +92 -0
  51. data/lib/joblin/uniqueness/on_conflict/base.rb +32 -0
  52. data/lib/joblin/uniqueness/on_conflict/log.rb +13 -0
  53. data/lib/joblin/uniqueness/on_conflict/null_strategy.rb +9 -0
  54. data/lib/joblin/uniqueness/on_conflict/raise.rb +11 -0
  55. data/lib/joblin/uniqueness/on_conflict/reject.rb +21 -0
  56. data/lib/joblin/uniqueness/on_conflict/reschedule.rb +20 -0
  57. data/lib/joblin/uniqueness/on_conflict.rb +62 -0
  58. data/lib/joblin/uniqueness/strategy/base.rb +107 -0
  59. data/lib/joblin/uniqueness/strategy/until_and_while_executing.rb +35 -0
  60. data/lib/joblin/uniqueness/strategy/until_executed.rb +20 -0
  61. data/lib/joblin/uniqueness/strategy/until_executing.rb +20 -0
  62. data/lib/joblin/uniqueness/strategy/until_expired.rb +16 -0
  63. data/lib/joblin/uniqueness/strategy/while_executing.rb +26 -0
  64. data/lib/joblin/uniqueness/strategy.rb +27 -0
  65. data/lib/joblin/uniqueness/unique_job_common.rb +79 -0
  66. data/lib/joblin/version.rb +3 -0
  67. data/lib/joblin.rb +37 -0
  68. data/spec/batching/batch_spec.rb +493 -0
  69. data/spec/batching/callback_spec.rb +38 -0
  70. data/spec/batching/compat/active_job_spec.rb +107 -0
  71. data/spec/batching/compat/sidekiq_spec.rb +127 -0
  72. data/spec/batching/context_hash_spec.rb +54 -0
  73. data/spec/batching/flow_spec.rb +82 -0
  74. data/spec/batching/integration/fail_then_succeed.rb +42 -0
  75. data/spec/batching/integration/integration.rb +57 -0
  76. data/spec/batching/integration/nested.rb +88 -0
  77. data/spec/batching/integration/simple.rb +47 -0
  78. data/spec/batching/integration/workflow.rb +134 -0
  79. data/spec/batching/integration_helper.rb +50 -0
  80. data/spec/batching/pool_spec.rb +161 -0
  81. data/spec/batching/status_spec.rb +76 -0
  82. data/spec/batching/support/base_job.rb +19 -0
  83. data/spec/batching/support/sample_callback.rb +2 -0
  84. data/spec/internal/config/database.yml +5 -0
  85. data/spec/internal/config/routes.rb +5 -0
  86. data/spec/internal/config/storage.yml +3 -0
  87. data/spec/internal/db/combustion_test.sqlite +0 -0
  88. data/spec/internal/db/schema.rb +6 -0
  89. data/spec/internal/log/test.log +48200 -0
  90. data/spec/internal/public/favicon.ico +0 -0
  91. data/spec/models/background_task_spec.rb +41 -0
  92. data/spec/spec_helper.rb +29 -0
  93. data/spec/uniqueness/compat/active_job_spec.rb +49 -0
  94. data/spec/uniqueness/compat/sidekiq_spec.rb +68 -0
  95. data/spec/uniqueness/lock_context_spec.rb +106 -0
  96. data/spec/uniqueness/on_conflict/log_spec.rb +11 -0
  97. data/spec/uniqueness/on_conflict/raise_spec.rb +10 -0
  98. data/spec/uniqueness/on_conflict/reschedule_spec.rb +63 -0
  99. data/spec/uniqueness/on_conflict_spec.rb +16 -0
  100. data/spec/uniqueness/spec_helper.rb +19 -0
  101. data/spec/uniqueness/strategy/base_spec.rb +100 -0
  102. data/spec/uniqueness/strategy/until_and_while_executing_spec.rb +48 -0
  103. data/spec/uniqueness/strategy/until_executed_spec.rb +23 -0
  104. data/spec/uniqueness/strategy/until_executing_spec.rb +23 -0
  105. data/spec/uniqueness/strategy/until_expired_spec.rb +23 -0
  106. data/spec/uniqueness/strategy/while_executing_spec.rb +33 -0
  107. data/spec/uniqueness/support/lock_strategy.rb +28 -0
  108. data/spec/uniqueness/support/on_conflict.rb +24 -0
  109. data/spec/uniqueness/support/test_worker.rb +19 -0
  110. data/spec/uniqueness/unique_job_common_spec.rb +45 -0
  111. metadata +308 -0
@@ -0,0 +1,493 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Joblin::Batching::Batch do
4
+ describe '#initialize' do
5
+ subject { described_class }
6
+
7
+ it 'creates bid when called without it' do
8
+ expect(subject.new.bid).not_to be_nil
9
+ end
10
+
11
+ it 'reuses bid when called with it' do
12
+ batch = subject.new('dayPO5KxuRXXxw')
13
+ expect(batch.bid).to eq('dayPO5KxuRXXxw')
14
+ end
15
+ end
16
+
17
+ describe '#description' do
18
+ let(:description) { 'custom description' }
19
+ before do
20
+ subject.description = description
21
+ subject.jobs { }
22
+ end
23
+
24
+ it 'sets descriptions' do
25
+ expect(subject.description).to eq(description)
26
+ end
27
+
28
+ it 'persists description' do
29
+ expect(Joblin::Batching::Batch.redis { |r| r.hget("BID-#{subject.bid}", 'description') })
30
+ .to eq(description)
31
+ end
32
+ end
33
+
34
+ describe '#callback_queue' do
35
+ let(:callback_queue) { 'custom_queue' }
36
+ before do
37
+ subject.callback_queue = callback_queue
38
+ subject.jobs { }
39
+ end
40
+
41
+ it 'sets callback_queue' do
42
+ expect(subject.callback_queue).to eq(callback_queue)
43
+ end
44
+
45
+ it 'persists callback_queue' do
46
+ expect(Joblin::Batching::Batch.redis { |r| r.hget("BID-#{subject.bid}", 'callback_queue') })
47
+ .to eq(callback_queue)
48
+ end
49
+ end
50
+
51
+ describe '#jobs' do
52
+ it 'throws error if no block given' do
53
+ expect { subject.jobs }.to raise_error Joblin::Batching::Batch::NoBlockGivenError
54
+ end
55
+
56
+ it 'increments to_process (when started)'
57
+
58
+ it 'decrements to_process (when finished)'
59
+ # it 'calls process_successful_job to wait for block to finish' do
60
+ # batch = Joblin::Batching::Batch.new
61
+ # expect(Joblin::Batching::Batch).to receive(:process_successful_job).with(batch.bid)
62
+ # batch.jobs {}
63
+ # end
64
+
65
+ it 'sets Thread.current bid' do
66
+ batch = Joblin::Batching::Batch.new
67
+ batch.jobs do
68
+ expect(Thread.current[Joblin::Batching::CURRENT_BATCH_THREAD_KEY]).to eq(batch)
69
+ end
70
+ end
71
+ end
72
+
73
+ describe '#invalidate_all' do
74
+ class InvalidatableJob < BatchTestJobBase
75
+ def perform
76
+ return unless valid_within_batch?
77
+ was_performed
78
+ end
79
+
80
+ def was_performed; end
81
+ end
82
+
83
+ it 'marks batch in redis as invalidated' do
84
+ batch = Joblin::Batching::Batch.new
85
+ job = InvalidatableJob.new
86
+ allow(job).to receive(:was_performed)
87
+
88
+ batch.invalidate_all
89
+ batch.jobs { job.perform }
90
+
91
+ expect(job).not_to have_received(:was_performed)
92
+ end
93
+
94
+ context 'nested batches' do
95
+ let(:batch_parent) { Joblin::Batching::Batch.new }
96
+ let(:batch_child_1) { Joblin::Batching::Batch.new }
97
+ let(:batch_child_2) { Joblin::Batching::Batch.new }
98
+ let(:job_of_parent) { InvalidatableJob.new }
99
+ let(:job_of_child_1) { InvalidatableJob.new }
100
+ let(:job_of_child_2) { InvalidatableJob.new }
101
+
102
+ before do
103
+ allow(job_of_parent).to receive(:was_performed)
104
+ allow(job_of_child_1).to receive(:was_performed)
105
+ allow(job_of_child_2).to receive(:was_performed)
106
+ end
107
+
108
+ it 'invalidates all job if parent batch is marked as invalidated' do
109
+ batch_parent.invalidate_all
110
+ batch_parent.jobs do
111
+ [
112
+ job_of_parent.perform,
113
+ batch_child_1.jobs do
114
+ [
115
+ job_of_child_1.perform,
116
+ batch_child_2.jobs { job_of_child_2.perform }
117
+ ]
118
+ end
119
+ ]
120
+ end
121
+
122
+ expect(job_of_parent).not_to have_received(:was_performed)
123
+ expect(job_of_child_1).not_to have_received(:was_performed)
124
+ expect(job_of_child_2).not_to have_received(:was_performed)
125
+ end
126
+
127
+ it 'invalidates only requested batch' do
128
+ batch_child_2.invalidate_all
129
+ batch_parent.jobs do
130
+ [
131
+ job_of_parent.perform,
132
+ batch_child_1.jobs do
133
+ [
134
+ job_of_child_1.perform,
135
+ batch_child_2.jobs { job_of_child_2.perform }
136
+ ]
137
+ end
138
+ ]
139
+ end
140
+
141
+ expect(job_of_parent).to have_received(:was_performed)
142
+ expect(job_of_child_1).to have_received(:was_performed)
143
+ expect(job_of_child_2).not_to have_received(:was_performed)
144
+ end
145
+ end
146
+ end
147
+
148
+ describe '#process_failed_job' do
149
+ let(:batch) { Joblin::Batching::Batch.new }
150
+ let(:bid) { batch.bid }
151
+ let(:jid) { 'ABCD' }
152
+ before { Joblin::Batching::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 1) } }
153
+
154
+ context 'complete' do
155
+ let(:failed_jid) { 'xxx' }
156
+
157
+ it 'tries to call complete callback' do
158
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:complete, bid)
159
+ Joblin::Batching::Batch.process_failed_job(bid, failed_jid)
160
+ end
161
+
162
+ it 'add job to failed list' do
163
+ Joblin::Batching::Batch.process_failed_job(bid, 'failed-job-id')
164
+ Joblin::Batching::Batch.process_failed_job(bid, failed_jid)
165
+ failed = Joblin::Batching::Batch.redis { |r| r.smembers("BID-#{bid}-failed") }
166
+ expect(failed).to contain_exactly('xxx', 'failed-job-id')
167
+ end
168
+ end
169
+ end
170
+
171
+ describe '#process_dead_job' do
172
+ let(:batch) { Joblin::Batching::Batch.new }
173
+ let(:bid) { batch.bid }
174
+ let(:jid) { 'ABCD' }
175
+ before { Joblin::Batching::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 1) } }
176
+
177
+ context 'dead' do
178
+ let(:failed_jid) { 'xxx' }
179
+
180
+ it 'tries to call death callback' do
181
+ allow(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:stagnated, bid)
182
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:death, bid)
183
+ Joblin::Batching::Batch.process_dead_job(bid, failed_jid)
184
+ end
185
+
186
+ it 'add job to failed list' do
187
+ Joblin::Batching::Batch.process_dead_job(bid, 'failed-job-id')
188
+ Joblin::Batching::Batch.process_dead_job(bid, failed_jid)
189
+ failed = Joblin::Batching::Batch.redis { |r| r.smembers("BID-#{bid}-dead") }
190
+ expect(failed).to contain_exactly('xxx', 'failed-job-id')
191
+ end
192
+
193
+ it 'automattically triggers the :stagnated callback' do
194
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:stagnated, bid)
195
+ allow(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:death, bid)
196
+ Joblin::Batching::Batch.process_dead_job(bid, failed_jid)
197
+ end
198
+
199
+ it 'does not trigger :stagnated if pending jobs are still present' do
200
+ Joblin::Batching::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 2) }
201
+ expect(Joblin::Batching::Batch).to_not receive(:enqueue_callbacks).with(:stagnated, bid)
202
+ Joblin::Batching::Batch.process_dead_job(bid, failed_jid)
203
+ end
204
+
205
+ it 'does not trigger :stagnated if pending batches are still present' do
206
+ Joblin::Batching::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 2) }
207
+ expect(Joblin::Batching::Batch).to_not receive(:enqueue_callbacks).with(:stagnated, bid)
208
+ Joblin::Batching::Batch.process_dead_job(bid, failed_jid)
209
+ end
210
+ end
211
+ end
212
+
213
+ describe '#process_successful_job' do
214
+ let(:batch) { Joblin::Batching::Batch.new }
215
+ let(:bid) { batch.bid }
216
+ let(:jid) { 'ABCD' }
217
+ before { Joblin::Batching::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 1) } }
218
+
219
+ context 'complete' do
220
+ before { batch.on(:complete, Object) }
221
+ # before { batch.append_jobs(bid) }
222
+ # before { batch.jobs do BatchTestWorker.perform_async end }
223
+ # before { Joblin::Batching::Batch.process_failed_job(bid, 'failed-job-id') }
224
+
225
+ it 'tries to call complete callback' do
226
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:complete, bid)
227
+ Joblin::Batching::Batch.process_failed_job(bid, 'failed-job-id')
228
+ end
229
+ end
230
+
231
+ context 'success' do
232
+ before { batch.on(:complete, Object) }
233
+
234
+ it 'tries to call complete callback' do
235
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:complete, bid).ordered
236
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:success, bid).ordered
237
+ Joblin::Batching::Batch.process_successful_job(bid, jid)
238
+ end
239
+
240
+ it 'tries to call success callback after a previous failure' do
241
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:complete, bid).ordered
242
+ Joblin::Batching::Batch.process_failed_job(bid, jid)
243
+
244
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:complete, bid).ordered
245
+ expect(Joblin::Batching::Batch).to receive(:enqueue_callbacks).with(:success, bid).ordered
246
+ Joblin::Batching::Batch.process_successful_job(bid, jid)
247
+ end
248
+
249
+ it 'triggers callbacks as expected' do
250
+ ActiveJob::Base.queue_adapter = :sidekiq
251
+ Joblin::Batching::Batch::Callback.worker_class = Joblin::Batching::Compat::Sidekiq::SidekiqCallbackWorker
252
+
253
+ callback_instance = double('SampleCallback')
254
+ expect(SampleCallback).to receive(:new).at_least(1).times.and_return(callback_instance)
255
+ expect(callback_instance).to receive(:on_complete)
256
+ expect(callback_instance).to receive(:on_success)
257
+
258
+ batch.on(:complete, SampleCallback)
259
+ batch.on(:success, SampleCallback)
260
+
261
+ Sidekiq::Testing.inline! do
262
+ Joblin::Batching::Batch.process_failed_job(bid, jid)
263
+ Joblin::Batching::Batch.process_successful_job(bid, jid)
264
+ end
265
+ end
266
+
267
+ it 'delays triggering callbacks if keep_open is set' do
268
+ ActiveJob::Base.queue_adapter = :sidekiq
269
+ Joblin::Batching::Batch::Callback.worker_class = Joblin::Batching::Compat::Sidekiq::SidekiqCallbackWorker
270
+
271
+ callback_instance = double('SampleCallback')
272
+ expect(SampleCallback).to receive(:new).at_least(1).times.and_return(callback_instance)
273
+ expect(callback_instance).not_to receive(:on_complete)
274
+ expect(callback_instance).not_to receive(:on_success)
275
+
276
+ batch.on(:complete, SampleCallback)
277
+ batch.on(:success, SampleCallback)
278
+ batch.keep_open!
279
+
280
+ Sidekiq::Testing.inline! do
281
+ Joblin::Batching::Batch.process_failed_job(bid, jid)
282
+ Joblin::Batching::Batch.process_successful_job(bid, jid)
283
+ end
284
+
285
+ RSpec::Mocks.space.proxy_for(callback_instance).reset
286
+
287
+ expect(callback_instance).to receive(:on_complete)
288
+ expect(callback_instance).to receive(:on_success)
289
+
290
+ Sidekiq::Testing.inline! do
291
+ batch.let_close!
292
+ end
293
+ end
294
+
295
+ it 'triggers callbacks as expected' do
296
+ ActiveJob::Base.queue_adapter = :sidekiq
297
+ Joblin::Batching::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 0) }
298
+
299
+ class RetryingJob < BatchTestJobBase
300
+ @@failed = false
301
+
302
+ def perform
303
+ unless @@failed
304
+ @@failed = true
305
+ raise "A Failure"
306
+ end
307
+ end
308
+ end
309
+
310
+ callback_instance = double('SampleCallback')
311
+ expect(SampleCallback).to receive(:new).at_least(1).times.and_return(callback_instance)
312
+ expect(callback_instance).to receive(:on_complete)
313
+ expect(callback_instance).to receive(:on_success)
314
+
315
+ batch.on(:complete, SampleCallback)
316
+ batch.on(:success, SampleCallback)
317
+
318
+ batch.jobs do
319
+ RetryingJob.perform_later
320
+ end
321
+
322
+ job_def = Sidekiq::Worker.jobs[0]
323
+ int_job_class = job_def["class"].constantize
324
+
325
+ begin
326
+ int_job_class.process_job(job_def)
327
+ rescue
328
+ end
329
+ Sidekiq::Worker.drain_all
330
+ end
331
+
332
+ it 'cleanups redis key' do
333
+ Joblin::Batching::Batch.process_successful_job(bid, jid)
334
+ expect(Joblin::Batching::Batch.redis { |r| r.get("BID-#{bid}-pending") }.to_i).to eq(0)
335
+ end
336
+ end
337
+ end
338
+
339
+ describe '#append_jobs' do
340
+ let(:batch) { Joblin::Batching::Batch.new }
341
+
342
+ it 'increments pending' do
343
+ batch.jobs do BatchTestWorker.perform_async end
344
+ pending = Joblin::Batching::Batch.redis { |r| r.hget("BID-#{batch.bid}", 'pending') }
345
+ expect(pending).to eq('1')
346
+ end
347
+ end
348
+
349
+ describe '#enqueue_callbacks' do
350
+ let(:callback) { double('callback') }
351
+ let(:event) { :complete }
352
+
353
+ context 'on :success' do
354
+ let(:event) { :success }
355
+ context 'when no callbacks are defined' do
356
+ it 'clears redis keys' do
357
+ batch = Joblin::Batching::Batch.new
358
+ batch.jobs {}
359
+ expect(Joblin::Batching::Batch).to receive(:cleanup_redis).with(batch.bid)
360
+ Joblin::Batching::Batch.enqueue_callbacks(event, batch.bid)
361
+ end
362
+ end
363
+ end
364
+
365
+ context 'when already called' do
366
+ it 'returns and does not enqueue callbacks' do
367
+ batch = Joblin::Batching::Batch.new
368
+ batch.on(event, SampleCallback)
369
+ Joblin::Batching::Batch.redis { |r| r.hset("BID-#{batch.bid}", event, "true") }
370
+
371
+ expect(batch).not_to receive(:push_callbacks)
372
+ Joblin::Batching::Batch.enqueue_callbacks(event, batch.bid)
373
+ end
374
+ end
375
+
376
+ context 'With ActiveJob Adapter' do
377
+ around(:all) do |block|
378
+ Joblin::Batching::Batch::Callback.worker_class = Joblin::Batching::Compat::ActiveJob::ActiveJobCallbackWorker
379
+ block.run
380
+ end
381
+
382
+ context 'when not yet called' do
383
+ context 'when there is no callback' do
384
+ it 'it returns' do
385
+ batch = Joblin::Batching::Batch.new
386
+
387
+ expect(batch).not_to receive(:push_callbacks)
388
+ Joblin::Batching::Batch.enqueue_callbacks(event, batch.bid)
389
+ end
390
+ end
391
+
392
+ context 'when callback defined' do
393
+ let(:opts) { { 'a' => 'b' } }
394
+
395
+ it 'calls it passing options' do
396
+ ActiveJob::Base.queue_adapter = :test
397
+
398
+ batch = Joblin::Batching::Batch.new
399
+ batch.on(event, SampleCallback, opts)
400
+ batch.jobs {}
401
+
402
+ Joblin::Batching::Batch.enqueue_callbacks(event, batch.bid)
403
+
404
+ expect(Joblin::Batching::Batch::Callback.worker_class).to have_been_enqueued.with(
405
+ 'SampleCallback', event.to_s, opts, batch.bid, nil
406
+ )
407
+ end
408
+ end
409
+
410
+ context 'when multiple callbacks are defined' do
411
+ let(:opts) { { 'a' => 'b' } }
412
+ let(:opts2) { { 'b' => 'a' } }
413
+
414
+ it 'enqueues each callback passing their options' do
415
+ ActiveJob::Base.queue_adapter = :test
416
+
417
+ batch = Joblin::Batching::Batch.new
418
+ batch.on(event, SampleCallback, opts)
419
+ batch.on(event, SampleCallback2, opts2)
420
+ batch.jobs{}
421
+
422
+ Joblin::Batching::Batch.enqueue_callbacks(event, batch.bid)
423
+ expect(Joblin::Batching::Batch::Callback.worker_class).to have_been_enqueued.with(
424
+ 'SampleCallback2', event.to_s, opts2, batch.bid, nil
425
+ )
426
+ expect(Joblin::Batching::Batch::Callback.worker_class).to have_been_enqueued.with(
427
+ 'SampleCallback', event.to_s, opts, batch.bid, nil
428
+ )
429
+ end
430
+ end
431
+ end
432
+ end
433
+
434
+ context 'With Sidekiq Adapter' do
435
+ around(:all) do |block|
436
+ Joblin::Batching::Batch::Callback.worker_class = Joblin::Batching::Compat::Sidekiq::SidekiqCallbackWorker
437
+ block.run
438
+ end
439
+
440
+ context 'when not yet called' do
441
+ context 'when there is no callback' do
442
+ it 'it returns' do
443
+ batch = Joblin::Batching::Batch.new
444
+
445
+ expect(batch).not_to receive(:push_callbacks)
446
+ Joblin::Batching::Batch.enqueue_callbacks(event, batch.bid)
447
+ end
448
+ end
449
+
450
+ context 'when callback defined' do
451
+ let(:opts) { { 'a' => 'b' } }
452
+
453
+ it 'calls it passing options' do
454
+ batch = Joblin::Batching::Batch.new
455
+ batch.on(event, SampleCallback, opts)
456
+ batch.jobs{}
457
+
458
+ expect(Sidekiq::Client).to receive(:push_bulk).with(
459
+ 'class' => Sidekiq::Batch::Callback.worker_class,
460
+ 'args' => [['SampleCallback', event.to_s, opts, batch.bid, nil]],
461
+ 'queue' => 'default'
462
+ )
463
+
464
+ Joblin::Batching::Batch.enqueue_callbacks(event, batch.bid)
465
+ end
466
+ end
467
+
468
+ context 'when multiple callbacks are defined' do
469
+ let(:opts) { { 'a' => 'b' } }
470
+ let(:opts2) { { 'b' => 'a' } }
471
+
472
+ it 'enqueues each callback passing their options' do
473
+ batch = Joblin::Batching::Batch.new
474
+ batch.on(event, SampleCallback, opts)
475
+ batch.on(event, SampleCallback2, opts2)
476
+ batch.jobs{}
477
+
478
+ expect(Sidekiq::Client).to receive(:push_bulk).with(
479
+ 'class' => Sidekiq::Batch::Callback.worker_class,
480
+ 'args' => array_including(
481
+ ['SampleCallback', event.to_s, opts, batch.bid, nil],
482
+ ['SampleCallback2', event.to_s, opts2, batch.bid, nil],
483
+ ),
484
+ 'queue' => 'default'
485
+ )
486
+
487
+ Joblin::Batching::Batch.enqueue_callbacks(event, batch.bid)
488
+ end
489
+ end
490
+ end
491
+ end
492
+ end
493
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Joblin::Batching::Batch::Callback.worker_class do
4
+ describe '#perform' do
5
+ it 'does not do anything if it cannot find the callback class' do
6
+ subject.perform('SampleCallback', 'complete', {}, 'ABCD', 'EFGH')
7
+ end
8
+
9
+ it 'does not do anything if event is different from complete or success' do
10
+ expect(SampleCallback).not_to receive(:new)
11
+ subject.perform('SampleCallback', 'ups', {}, 'ABCD', 'EFGH')
12
+ end
13
+
14
+ it 'calls on_success if defined' do
15
+ callback_instance = double('SampleCallback', on_success: true)
16
+ expect(SampleCallback).to receive(:new).and_return(callback_instance)
17
+ expect(callback_instance).to receive(:on_success)
18
+ .with(instance_of(Joblin::Batching::Batch::Status), {})
19
+ subject.perform('SampleCallback', 'success', {}, 'ABCD', 'EFGH')
20
+ end
21
+
22
+ it 'calls on_complete if defined' do
23
+ callback_instance = double('SampleCallback')
24
+ expect(SampleCallback).to receive(:new).and_return(callback_instance)
25
+ expect(callback_instance).to receive(:on_complete)
26
+ .with(instance_of(Joblin::Batching::Batch::Status), {})
27
+ subject.perform('SampleCallback', 'complete', {}, 'ABCD', 'EFGH')
28
+ end
29
+
30
+ it 'calls specific callback if defined' do
31
+ callback_instance = double('SampleCallback')
32
+ expect(SampleCallback).to receive(:new).and_return(callback_instance)
33
+ expect(callback_instance).to receive(:sample_method)
34
+ .with(instance_of(Joblin::Batching::Batch::Status), {})
35
+ subject.perform('SampleCallback#sample_method', 'complete', {}, 'ABCD', 'EFGH')
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,107 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Joblin::Batching::Compat::ActiveJob do
4
+ describe Joblin::Batching::Compat::ActiveJob::BatchAwareJob do
5
+ include ActiveJob::TestHelper
6
+
7
+ after do
8
+ clear_enqueued_jobs
9
+ clear_performed_jobs
10
+ end
11
+
12
+ context "When Performing" do
13
+ context 'when without batch' do
14
+ it 'just yields' do
15
+ expect(Joblin::Batching::Batch).not_to receive(:process_successful_job)
16
+ expect(Joblin::Batching::Batch).not_to receive(:process_failed_job)
17
+ expect_any_instance_of(BatchTestJobBase).to receive(:perform)
18
+
19
+ BatchTestJobBase.perform_now
20
+ end
21
+ end
22
+
23
+ context 'when in batch' do
24
+ let(:bid) { 'SAMPLEBID' }
25
+
26
+ context 'when successful' do
27
+ it 'yields' do
28
+ expect_any_instance_of(BatchTestJobBase).to receive(:perform)
29
+ BatchTestJobBase.perform_now
30
+ end
31
+
32
+ it 'calls process_successful_job' do
33
+ job = BatchTestJobBase.new
34
+ job.instance_variable_set(:@bid, bid)
35
+ expect(Joblin::Batching::Batch).to receive(:process_successful_job).with(bid, job.job_id)
36
+ job.perform_now
37
+ end
38
+ end
39
+
40
+ context 'when failed' do
41
+ it 'calls process_failed_job and reraises exception' do
42
+ reraised = false
43
+ job = FailingBatchTestJobBase.new
44
+ job.instance_variable_set(:@bid, bid)
45
+ expect(Joblin::Batching::Batch).to receive(:process_failed_job)
46
+ begin
47
+ job.perform_now
48
+ rescue
49
+ reraised = true
50
+ end
51
+ expect(reraised).to be_truthy
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ context "When Enqueueing" do
58
+ context 'when without batch' do
59
+ it 'just yields' do
60
+ expect(Joblin::Batching::Batch).not_to receive(:append_jobs)
61
+ BatchTestJobBase.perform_later
62
+ expect(BatchTestJobBase).to have_been_enqueued
63
+ end
64
+ end
65
+
66
+ context 'when in batch' do
67
+ let(:bid) { 'SAMPLEBID' }
68
+
69
+ before do
70
+ Thread.current[Joblin::Batching::CURRENT_BATCH_THREAD_KEY] = Joblin::Batching::Batch.new(bid)
71
+ Thread.current[Joblin::Batching::CURRENT_BATCH_THREAD_KEY].instance_variable_set(:@open, true)
72
+ end
73
+ after { Thread.current[Joblin::Batching::CURRENT_BATCH_THREAD_KEY] = nil }
74
+
75
+ it 'yields' do
76
+ expect {
77
+ BatchTestJobBase.perform_later
78
+ }.to enqueue_job(BatchTestJobBase)
79
+ end
80
+
81
+ it 'assigns bid to job metadata' do
82
+ job = BatchTestJobBase.perform_later
83
+ expect(job.bid).to eq bid
84
+ expect(job.serialize['batch_id']).to eq bid
85
+ end
86
+ end
87
+ end
88
+
89
+ context 'worker' do
90
+ it 'defines method bid' do
91
+ expect(ActiveJob::Base.instance_methods).to include(:bid)
92
+ end
93
+
94
+ it 'defines method batch' do
95
+ expect(ActiveJob::Base.instance_methods).to include(:batch)
96
+ end
97
+
98
+ it 'defines method valid_within_batch?' do
99
+ expect(ActiveJob::Base.instance_methods).to include(:valid_within_batch?)
100
+ end
101
+ end
102
+ end
103
+
104
+ describe ".handle_job_death" do
105
+
106
+ end
107
+ end