canvas_sync 0.16.5 → 0.17.0.beta5

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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -137
  3. data/app/models/canvas_sync/sync_batch.rb +5 -0
  4. data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +11 -0
  5. data/lib/canvas_sync.rb +35 -97
  6. data/lib/canvas_sync/importers/bulk_importer.rb +4 -7
  7. data/lib/canvas_sync/job.rb +4 -10
  8. data/lib/canvas_sync/job_batches/batch.rb +403 -0
  9. data/lib/canvas_sync/job_batches/batch_aware_job.rb +62 -0
  10. data/lib/canvas_sync/job_batches/callback.rb +152 -0
  11. data/lib/canvas_sync/job_batches/chain_builder.rb +220 -0
  12. data/lib/canvas_sync/job_batches/context_hash.rb +147 -0
  13. data/lib/canvas_sync/job_batches/jobs/base_job.rb +7 -0
  14. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +19 -0
  15. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +75 -0
  16. data/lib/canvas_sync/job_batches/sidekiq.rb +93 -0
  17. data/lib/canvas_sync/job_batches/status.rb +83 -0
  18. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +35 -0
  19. data/lib/canvas_sync/jobs/report_checker.rb +3 -6
  20. data/lib/canvas_sync/jobs/report_processor_job.rb +2 -5
  21. data/lib/canvas_sync/jobs/report_starter.rb +28 -20
  22. data/lib/canvas_sync/jobs/sync_accounts_job.rb +3 -5
  23. data/lib/canvas_sync/jobs/sync_admins_job.rb +2 -4
  24. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +2 -4
  25. data/lib/canvas_sync/jobs/sync_assignments_job.rb +2 -4
  26. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +2 -4
  27. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +2 -4
  28. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +4 -34
  29. data/lib/canvas_sync/jobs/sync_roles_job.rb +2 -5
  30. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +11 -32
  31. data/lib/canvas_sync/jobs/sync_submissions_job.rb +2 -4
  32. data/lib/canvas_sync/jobs/sync_terms_job.rb +25 -8
  33. data/lib/canvas_sync/processors/assignment_groups_processor.rb +2 -3
  34. data/lib/canvas_sync/processors/assignments_processor.rb +2 -3
  35. data/lib/canvas_sync/processors/context_module_items_processor.rb +2 -3
  36. data/lib/canvas_sync/processors/context_modules_processor.rb +2 -3
  37. data/lib/canvas_sync/processors/normal_processor.rb +1 -2
  38. data/lib/canvas_sync/processors/provisioning_report_processor.rb +2 -10
  39. data/lib/canvas_sync/processors/submissions_processor.rb +2 -3
  40. data/lib/canvas_sync/version.rb +1 -1
  41. data/spec/canvas_sync/canvas_sync_spec.rb +136 -153
  42. data/spec/canvas_sync/jobs/job_spec.rb +9 -17
  43. data/spec/canvas_sync/jobs/report_checker_spec.rb +1 -3
  44. data/spec/canvas_sync/jobs/report_processor_job_spec.rb +0 -3
  45. data/spec/canvas_sync/jobs/report_starter_spec.rb +19 -28
  46. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -4
  47. data/spec/canvas_sync/jobs/sync_assignment_groups_job_spec.rb +2 -1
  48. data/spec/canvas_sync/jobs/sync_assignments_job_spec.rb +3 -2
  49. data/spec/canvas_sync/jobs/sync_context_module_items_job_spec.rb +3 -2
  50. data/spec/canvas_sync/jobs/sync_context_modules_job_spec.rb +3 -2
  51. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +3 -35
  52. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -4
  53. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +5 -12
  54. data/spec/canvas_sync/jobs/sync_submissions_job_spec.rb +2 -1
  55. data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +1 -4
  56. data/spec/dummy/config/environments/test.rb +2 -0
  57. data/spec/dummy/db/schema.rb +9 -1
  58. data/spec/job_batching/batch_aware_job_spec.rb +100 -0
  59. data/spec/job_batching/batch_spec.rb +372 -0
  60. data/spec/job_batching/callback_spec.rb +38 -0
  61. data/spec/job_batching/flow_spec.rb +88 -0
  62. data/spec/job_batching/integration/integration.rb +57 -0
  63. data/spec/job_batching/integration/nested.rb +88 -0
  64. data/spec/job_batching/integration/simple.rb +47 -0
  65. data/spec/job_batching/integration/workflow.rb +134 -0
  66. data/spec/job_batching/integration_helper.rb +48 -0
  67. data/spec/job_batching/sidekiq_spec.rb +124 -0
  68. data/spec/job_batching/status_spec.rb +92 -0
  69. data/spec/job_batching/support/base_job.rb +14 -0
  70. data/spec/job_batching/support/sample_callback.rb +2 -0
  71. data/spec/spec_helper.rb +17 -0
  72. metadata +85 -8
  73. data/lib/canvas_sync/job_chain.rb +0 -102
  74. data/lib/canvas_sync/jobs/fork_gather.rb +0 -74
  75. data/spec/canvas_sync/jobs/fork_gather_spec.rb +0 -73
