canvas_sync 0.22.1 → 0.22.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/canvas_sync/job_batches/batch.rb +102 -80
- data/lib/canvas_sync/job_batches/compat/active_job.rb +1 -1
- data/lib/canvas_sync/job_batches/compat/sidekiq.rb +1 -1
- data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +8 -1
- data/lib/canvas_sync/job_batches/pool.rb +20 -9
- data/lib/canvas_sync/job_uniqueness/lock_context.rb +26 -4
- data/lib/canvas_sync/version.rb +1 -1
- data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -1
- data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +1 -1
- data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -1
- data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +1 -1
- data/spec/canvas_sync/models/accounts_spec.rb +1 -1
- data/spec/canvas_sync/models/admins_spec.rb +2 -2
- data/spec/canvas_sync/models/assignment_group_spec.rb +1 -1
- data/spec/canvas_sync/models/assignment_spec.rb +3 -3
- data/spec/canvas_sync/models/context_module_item_spec.rb +1 -1
- data/spec/canvas_sync/models/context_module_spec.rb +1 -1
- data/spec/canvas_sync/models/course_spec.rb +1 -1
- data/spec/canvas_sync/models/enrollment_spec.rb +1 -1
- data/spec/canvas_sync/models/group_membership_spec.rb +1 -1
- data/spec/canvas_sync/models/group_spec.rb +1 -1
- data/spec/canvas_sync/models/roles_spec.rb +2 -2
- data/spec/canvas_sync/models/section_spec.rb +1 -1
- data/spec/canvas_sync/models/submission_spec.rb +1 -1
- data/spec/canvas_sync/models/term_spec.rb +6 -6
- data/spec/canvas_sync/models/user_spec.rb +1 -1
- data/spec/canvas_sync/services/module_event_spec.rb +1 -1
- data/spec/canvas_sync/services/module_item_event_spec.rb +1 -1
- data/spec/factories/account_factory.rb +4 -4
- data/spec/factories/admin_factory.rb +4 -4
- data/spec/factories/assignment_factory.rb +1 -1
- data/spec/factories/assignment_group_factory.rb +3 -3
- data/spec/factories/context_module_factory.rb +4 -4
- data/spec/factories/context_module_item_factory.rb +2 -2
- data/spec/factories/course_factory.rb +3 -3
- data/spec/factories/enrollment_factory.rb +1 -1
- data/spec/factories/group_factory.rb +3 -3
- data/spec/factories/group_membership_factory.rb +2 -2
- data/spec/factories/role_factory.rb +4 -4
- data/spec/factories/section_factory.rb +1 -1
- data/spec/factories/submission_factory.rb +1 -1
- data/spec/factories/term_factory.rb +5 -5
- data/spec/factories/user_factory.rb +4 -4
- data/spec/job_batching/batch_spec.rb +2 -2
- data/spec/job_batching/compat/active_job_spec.rb +1 -1
- data/spec/job_batching/compat/sidekiq_spec.rb +2 -2
- data/spec/job_batching/status_spec.rb +1 -1
- data/spec/job_uniqueness/lock_context_spec.rb +11 -0
- data/spec/spec_helper.rb +5 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cc62b25843e2b3e57b83e1ca41b4a0e546666ce6e824a8a799f534ee90fb28c5
|
4
|
+
data.tar.gz: 1fb1747c3882282894b0aa4e6fef9d7240977a453da6415e3a31460c55833306
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3ac70536bc476c02ee262487ff7074493e99c05d585cec78a32cddb5569feb9e5ca6407a7104cf7538ed9030e4cb2401be823171de5f5e261de5bd2aeafbc11f
|
7
|
+
data.tar.gz: 31178103ff1168d6a54b622c612260857c4f5841dff32c637a95877a1df3f7c4467221e05c19e7688f1ebf3284d79cc8cf5113208f89ac4299983dcaa2ccf51d
|
@@ -98,30 +98,14 @@ module CanvasSync::JobBatches
|
|
98
98
|
def jobs
|
99
99
|
raise NoBlockGivenError unless block_given?
|
100
100
|
|
101
|
-
|
102
|
-
parent_bid = Thread.current[CURRENT_BATCH_THREAD_KEY]&.bid
|
101
|
+
persist!
|
103
102
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
if parent_bid
|
109
|
-
r.hincrby("BID-#{parent_bid}", "children", 1)
|
110
|
-
r.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
|
111
|
-
r.zadd("BID-#{parent_bid}-bids", created_at, bid)
|
112
|
-
else
|
113
|
-
r.zadd("BID-ROOT-bids", created_at, bid)
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
flush_pending_attrs
|
118
|
-
@context&.save!
|
119
|
-
|
120
|
-
@initialized = true
|
121
|
-
else
|
122
|
-
assert_batch_is_open
|
123
|
-
end
|
103
|
+
# TODO This keep_open! block is probably desired, but it has some caveats:
|
104
|
+
# - Old logic didn't auto-clean empty batches
|
105
|
+
# - Could be an issue if the Thread crashes at a bad moment (do we even need to plan for this?)
|
106
|
+
# - Technically there could be a race condition without it, but such hasn't been observed
|
124
107
|
|
108
|
+
# keep_open! do
|
125
109
|
begin
|
126
110
|
parent = Thread.current[CURRENT_BATCH_THREAD_KEY]
|
127
111
|
Thread.current[CURRENT_BATCH_THREAD_KEY] = self
|
@@ -129,13 +113,16 @@ module CanvasSync::JobBatches
|
|
129
113
|
ensure
|
130
114
|
Thread.current[CURRENT_BATCH_THREAD_KEY] = parent
|
131
115
|
end
|
116
|
+
# end
|
132
117
|
|
133
118
|
nil
|
134
119
|
end
|
135
120
|
|
136
|
-
|
137
|
-
|
138
|
-
|
121
|
+
# Mark this Batch as a placeholder. It will be persisted to Redis, but no jobs will be added.
|
122
|
+
# From here, you can either use `.jobs` on the batch to add jobs as usual, or call `.let_close!` to allow cleanup to occur.
|
123
|
+
def placeholder!
|
124
|
+
# TODO Provide a stable `let_close!` token?
|
125
|
+
persist!
|
139
126
|
end
|
140
127
|
|
141
128
|
def invalidate_all
|
@@ -157,35 +144,36 @@ module CanvasSync::JobBatches
|
|
157
144
|
batch.parent ? valid && valid?(batch.parent) : valid
|
158
145
|
end
|
159
146
|
|
160
|
-
def keep_open!
|
147
|
+
def keep_open!(token = SecureRandom.urlsafe_base64(10))
|
161
148
|
if block_given?
|
162
149
|
begin
|
163
|
-
keep_open!
|
150
|
+
token = keep_open!(token)
|
164
151
|
yield
|
165
152
|
ensure
|
166
|
-
let_close!
|
153
|
+
let_close!(token)
|
167
154
|
end
|
168
155
|
else
|
169
|
-
|
170
|
-
|
171
|
-
|
156
|
+
persist!
|
157
|
+
|
158
|
+
redis.multi do |r|
|
159
|
+
r.sadd("#{@bidkey}-holds", token)
|
160
|
+
r.expire("#{@bidkey}-holds", BID_EXPIRE_TTL)
|
161
|
+
end
|
172
162
|
|
173
|
-
|
174
|
-
_, failed, pending, children, complete, success = redis.multi do |r|
|
175
|
-
r.hset(@bidkey, 'keep_open', "false")
|
163
|
+
assert_batch_is_open
|
176
164
|
|
177
|
-
|
178
|
-
r.hincrby("BID-#{bid}", "pending", 0)
|
179
|
-
r.hincrby("BID-#{bid}", "children", 0)
|
180
|
-
r.scard("BID-#{bid}-batches-complete")
|
181
|
-
r.scard("BID-#{bid}-batches-success")
|
165
|
+
token
|
182
166
|
end
|
167
|
+
end
|
183
168
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
169
|
+
def let_close!(token = :unset)
|
170
|
+
self.class.with_callback_check(bid, only: %i[complete success]) do |r|
|
171
|
+
if token == :unset # Legacy
|
172
|
+
r.del("#{@bidkey}-holds")
|
173
|
+
r.hset(@bidkey, 'keep_open', "false")
|
174
|
+
else
|
175
|
+
r.srem("#{@bidkey}-holds", token)
|
176
|
+
end
|
189
177
|
end
|
190
178
|
end
|
191
179
|
|
@@ -203,6 +191,23 @@ module CanvasSync::JobBatches
|
|
203
191
|
with_batch(nil, &blk)
|
204
192
|
end
|
205
193
|
|
194
|
+
def append_jobs(jids)
|
195
|
+
jids = Array(jids)
|
196
|
+
jids = jids.uniq
|
197
|
+
return unless jids.size > 0
|
198
|
+
|
199
|
+
redis do |r|
|
200
|
+
tme = Time.now.utc.to_f
|
201
|
+
added = r.zadd(@bidkey + "-jids", jids.map{|jid| [tme, jid] }, nx: true)
|
202
|
+
r.multi do |r|
|
203
|
+
r.hincrby(@bidkey, "pending", added)
|
204
|
+
r.hincrby(@bidkey, "job_count", added)
|
205
|
+
r.expire(@bidkey, BID_EXPIRE_TTL)
|
206
|
+
r.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
206
211
|
protected
|
207
212
|
|
208
213
|
def redis_key
|
@@ -214,28 +219,34 @@ module CanvasSync::JobBatches
|
|
214
219
|
redis.zadd("batches", created_at, bid) if INDEX_ALL_BATCHES
|
215
220
|
end
|
216
221
|
|
217
|
-
private
|
218
|
-
|
219
222
|
def assert_batch_is_open
|
220
223
|
unless defined?(@closed)
|
221
|
-
@closed = redis.hget(@bidkey, '
|
224
|
+
@closed = redis.hget(@bidkey, 'complete') == 'true'
|
222
225
|
end
|
223
226
|
raise "Cannot add jobs to Batch #{} bid - it has already entered the callback-stage" if @closed
|
224
227
|
end
|
225
228
|
|
226
|
-
def
|
227
|
-
|
228
|
-
|
229
|
+
def persist!
|
230
|
+
if !@existing && !@initialized
|
231
|
+
parent_bid = Thread.current[CURRENT_BATCH_THREAD_KEY]&.bid
|
229
232
|
|
230
|
-
|
231
|
-
|
232
|
-
added = r.zadd(@bidkey + "-jids", jids.map{|jid| [tme, jid] }, nx: true)
|
233
|
-
r.multi do |r|
|
234
|
-
r.hincrby(@bidkey, "pending", added)
|
235
|
-
r.hincrby(@bidkey, "job_count", added)
|
233
|
+
redis.multi do |r|
|
234
|
+
r.hset(@bidkey, "parent_bid", parent_bid.to_s) if parent_bid
|
236
235
|
r.expire(@bidkey, BID_EXPIRE_TTL)
|
237
|
-
|
236
|
+
|
237
|
+
if parent_bid
|
238
|
+
r.hincrby("BID-#{parent_bid}", "children", 1)
|
239
|
+
r.expire("BID-#{parent_bid}", BID_EXPIRE_TTL)
|
240
|
+
r.zadd("BID-#{parent_bid}-bids", created_at, bid)
|
241
|
+
else
|
242
|
+
r.zadd("BID-ROOT-bids", created_at, bid)
|
243
|
+
end
|
238
244
|
end
|
245
|
+
|
246
|
+
flush_pending_attrs
|
247
|
+
@context&.save!
|
248
|
+
|
249
|
+
@initialized = true
|
239
250
|
end
|
240
251
|
end
|
241
252
|
|
@@ -269,6 +280,7 @@ module CanvasSync::JobBatches
|
|
269
280
|
# Misc
|
270
281
|
r.hget("BID-#{bid}", "parent_bid")
|
271
282
|
r.hget("BID-#{bid}", "keep_open")
|
283
|
+
r.scard("BID-#{bid}-holds")
|
272
284
|
|
273
285
|
# Jobs
|
274
286
|
r.hincrby("BID-#{bid}", "pending", 0)
|
@@ -295,13 +307,18 @@ module CanvasSync::JobBatches
|
|
295
307
|
# "failed" = dead or retrying
|
296
308
|
# "complete" = successful or failed
|
297
309
|
|
298
|
-
parent_bid, keep_open, \
|
310
|
+
parent_bid, keep_open, holds, \
|
299
311
|
pending_jobs, failed_jobs, dead_jobs, \
|
300
312
|
child_batches, complete_batches, success_batches, failed_batches, stagnated_batches \
|
301
313
|
= actual_results
|
302
314
|
|
303
315
|
pending_batches = child_batches - success_batches
|
304
316
|
|
317
|
+
if keep_open == 'true' || (holds && holds > 0)
|
318
|
+
except << :complete
|
319
|
+
except << :success
|
320
|
+
end
|
321
|
+
|
305
322
|
trigger_callback = ->(callback) {
|
306
323
|
next if except.include?(callback.to_sym)
|
307
324
|
next unless only.include?(callback.to_sym)
|
@@ -360,11 +377,10 @@ module CanvasSync::JobBatches
|
|
360
377
|
batch_key = "BID-#{bid}"
|
361
378
|
callback_key = "#{batch_key}-callbacks-#{event}"
|
362
379
|
|
363
|
-
callbacks, queue, parent_bid, callback_params = redis do |r|
|
364
|
-
return unless r.exists?(batch_key)
|
365
|
-
return if r.hget(batch_key, 'keep_open') == 'true'
|
366
|
-
|
380
|
+
exists, callbacks, queue, parent_bid, callback_params = redis do |r|
|
367
381
|
r.multi do |r|
|
382
|
+
r.exists?(batch_key)
|
383
|
+
|
368
384
|
r.smembers(callback_key)
|
369
385
|
r.hget(batch_key, "callback_queue")
|
370
386
|
r.hget(batch_key, "parent_bid")
|
@@ -372,6 +388,8 @@ module CanvasSync::JobBatches
|
|
372
388
|
end
|
373
389
|
end
|
374
390
|
|
391
|
+
return unless exists
|
392
|
+
|
375
393
|
queue ||= "default"
|
376
394
|
parent_bid = !parent_bid || parent_bid.empty? ? nil : parent_bid # Basically parent_bid.blank?
|
377
395
|
|
@@ -460,20 +478,22 @@ module CanvasSync::JobBatches
|
|
460
478
|
|
461
479
|
# Internal method to cleanup a Redis Hash and related keys
|
462
480
|
def cleanup_redis_index_for(key, suffixes = [""])
|
463
|
-
|
464
|
-
r.
|
465
|
-
|
466
|
-
|
481
|
+
redis do |r|
|
482
|
+
if r.hget(k, "created_at").present?
|
483
|
+
r.multi do |r|
|
484
|
+
suffixes.each do |suffix|
|
485
|
+
r.expire(key + suffix, BID_EXPIRE_TTL)
|
486
|
+
end
|
467
487
|
end
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
488
|
+
false
|
489
|
+
else
|
490
|
+
r.multi do |r|
|
491
|
+
suffixes.each do |suffix|
|
492
|
+
r.unlink(key + suffix)
|
493
|
+
end
|
474
494
|
end
|
495
|
+
true
|
475
496
|
end
|
476
|
-
true
|
477
497
|
end
|
478
498
|
end
|
479
499
|
|
@@ -481,14 +501,16 @@ module CanvasSync::JobBatches
|
|
481
501
|
def cleanup_redis_index!
|
482
502
|
suffixes = ["", "-callbacks-complete", "-callbacks-success", "-failed", "-dead", "-batches-success", "-batches-complete", "-batches-failed", "-bids", "-jids", "-pending_callbacks"]
|
483
503
|
|
484
|
-
|
485
|
-
|
486
|
-
r.
|
487
|
-
|
488
|
-
|
504
|
+
redis do |r|
|
505
|
+
cleanup_index = ->(index) {
|
506
|
+
r.zrangebyscore(index, "0", BID_EXPIRE_TTL.seconds.ago.to_i).each do |bid|
|
507
|
+
r.zrem(index, bid) if cleanup_redis_index_for("BID-#{bid}", suffixes)
|
508
|
+
end
|
509
|
+
}
|
489
510
|
|
490
|
-
|
491
|
-
|
511
|
+
cleanup_index.("BID-ROOT-bids")
|
512
|
+
cleanup_index.("batches")
|
513
|
+
end
|
492
514
|
end
|
493
515
|
|
494
516
|
def redis(&blk)
|
@@ -45,7 +45,7 @@ module CanvasSync::JobBatches
|
|
45
45
|
|
46
46
|
def call(_worker, msg, _queue, _redis_pool = nil)
|
47
47
|
if (batch = Thread.current[CURRENT_BATCH_THREAD_KEY]) && should_handle_batch?(msg)
|
48
|
-
batch.
|
48
|
+
batch.append_jobs(msg['jid']) if (msg['bid'] = batch.bid)
|
49
49
|
end
|
50
50
|
yield
|
51
51
|
end
|
@@ -54,7 +54,7 @@ module CanvasSync::JobBatches
|
|
54
54
|
root_batch.context["managed_batch_bid"] = man_batch_id if man_batch_id
|
55
55
|
|
56
56
|
if concurrency < sub_jobs.count
|
57
|
-
root_batch.
|
57
|
+
root_batch.placeholder!
|
58
58
|
concurrency.times do
|
59
59
|
perform_next_sequence_job(man_batch_id, skip_preflight: true)
|
60
60
|
end
|
@@ -140,6 +140,13 @@ module CanvasSync::JobBatches
|
|
140
140
|
Batch.new.tap do |batch|
|
141
141
|
batch.description = "Managed Batch Fiber (#{man_batch_id})"
|
142
142
|
batch.on(:success, "#{self.to_s}.job_succeeded_callback", managed_batch_id: man_batch_id)
|
143
|
+
|
144
|
+
if next_job[:chain_link].present?
|
145
|
+
# Annotate Batch with chain-step info
|
146
|
+
batch.context["csb:chain_link"] = next_job[:chain_link]
|
147
|
+
batch.on(:complete, "#{ChainBuilder.to_s}.chain_step_complete", chain_link: next_job[:chain_link])
|
148
|
+
end
|
149
|
+
|
143
150
|
batch.jobs do
|
144
151
|
ChainBuilder.enqueue_job(next_job)
|
145
152
|
end
|
@@ -42,7 +42,7 @@ module CanvasSync::JobBatches
|
|
42
42
|
wrapper.description = "Pool Job Wrapper (PID: #{pid})"
|
43
43
|
checkin_event = (on_failed_job == :wait) ? :success : :complete
|
44
44
|
wrapper.on(checkin_event, "#{self.class.to_s}.job_checked_in", pool_id: pid)
|
45
|
-
wrapper.
|
45
|
+
wrapper.placeholder!
|
46
46
|
|
47
47
|
job_desc = job_desc.symbolize_keys
|
48
48
|
job_desc = job_desc.merge!(
|
@@ -55,21 +55,31 @@ module CanvasSync::JobBatches
|
|
55
55
|
refill_allotment unless skip_refill
|
56
56
|
end
|
57
57
|
|
58
|
-
def keep_open!
|
58
|
+
def keep_open!(token = SecureRandom.urlsafe_base64(10))
|
59
59
|
if block_given?
|
60
60
|
begin
|
61
|
-
keep_open!
|
61
|
+
token = keep_open!(token)
|
62
62
|
yield
|
63
63
|
ensure
|
64
|
-
let_close!
|
64
|
+
let_close!(token)
|
65
65
|
end
|
66
66
|
else
|
67
|
-
redis.
|
67
|
+
redis.multi do |r|
|
68
|
+
r.sadd("#{redis_key}-holds", token)
|
69
|
+
r.expire("#{redis_key}-holds", Batch::BID_EXPIRE_TTL)
|
70
|
+
end
|
71
|
+
token
|
68
72
|
end
|
69
73
|
end
|
70
74
|
|
71
|
-
def let_close!
|
72
|
-
|
75
|
+
def let_close!(token = :unset)
|
76
|
+
if token == :unset # Legacy
|
77
|
+
redis.del("#{redis_key}-holds")
|
78
|
+
redis.hset(redis_key, 'keep_open', 'false')
|
79
|
+
else
|
80
|
+
redis.srem("#{redis_key}-holds", token)
|
81
|
+
end
|
82
|
+
|
73
83
|
cleanup_if_empty
|
74
84
|
end
|
75
85
|
|
@@ -87,15 +97,16 @@ module CanvasSync::JobBatches
|
|
87
97
|
def cleanup_if_empty
|
88
98
|
self.order
|
89
99
|
|
90
|
-
activec, pactivec, pendingc, clean_when_empty, keep_open = redis.multi do |r|
|
100
|
+
activec, pactivec, pendingc, clean_when_empty, keep_open, holds = redis.multi do |r|
|
91
101
|
r.hlen("#{redis_key}-active")
|
92
102
|
r.hget(redis_key, "_active_count")
|
93
103
|
pending_count(r)
|
94
104
|
r.hget(redis_key, 'clean_when_empty')
|
95
105
|
r.hget(redis_key, 'keep_open')
|
106
|
+
r.scard("#{redis_key}-holds")
|
96
107
|
end
|
97
108
|
|
98
|
-
return if keep_open == 'true' || clean_when_empty == 'false'
|
109
|
+
return if keep_open == 'true' || clean_when_empty == 'false' || (holds && holds > 0)
|
99
110
|
|
100
111
|
if activec <= 0 && (pactivec.try(:to_i) || 0) <= 0 && pendingc <= 0
|
101
112
|
cleanup_redis
|
@@ -122,14 +122,27 @@ module CanvasSync::JobUniqueness
|
|
122
122
|
|
123
123
|
args = @context_data[:args] || []
|
124
124
|
kwargs = @context_data[:kwargs] || {}
|
125
|
+
hash = config[:hash]
|
125
126
|
if config[:hash].is_a?(Proc)
|
126
|
-
|
127
|
+
hash = config[:hash].call(*args, **kwargs)
|
127
128
|
elsif config[:hash].nil?
|
128
|
-
|
129
|
-
else
|
130
|
-
base_key << config[:hash]
|
129
|
+
hash = [*args, kwargs]
|
131
130
|
end
|
132
131
|
|
132
|
+
hash = ":#{hash}" if hash.is_a?(Symbol)
|
133
|
+
|
134
|
+
if hash && !hash.is_a?(String)
|
135
|
+
hash = Array(hash)
|
136
|
+
|
137
|
+
# Normalize the hash to ensure that the order of any Hash keys don't matter
|
138
|
+
hash = normalize_hash_chunk(hash)
|
139
|
+
|
140
|
+
normalized = ActiveJob::Arguments.serialize(hash)
|
141
|
+
hash = OpenSSL::Digest::MD5.hexdigest(JSON.dump(normalized))
|
142
|
+
end
|
143
|
+
|
144
|
+
base_key << hash if hash
|
145
|
+
|
133
146
|
base_key.join(":")
|
134
147
|
end
|
135
148
|
end
|
@@ -173,5 +186,14 @@ module CanvasSync::JobUniqueness
|
|
173
186
|
|
174
187
|
private
|
175
188
|
|
189
|
+
def normalize_hash_chunk(chunk)
|
190
|
+
if chunk.is_a?(Hash)
|
191
|
+
chunk.map { |k, v| [k, normalize_hash_chunk(v)] }.sort.to_h
|
192
|
+
elsif chunk.is_a?(Array)
|
193
|
+
chunk.map { |c| normalize_hash_chunk(c) }
|
194
|
+
else
|
195
|
+
chunk
|
196
|
+
end
|
197
|
+
end
|
176
198
|
end
|
177
199
|
end
|
data/lib/canvas_sync/version.rb
CHANGED
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe CanvasSync::Jobs::SyncAdminsJob do
|
4
4
|
describe '#perform' do
|
5
|
-
let!(:account) {
|
5
|
+
let!(:account) { FactoryBot.create(:account, canvas_id: 1) }
|
6
6
|
let(:admin_params) { open_canvas_fixture('admins') }
|
7
7
|
|
8
8
|
it 'retrieves all admins from the Canvas API and then invokes the next job' do
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe CanvasSync::Jobs::SyncProvisioningReportJob do
|
4
4
|
describe '#perform' do
|
5
5
|
context 'a term scope is specified' do
|
6
|
-
let!(:term) {
|
6
|
+
let!(:term) { FactoryBot.create(:term) }
|
7
7
|
|
8
8
|
it 'enqueues a ReportStarter for a provisioning report for the specified models for each term' do
|
9
9
|
expect_any_instance_of(CanvasSync::Jobs::ReportStarter).to receive(:start_report)
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe CanvasSync::Jobs::SyncRolesJob do
|
4
4
|
describe '#perform' do
|
5
|
-
let!(:account) {
|
5
|
+
let!(:account) { FactoryBot.create(:account, canvas_id: 1) }
|
6
6
|
let(:role_params) { open_canvas_fixture('roles') }
|
7
7
|
|
8
8
|
it 'retrieves all roles from the Canvas API and then invokes the next job' do
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
RSpec.describe CanvasSync::Jobs::SyncSimpleTableJob do
|
4
4
|
describe '#perform' do
|
5
5
|
context 'Simple report' do
|
6
|
-
let!(:term) {
|
6
|
+
let!(:term) { FactoryBot.create(:term) }
|
7
7
|
|
8
8
|
it 'enqueues a ReportStarter for a provisioning report for the specified model for a term' do
|
9
9
|
expect(CanvasSync::Jobs::ReportStarter).to receive(:perform_later)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Admin, type: :model do
|
4
|
-
let(:subject) {
|
4
|
+
let(:subject) { FactoryBot.create(:admin) }
|
5
5
|
|
6
6
|
describe 'validations' do
|
7
7
|
it { should validate_presence_of(:canvas_id) }
|
@@ -26,7 +26,7 @@ RSpec.describe Admin, type: :model do
|
|
26
26
|
end
|
27
27
|
|
28
28
|
context 'the admin already exists' do
|
29
|
-
let!(:existing_admin) {
|
29
|
+
let!(:existing_admin) { FactoryBot.create(:admin, canvas_id: admin_params['id']) }
|
30
30
|
|
31
31
|
it 'updates it' do
|
32
32
|
expect {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe AssignmentGroup, type: :model do
|
4
|
-
let(:subject) {
|
4
|
+
let(:subject) { FactoryBot.create(:assignment_group) }
|
5
5
|
|
6
6
|
describe 'validations' do
|
7
7
|
it { should validate_presence_of(:canvas_id) }
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
RSpec.describe Assignment, type: :model do
|
4
|
-
let(:subject) {
|
4
|
+
let(:subject) { FactoryBot.create(:assignment) }
|
5
5
|
|
6
6
|
describe "validations" do
|
7
7
|
it { should validate_presence_of(:canvas_id) }
|
@@ -31,8 +31,8 @@ RSpec.describe Assignment, type: :model do
|
|
31
31
|
it { should have_many(:context_modules).through(:context_module_items) }
|
32
32
|
|
33
33
|
describe "context" do
|
34
|
-
let!(:other_course) {
|
35
|
-
let!(:matching_course) {
|
34
|
+
let!(:other_course) { FactoryBot.create(:course) }
|
35
|
+
let!(:matching_course) { FactoryBot.create(:course) }
|
36
36
|
|
37
37
|
before do
|
38
38
|
subject.update(canvas_context_type: "Course", canvas_context_id: matching_course.canvas_id)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require "spec_helper"
|
2
2
|
|
3
3
|
RSpec.describe ContextModuleItem, type: :model do
|
4
|
-
let(:subject) {
|
4
|
+
let(:subject) { FactoryBot.create(:context_module_item) }
|
5
5
|
|
6
6
|
describe "associations" do
|
7
7
|
it { should belong_to(:context_module) }
|
@@ -2,7 +2,7 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
RSpec.describe GroupMembership, type: :model do
|
4
4
|
let(:subject) {
|
5
|
-
|
5
|
+
FactoryBot.create(:group_membership, group: FactoryBot.create(:group), user: FactoryBot.create(:user))
|
6
6
|
}
|
7
7
|
|
8
8
|
describe 'validations' do
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Role, type: :model do
|
4
|
-
let(:subject) {
|
4
|
+
let(:subject) { FactoryBot.create(:role) }
|
5
5
|
|
6
6
|
describe 'validations' do
|
7
7
|
it { should validate_presence_of(:canvas_id) }
|
@@ -27,7 +27,7 @@ RSpec.describe Role, type: :model do
|
|
27
27
|
end
|
28
28
|
|
29
29
|
context 'the role already exists' do
|
30
|
-
let!(:existing_role) {
|
30
|
+
let!(:existing_role) { FactoryBot.create(:role, canvas_id: role_params['id']) }
|
31
31
|
|
32
32
|
it 'updates it' do
|
33
33
|
expect {
|
@@ -1,7 +1,7 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
RSpec.describe Term, type: :model do
|
4
|
-
let(:subject) {
|
4
|
+
let(:subject) { FactoryBot.create(:term) }
|
5
5
|
|
6
6
|
describe 'validations' do
|
7
7
|
it { should validate_presence_of(:canvas_id) }
|
@@ -18,10 +18,10 @@ RSpec.describe Term, type: :model do
|
|
18
18
|
|
19
19
|
describe 'scopes' do
|
20
20
|
describe '.active' do
|
21
|
-
let!(:active_term) {
|
22
|
-
let!(:inactive_term) {
|
23
|
-
let!(:completed_term) {
|
24
|
-
let!(:unstarted_term) {
|
21
|
+
let!(:active_term) { FactoryBot.create(:term, workflow_state: 'active', start_at: 2.days.ago, end_at: 3.days.from_now) }
|
22
|
+
let!(:inactive_term) { FactoryBot.create(:term, workflow_state: 'inactive') }
|
23
|
+
let!(:completed_term) { FactoryBot.create(:term, start_at: 2.months.ago, end_at: 1.month.ago) }
|
24
|
+
let!(:unstarted_term) { FactoryBot.create(:term, start_at: 6.months.from_now, end_at: 1.year.from_now) }
|
25
25
|
|
26
26
|
it 'returns terms with an active workflow_state that have a start_at 15 days in the future or earlier and an end_at 15 days in the past or later' do
|
27
27
|
expect(Term.active).to match_array([active_term])
|
@@ -50,7 +50,7 @@ RSpec.describe Term, type: :model do
|
|
50
50
|
end
|
51
51
|
|
52
52
|
context 'the term already exists' do
|
53
|
-
let!(:existing_term) {
|
53
|
+
let!(:existing_term) { FactoryBot.create(:term, canvas_id: term_params['id']) }
|
54
54
|
|
55
55
|
it 'updates it' do
|
56
56
|
expect {
|
@@ -16,7 +16,7 @@ RSpec.describe LiveEvents::ModuleEvent do
|
|
16
16
|
|
17
17
|
describe "#perform" do
|
18
18
|
context "the module already exists" do
|
19
|
-
let!(:context_module) {
|
19
|
+
let!(:context_module) { FactoryBot.create(:context_module, canvas_id: payload["body"][:module_id]) }
|
20
20
|
|
21
21
|
xit "updates it" do
|
22
22
|
expect {
|
@@ -16,7 +16,7 @@ RSpec.describe LiveEvents::ModuleItemEvent do
|
|
16
16
|
|
17
17
|
describe "#perform" do
|
18
18
|
context "the module item already exists" do
|
19
|
-
let!(:cmi) {
|
19
|
+
let!(:cmi) { FactoryBot.create(:context_module_item, canvas_id: payload["body"][:module_item_id]) }
|
20
20
|
|
21
21
|
xit "updates it" do
|
22
22
|
expect {
|
@@ -1,10 +1,10 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :account do
|
3
3
|
canvas_id { SecureRandom.random_number(100_000_000) }
|
4
4
|
sis_id { SecureRandom.hex }
|
5
|
-
canvas_parent_account_id 1
|
5
|
+
canvas_parent_account_id { 1 }
|
6
6
|
sis_parent_account_id { SecureRandom.hex }
|
7
|
-
name "Account name"
|
8
|
-
workflow_state "active"
|
7
|
+
name { "Account name" }
|
8
|
+
workflow_state { "active" }
|
9
9
|
end
|
10
10
|
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :admin do
|
3
3
|
canvas_id { SecureRandom.random_number(100_000_000) }
|
4
|
-
role_name 'Cool Role'
|
4
|
+
role_name { 'Cool Role' }
|
5
5
|
canvas_role_id { SecureRandom.random_number(100_000_000) }
|
6
|
-
canvas_user_id 1
|
7
|
-
workflow_state 'active'
|
6
|
+
canvas_user_id { 1 }
|
7
|
+
workflow_state { 'active' }
|
8
8
|
end
|
9
9
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :assignment_group do
|
3
3
|
sequence(:canvas_id) { |n| n }
|
4
|
-
name 'Dummy Assignment Group'
|
5
|
-
workflow_state 'available'
|
4
|
+
name { 'Dummy Assignment Group' }
|
5
|
+
workflow_state { 'available' }
|
6
6
|
canvas_created_at {1.week.ago}
|
7
7
|
canvas_updated_at {1.day.ago}
|
8
8
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :context_module do
|
3
3
|
sequence(:canvas_id) { |n| n }
|
4
|
-
name "Context Module"
|
5
|
-
workflow_state "available"
|
6
|
-
context {
|
4
|
+
name { "Context Module" }
|
5
|
+
workflow_state { "available" }
|
6
|
+
context { FactoryBot.create(:course) }
|
7
7
|
end
|
8
8
|
end
|
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :course do
|
3
3
|
canvas_id { SecureRandom.random_number(100_000_000) }
|
4
4
|
sis_id { SecureRandom.hex }
|
5
|
-
course_code 'Cool Course'
|
6
|
-
name 'Really Really Cool Course'
|
5
|
+
course_code { 'Cool Course' }
|
6
|
+
name { 'Really Really Cool Course' }
|
7
7
|
start_at { 3.days.ago }
|
8
8
|
end_at { 3.weeks.from_now }
|
9
9
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :group do
|
3
3
|
canvas_id { SecureRandom.random_number(100_000_000) }
|
4
4
|
sis_id { SecureRandom.hex }
|
5
|
-
name 'Some User Group'
|
6
|
-
workflow_state 'available'
|
5
|
+
name { 'Some User Group' }
|
6
|
+
workflow_state { 'available' }
|
7
7
|
end
|
8
8
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :role do
|
3
|
-
label 'Cool Role'
|
4
|
-
base_role_type 'AccountMembership'
|
3
|
+
label { 'Cool Role' }
|
4
|
+
base_role_type { 'AccountMembership' }
|
5
5
|
canvas_id { SecureRandom.random_number(100_000_000) }
|
6
|
-
workflow_state 'active'
|
6
|
+
workflow_state { 'active' }
|
7
7
|
end
|
8
8
|
end
|
@@ -1,10 +1,10 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :term do
|
3
|
-
name 'Cool Term'
|
4
|
-
start_at 3.days.ago
|
5
|
-
end_at 3.days.from_now
|
3
|
+
name { 'Cool Term' }
|
4
|
+
start_at { 3.days.ago }
|
5
|
+
end_at { 3.days.from_now }
|
6
6
|
canvas_id { SecureRandom.random_number(100_000_000) }
|
7
|
-
workflow_state 'active'
|
7
|
+
workflow_state { 'active' }
|
8
8
|
sis_id { SecureRandom.hex }
|
9
9
|
end
|
10
10
|
end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
FactoryBot.define do
|
2
2
|
factory :user do
|
3
3
|
canvas_id { SecureRandom.random_number(100_000_000) }
|
4
|
-
email "cooldude@coolsite.com"
|
5
|
-
first_name "Cool"
|
6
|
-
last_name "Dude"
|
4
|
+
email { "cooldude@coolsite.com" }
|
5
|
+
first_name { "Cool" }
|
6
|
+
last_name { "Dude" }
|
7
7
|
end
|
8
8
|
end
|
@@ -223,7 +223,7 @@ RSpec.describe CanvasSync::JobBatches::Batch do
|
|
223
223
|
|
224
224
|
context 'complete' do
|
225
225
|
before { batch.on(:complete, Object) }
|
226
|
-
# before { batch.
|
226
|
+
# before { batch.append_jobs(bid) }
|
227
227
|
# before { batch.jobs do BatchTestWorker.perform_async end }
|
228
228
|
# before { CanvasSync::JobBatches::Batch.process_failed_job(bid, 'failed-job-id') }
|
229
229
|
|
@@ -341,7 +341,7 @@ RSpec.describe CanvasSync::JobBatches::Batch do
|
|
341
341
|
end
|
342
342
|
end
|
343
343
|
|
344
|
-
describe '#
|
344
|
+
describe '#append_jobs' do
|
345
345
|
let(:batch) { CanvasSync::JobBatches::Batch.new }
|
346
346
|
|
347
347
|
it 'increments pending' do
|
@@ -57,7 +57,7 @@ RSpec.describe CanvasSync::JobBatches::Compat::ActiveJob do
|
|
57
57
|
context "When Enqueueing" do
|
58
58
|
context 'when without batch' do
|
59
59
|
it 'just yields' do
|
60
|
-
expect(CanvasSync::JobBatches::Batch).not_to receive(:
|
60
|
+
expect(CanvasSync::JobBatches::Batch).not_to receive(:append_jobs)
|
61
61
|
BatchTestJobBase.perform_later
|
62
62
|
expect(BatchTestJobBase).to have_been_enqueued
|
63
63
|
end
|
@@ -48,7 +48,7 @@ RSpec.describe CanvasSync::JobBatches::Compat::Sidekiq do
|
|
48
48
|
context 'when without batch' do
|
49
49
|
it 'just yields' do
|
50
50
|
yielded = false
|
51
|
-
expect(CanvasSync::JobBatches::Batch).not_to receive(:
|
51
|
+
expect(CanvasSync::JobBatches::Batch).not_to receive(:append_jobs)
|
52
52
|
subject.call(nil, {}, nil) { yielded = true }
|
53
53
|
expect(yielded).to be_truthy
|
54
54
|
end
|
@@ -67,7 +67,7 @@ RSpec.describe CanvasSync::JobBatches::Compat::Sidekiq do
|
|
67
67
|
end
|
68
68
|
|
69
69
|
it 'increments job queue' do
|
70
|
-
# expect(CanvasSync::JobBatches::Batch).to receive(:
|
70
|
+
# expect(CanvasSync::JobBatches::Batch).to receive(:append_jobs).with(bid)
|
71
71
|
# subject.call(nil, { 'jid' => jid }, nil) {}
|
72
72
|
end
|
73
73
|
|
@@ -34,7 +34,7 @@ RSpec.describe CanvasSync::JobBatches::Batch::Status do
|
|
34
34
|
end
|
35
35
|
|
36
36
|
context 'when more than 0' do
|
37
|
-
before { batch.
|
37
|
+
before { batch.append_jobs(bid) }
|
38
38
|
before { CanvasSync::JobBatches::Batch.process_failed_job(bid, 'FAILEDID') }
|
39
39
|
|
40
40
|
it 'returns failed jobs' do
|
@@ -24,6 +24,17 @@ RSpec.describe CanvasSync::JobUniqueness::LockContext do
|
|
24
24
|
TestWorker.unique_job_options[:scope] = scope
|
25
25
|
end
|
26
26
|
|
27
|
+
it 'returns matching keys for equal hashes' do
|
28
|
+
context_data[:args] = [{ foo: 'bar', bar: 'foo' }]
|
29
|
+
key1 = lock_context.base_key
|
30
|
+
lock_context.instance_variable_set(:@base_key, nil)
|
31
|
+
|
32
|
+
context_data[:args] = [{ bar: 'foo', foo: 'bar' }]
|
33
|
+
key2 = lock_context.base_key
|
34
|
+
|
35
|
+
expect(key1).to eq key2
|
36
|
+
end
|
37
|
+
|
27
38
|
context 'when scope is a Proc' do
|
28
39
|
let(:scope) { ->(queue:) { "blob" } }
|
29
40
|
|
data/spec/spec_helper.rb
CHANGED
@@ -7,7 +7,7 @@ require File.expand_path("../dummy/config/environment.rb", __FILE__)
|
|
7
7
|
require "bundler/setup"
|
8
8
|
require 'rspec/rails'
|
9
9
|
require 'spec_helper'
|
10
|
-
require '
|
10
|
+
require 'factory_bot_rails'
|
11
11
|
require 'timecop'
|
12
12
|
require 'webmock/rspec'
|
13
13
|
require 'support/fake_canvas'
|
@@ -16,6 +16,9 @@ require 'pry'
|
|
16
16
|
require 'pry-nav'
|
17
17
|
require 'with_model'
|
18
18
|
|
19
|
+
require 'redis'
|
20
|
+
Redis.silence_deprecations = true
|
21
|
+
|
19
22
|
require 'sidekiq/testing'
|
20
23
|
Sidekiq::Testing.fake!
|
21
24
|
Sidekiq::Testing.server_middleware do |chain|
|
@@ -29,7 +32,7 @@ ActiveRecord::Migration.maintain_test_schema!
|
|
29
32
|
RSpec.configure do |config|
|
30
33
|
config.extend WithModel
|
31
34
|
|
32
|
-
config.include
|
35
|
+
config.include FactoryBot::Syntax::Methods
|
33
36
|
config.use_transactional_fixtures = true
|
34
37
|
config.infer_spec_type_from_file_location!
|
35
38
|
config.filter_rails_from_backtrace!
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: canvas_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.22.
|
4
|
+
version: 0.22.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Instructure CustomDev
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-08-
|
11
|
+
date: 2024-08-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -95,7 +95,7 @@ dependencies:
|
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '0'
|
97
97
|
- !ruby/object:Gem::Dependency
|
98
|
-
name:
|
98
|
+
name: factory_bot_rails
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
100
100
|
requirements:
|
101
101
|
- - ">="
|