@@ -0,0 +1,372 @@
1
+ require 'spec_helper'
2
+
3
+ class TestWorker < BatchTestJobBase
4
+ def perform
5
+ end
6
+ end
7
+
8
+ RSpec.describe CanvasSync::JobBatches::Batch do
9
+ describe '#initialize' do
10
+ subject { described_class }
11
+
12
+ it 'creates bid when called without it' do
13
+ expect(subject.new.bid).not_to be_nil
14
+ end
15
+
16
+ it 'reuses bid when called with it' do
17
+ batch = subject.new('dayPO5KxuRXXxw')
18
+ expect(batch.bid).to eq('dayPO5KxuRXXxw')
19
+ end
20
+ end
21
+
22
+ describe '#description' do
23
+ let(:description) { 'custom description' }
24
+ before do
25
+ subject.description = description
26
+ subject.jobs { }
27
+ end
28
+
29
+ it 'sets descriptions' do
30
+ expect(subject.description).to eq(description)
31
+ end
32
+
33
+ it 'persists description' do
34
+ expect(CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{subject.bid}", 'description') })
35
+ .to eq(description)
36
+ end
37
+ end
38
+
39
+ describe '#callback_queue' do
40
+ let(:callback_queue) { 'custom_queue' }
41
+ before do
42
+ subject.callback_queue = callback_queue
43
+ subject.jobs { }
44
+ end
45
+
46
+ it 'sets callback_queue' do
47
+ expect(subject.callback_queue).to eq(callback_queue)
48
+ end
49
+
50
+ it 'persists callback_queue' do
51
+ expect(CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{subject.bid}", 'callback_queue') })
52
+ .to eq(callback_queue)
53
+ end
54
+ end
55
+
56
+ describe '#jobs' do
57
+ it 'throws error if no block given' do
58
+ expect { subject.jobs }.to raise_error CanvasSync::JobBatches::Batch::NoBlockGivenError
59
+ end
60
+
61
+ it 'increments to_process (when started)'
62
+
63
+ it 'decrements to_process (when finished)'
64
+ # it 'calls process_successful_job to wait for block to finish' do
65
+ # batch = CanvasSync::JobBatches::Batch.new
66
+ # expect(CanvasSync::JobBatches::Batch).to receive(:process_successful_job).with(batch.bid)
67
+ # batch.jobs {}
68
+ # end
69
+
70
+ it 'sets Thread.current bid' do
71
+ batch = CanvasSync::JobBatches::Batch.new
72
+ batch.jobs do
73
+ expect(Thread.current[:batch]).to eq(batch)
74
+ end
75
+ end
76
+ end
77
+
78
+ describe '#invalidate_all' do
79
+ class InvalidatableJob < BatchTestJobBase
80
+ def perform
81
+ return unless valid_within_batch?
82
+ was_performed
83
+ end
84
+
85
+ def was_performed; end
86
+ end
87
+
88
+ it 'marks batch in redis as invalidated' do
89
+ batch = CanvasSync::JobBatches::Batch.new
90
+ job = InvalidatableJob.new
91
+ allow(job).to receive(:was_performed)
92
+
93
+ batch.invalidate_all
94
+ batch.jobs { job.perform }
95
+
96
+ expect(job).not_to have_received(:was_performed)
97
+ end
98
+
99
+ context 'nested batches' do
100
+ let(:batch_parent) { CanvasSync::JobBatches::Batch.new }
101
+ let(:batch_child_1) { CanvasSync::JobBatches::Batch.new }
102
+ let(:batch_child_2) { CanvasSync::JobBatches::Batch.new }
103
+ let(:job_of_parent) { InvalidatableJob.new }
104
+ let(:job_of_child_1) { InvalidatableJob.new }
105
+ let(:job_of_child_2) { InvalidatableJob.new }
106
+
107
+ before do
108
+ allow(job_of_parent).to receive(:was_performed)
109
+ allow(job_of_child_1).to receive(:was_performed)
110
+ allow(job_of_child_2).to receive(:was_performed)
111
+ end
112
+
113
+ it 'invalidates all job if parent batch is marked as invalidated' do
114
+ batch_parent.invalidate_all
115
+ batch_parent.jobs do
116
+ [
117
+ job_of_parent.perform,
118
+ batch_child_1.jobs do
119
+ [
120
+ job_of_child_1.perform,
121
+ batch_child_2.jobs { job_of_child_2.perform }
122
+ ]
123
+ end
124
+ ]
125
+ end
126
+
127
+ expect(job_of_parent).not_to have_received(:was_performed)
128
+ expect(job_of_child_1).not_to have_received(:was_performed)
129
+ expect(job_of_child_2).not_to have_received(:was_performed)
130
+ end
131
+
132
+ it 'invalidates only requested batch' do
133
+ batch_child_2.invalidate_all
134
+ batch_parent.jobs do
135
+ [
136
+ job_of_parent.perform,
137
+ batch_child_1.jobs do
138
+ [
139
+ job_of_child_1.perform,
140
+ batch_child_2.jobs { job_of_child_2.perform }
141
+ ]
142
+ end
143
+ ]
144
+ end
145
+
146
+ expect(job_of_parent).to have_received(:was_performed)
147
+ expect(job_of_child_1).to have_received(:was_performed)
148
+ expect(job_of_child_2).not_to have_received(:was_performed)
149
+ end
150
+ end
151
+ end
152
+
153
+ describe '#process_failed_job' do
154
+ let(:batch) { CanvasSync::JobBatches::Batch.new }
155
+ let(:bid) { batch.bid }
156
+ let(:jid) { 'ABCD' }
157
+ before { CanvasSync::JobBatches::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 1) } }
158
+
159
+ context 'complete' do
160
+ let(:failed_jid) { 'xxx' }
161
+
162
+ it 'tries to call complete callback' do
163
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:complete, bid)
164
+ CanvasSync::JobBatches::Batch.process_failed_job(bid, failed_jid)
165
+ end
166
+
167
+ it 'add job to failed list' do
168
+ CanvasSync::JobBatches::Batch.process_failed_job(bid, 'failed-job-id')
169
+ CanvasSync::JobBatches::Batch.process_failed_job(bid, failed_jid)
170
+ failed = CanvasSync::JobBatches::Batch.redis { |r| r.smembers("BID-#{bid}-failed") }
171
+ expect(failed).to eq(['xxx', 'failed-job-id'])
172
+ end
173
+ end
174
+ end
175
+
176
+ describe '#process_successful_job' do
177
+ let(:batch) { CanvasSync::JobBatches::Batch.new }
178
+ let(:bid) { batch.bid }
179
+ let(:jid) { 'ABCD' }
180
+ before { CanvasSync::JobBatches::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 1) } }
181
+
182
+ context 'complete' do
183
+ before { batch.on(:complete, Object) }
184
+ # before { batch.increment_job_queue(bid) }
185
+ # before { batch.jobs do TestWorker.perform_async end }
186
+ # before { CanvasSync::JobBatches::Batch.process_failed_job(bid, 'failed-job-id') }
187
+
188
+ it 'tries to call complete callback' do
189
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:complete, bid)
190
+ CanvasSync::JobBatches::Batch.process_failed_job(bid, 'failed-job-id')
191
+ end
192
+ end
193
+
194
+ context 'success' do
195
+ before { batch.on(:complete, Object) }
196
+ it 'tries to call complete callback' do
197
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:complete, bid).ordered
198
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:success, bid).ordered
199
+ CanvasSync::JobBatches::Batch.process_successful_job(bid, jid)
200
+ end
201
+
202
+ it 'tries to call success callback after a previous failure' do
203
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:complete, bid).ordered
204
+ CanvasSync::JobBatches::Batch.process_failed_job(bid, jid)
205
+
206
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:complete, bid).ordered
207
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:success, bid).ordered
208
+ CanvasSync::JobBatches::Batch.process_successful_job(bid, jid)
209
+ end
210
+
211
+ it 'cleanups redis key' do
212
+ CanvasSync::JobBatches::Batch.process_successful_job(bid, jid)
213
+ expect(CanvasSync::JobBatches::Batch.redis { |r| r.get("BID-#{bid}-pending") }.to_i).to eq(0)
214
+ end
215
+ end
216
+ end
217
+
218
+ describe '#increment_job_queue' do
219
+ let(:bid) { 'BID' }
220
+ let(:batch) { CanvasSync::JobBatches::Batch.new }
221
+
222
+ it 'increments pending' do
223
+ batch.jobs do TestWorker.perform_async end
224
+ pending = CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{batch.bid}", 'pending') }
225
+ expect(pending).to eq('1')
226
+ end
227
+
228
+ it 'increments total' do
229
+ batch.jobs do TestWorker.perform_async end
230
+ total = CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{batch.bid}", 'total') }
231
+ expect(total).to eq('1')
232
+ end
233
+ end
234
+
235
+ describe '#enqueue_callbacks' do
236
+ let(:callback) { double('callback') }
237
+ let(:event) { :complete }
238
+
239
+ context 'on :success' do
240
+ let(:event) { :success }
241
+ context 'when no callbacks are defined' do
242
+ it 'clears redis keys' do
243
+ batch = CanvasSync::JobBatches::Batch.new
244
+ expect(CanvasSync::JobBatches::Batch).to receive(:cleanup_redis).with(batch.bid)
245
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
246
+ end
247
+ end
248
+ end
249
+
250
+ context 'when already called' do
251
+ it 'returns and does not enqueue callbacks' do
252
+ batch = CanvasSync::JobBatches::Batch.new
253
+ batch.on(event, SampleCallback)
254
+ CanvasSync::JobBatches::Batch.redis { |r| r.hset("BID-#{batch.bid}", event, true) }
255
+
256
+ expect(batch).not_to receive(:push_callbacks)
257
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
258
+ end
259
+ end
260
+
261
+ context 'With ActiveJob Adapter' do
262
+ around(:all) do |block|
263
+ CanvasSync::JobBatches::Batch::Callback.send(:remove_const, :Worker)
264
+ CanvasSync::JobBatches::Batch::Callback.const_set(:Worker, CanvasSync::JobBatches::Batch::Callback::ActiveJobCallbackWorker)
265
+ block.run
266
+ end
267
+
268
+ context 'when not yet called' do
269
+ context 'when there is no callback' do
270
+ it 'it returns' do
271
+ batch = CanvasSync::JobBatches::Batch.new
272
+
273
+ expect(batch).not_to receive(:push_callbacks)
274
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
275
+ end
276
+ end
277
+
278
+ context 'when callback defined' do
279
+ let(:opts) { { 'a' => 'b' } }
280
+
281
+ it 'calls it passing options' do
282
+ batch = CanvasSync::JobBatches::Batch.new
283
+ batch.on(event, SampleCallback, opts)
284
+
285
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
286
+
287
+ expect(CanvasSync::JobBatches::Batch::Callback::Worker).to have_been_enqueued.with(
288
+ 'SampleCallback', event.to_s, opts, batch.bid, nil
289
+ )
290
+ end
291
+ end
292
+
293
+ context 'when multiple callbacks are defined' do
294
+ let(:opts) { { 'a' => 'b' } }
295
+ let(:opts2) { { 'b' => 'a' } }
296
+
297
+ it 'enqueues each callback passing their options' do
298
+ batch = CanvasSync::JobBatches::Batch.new
299
+ batch.on(event, SampleCallback, opts)
300
+ batch.on(event, SampleCallback2, opts2)
301
+
302
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
303
+ expect(CanvasSync::JobBatches::Batch::Callback::Worker).to have_been_enqueued.with(
304
+ 'SampleCallback2', event.to_s, opts2, batch.bid, nil
305
+ )
306
+ expect(CanvasSync::JobBatches::Batch::Callback::Worker).to have_been_enqueued.with(
307
+ 'SampleCallback', event.to_s, opts, batch.bid, nil
308
+ )
309
+ end
310
+ end
311
+ end
312
+ end
313
+
314
+ context 'With Sidekiq Adapter' do
315
+ around(:all) do |block|
316
+ CanvasSync::JobBatches::Batch::Callback.send(:remove_const, :Worker)
317
+ CanvasSync::JobBatches::Batch::Callback.const_set(:Worker, CanvasSync::JobBatches::Batch::Callback::SidekiqCallbackWorker)
318
+ block.run
319
+ end
320
+
321
+ context 'when not yet called' do
322
+ context 'when there is no callback' do
323
+ it 'it returns' do
324
+ batch = CanvasSync::JobBatches::Batch.new
325
+
326
+ expect(batch).not_to receive(:push_callbacks)
327
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
328
+ end
329
+ end
330
+
331
+ context 'when callback defined' do
332
+ let(:opts) { { 'a' => 'b' } }
333
+
334
+ it 'calls it passing options' do
335
+ batch = CanvasSync::JobBatches::Batch.new
336
+ batch.on(event, SampleCallback, opts)
337
+
338
+ expect(Sidekiq::Client).to receive(:push_bulk).with(
339
+ 'class' => Sidekiq::Batch::Callback::Worker,
340
+ 'args' => [['SampleCallback', event.to_s, opts, batch.bid, nil]],
341
+ 'queue' => 'default'
342
+ )
343
+
344
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
345
+ end
346
+ end
347
+
348
+ context 'when multiple callbacks are defined' do
349
+ let(:opts) { { 'a' => 'b' } }
350
+ let(:opts2) { { 'b' => 'a' } }
351
+
352
+ it 'enqueues each callback passing their options' do
353
+ batch = CanvasSync::JobBatches::Batch.new
354
+ batch.on(event, SampleCallback, opts)
355
+ batch.on(event, SampleCallback2, opts2)
356
+
357
+ expect(Sidekiq::Client).to receive(:push_bulk).with(
358
+ 'class' => Sidekiq::Batch::Callback::Worker,
359
+ 'args' => [
360
+ ['SampleCallback2', event.to_s, opts2, batch.bid, nil],
361
+ ['SampleCallback', event.to_s, opts, batch.bid, nil]
362
+ ],
363
+ 'queue' => 'default'
364
+ )
365
+
366
+ CanvasSync::JobBatches::Batch.enqueue_callbacks(event, batch.bid)
367
+ end
368
+ end
369
+ end
370
+ end
371
+ end
372
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe CanvasSync::JobBatches::Batch::Callback::Worker 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(CanvasSync::JobBatches::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(CanvasSync::JobBatches::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(CanvasSync::JobBatches::Batch::Status), {})
35
+ subject.perform('SampleCallback#sample_method', 'complete', {}, 'ABCD', 'EFGH')
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ class WorkerA < BatchTestJobBase
4
+ def perform
5
+ end
6
+ end
7
+
8
+ class WorkerB < BatchTestJobBase
9
+ def perform
10
+ end
11
+ end
12
+
13
+ class WorkerC < BatchTestJobBase
14
+ def perform
15
+ end
16
+ end
17
+
18
+ RSpec.describe 'Batch flow' do
19
+ context 'when handling a batch' do
20
+ let(:batch) { CanvasSync::JobBatches::Batch.new }
21
+ before { batch.on(:complete, SampleCallback, :id => 42) }
22
+ before { batch.description = 'describing the batch' }
23
+ let(:status) { CanvasSync::JobBatches::Batch::Status.new(batch.bid) }
24
+ let(:jids) { batch.jobs do 3.times do TestWorker.perform_async end end }
25
+ let(:queue) { Sidekiq::Queue.new }
26
+
27
+ it 'correctly initializes' do
28
+ expect(jids.size).to eq(3)
29
+
30
+ expect(batch.bid).not_to be_nil
31
+ expect(batch.description).to eq('describing the batch')
32
+
33
+ batch.jobs {}
34
+
35
+ expect(status.total).to eq(3)
36
+ expect(status.pending).to eq(3)
37
+ expect(status.failures).to eq(0)
38
+ expect(status.complete?).to be false
39
+ expect(status.created_at).not_to be_nil
40
+ expect(status.bid).to eq(batch.bid)
41
+ end
42
+
43
+ it 'handles an empty batch' do
44
+ batch = CanvasSync::JobBatches::Batch.new
45
+ jids = batch.jobs do nil end
46
+ expect(jids.size).to eq(0)
47
+ end
48
+ end
49
+
50
+ context 'when handling a nested batch' do
51
+ let(:batchA) { CanvasSync::JobBatches::Batch.new }
52
+ let(:batchB) { CanvasSync::JobBatches::Batch.new }
53
+ let(:batchC) { CanvasSync::JobBatches::Batch.new(batchA.bid) }
54
+ let(:batchD) { CanvasSync::JobBatches::Batch.new }
55
+ let(:jids) { [] }
56
+ let(:parent) { batchA.bid }
57
+ let(:children) { [] }
58
+
59
+ it 'handles a basic nested batch' do
60
+ batchA.jobs do
61
+ jids << WorkerA.perform_async
62
+ batchB.jobs do
63
+ jids << WorkerB.perform_async
64
+ end
65
+ jids << WorkerA.perform_async
66
+ children << batchB.bid
67
+ end
68
+
69
+ batchC.jobs do
70
+ batchD.jobs do
71
+ jids << WorkerC.perform_async
72
+ end
73
+ children << batchD.bid
74
+ end
75
+
76
+ expect(jids.size).to eq(4)
77
+ expect(CanvasSync::JobBatches::Batch::Status.new(parent).child_count).to eq(2)
78
+ children.each do |kid|
79
+ status = CanvasSync::JobBatches::Batch::Status.new(kid)
80
+ expect(status.child_count).to eq(0)
81
+ expect(status.pending).to eq(1)
82
+ expect(status.parent_bid).to eq(parent)
83
+ end
84
+
85
+ end
86
+
87
+ end
88
+ end