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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/lib/canvas_sync/job_batches/batch.rb +102 -80
  3. data/lib/canvas_sync/job_batches/compat/active_job.rb +1 -1
  4. data/lib/canvas_sync/job_batches/compat/sidekiq.rb +1 -1
  5. data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +8 -1
  6. data/lib/canvas_sync/job_batches/pool.rb +20 -9
  7. data/lib/canvas_sync/job_uniqueness/lock_context.rb +26 -4
  8. data/lib/canvas_sync/version.rb +1 -1
  9. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +1 -1
  10. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +1 -1
  11. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +1 -1
  12. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +1 -1
  13. data/spec/canvas_sync/models/accounts_spec.rb +1 -1
  14. data/spec/canvas_sync/models/admins_spec.rb +2 -2
  15. data/spec/canvas_sync/models/assignment_group_spec.rb +1 -1
  16. data/spec/canvas_sync/models/assignment_spec.rb +3 -3
  17. data/spec/canvas_sync/models/context_module_item_spec.rb +1 -1
  18. data/spec/canvas_sync/models/context_module_spec.rb +1 -1
  19. data/spec/canvas_sync/models/course_spec.rb +1 -1
  20. data/spec/canvas_sync/models/enrollment_spec.rb +1 -1
  21. data/spec/canvas_sync/models/group_membership_spec.rb +1 -1
  22. data/spec/canvas_sync/models/group_spec.rb +1 -1
  23. data/spec/canvas_sync/models/roles_spec.rb +2 -2
  24. data/spec/canvas_sync/models/section_spec.rb +1 -1
  25. data/spec/canvas_sync/models/submission_spec.rb +1 -1
  26. data/spec/canvas_sync/models/term_spec.rb +6 -6
  27. data/spec/canvas_sync/models/user_spec.rb +1 -1
  28. data/spec/canvas_sync/services/module_event_spec.rb +1 -1
  29. data/spec/canvas_sync/services/module_item_event_spec.rb +1 -1
  30. data/spec/factories/account_factory.rb +4 -4
  31. data/spec/factories/admin_factory.rb +4 -4
  32. data/spec/factories/assignment_factory.rb +1 -1
  33. data/spec/factories/assignment_group_factory.rb +3 -3
  34. data/spec/factories/context_module_factory.rb +4 -4
  35. data/spec/factories/context_module_item_factory.rb +2 -2
  36. data/spec/factories/course_factory.rb +3 -3
  37. data/spec/factories/enrollment_factory.rb +1 -1
  38. data/spec/factories/group_factory.rb +3 -3
  39. data/spec/factories/group_membership_factory.rb +2 -2
  40. data/spec/factories/role_factory.rb +4 -4
  41. data/spec/factories/section_factory.rb +1 -1
  42. data/spec/factories/submission_factory.rb +1 -1
  43. data/spec/factories/term_factory.rb +5 -5
  44. data/spec/factories/user_factory.rb +4 -4
  45. data/spec/job_batching/batch_spec.rb +2 -2
  46. data/spec/job_batching/compat/active_job_spec.rb +1 -1
  47. data/spec/job_batching/compat/sidekiq_spec.rb +2 -2
  48. data/spec/job_batching/status_spec.rb +1 -1
  49. data/spec/job_uniqueness/lock_context_spec.rb +11 -0
  50. data/spec/spec_helper.rb +5 -2
  51. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d55ef789158e71984651c554186f25c0206632712483cb1f196f0f772183641f
4
- data.tar.gz: e88f323145871f95d6979bd4c089fd9c993a5118e2075feb0e96257b96dad8ed
3
+ metadata.gz: cc62b25843e2b3e57b83e1ca41b4a0e546666ce6e824a8a799f534ee90fb28c5
4
+ data.tar.gz: 1fb1747c3882282894b0aa4e6fef9d7240977a453da6415e3a31460c55833306
5
5
  SHA512:
6
- metadata.gz: 34e5d02b710c1252657dd7d675b85d085a69bc6c03b0e52285f7264f5c7f5119113c6164bb148979eabf8a1c24be6f1f781ff28b61f9faf0aed331321369e598
7
- data.tar.gz: 74f7b996899e7e4d45f74f04aabf2d098f0531c0bbae5594cfbc604661d0762aa765a9ea4d94358ebc40cbbfaeef1dfb5219c4ab8eb2be50cd3044290b7139e9
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
- if !@existing && !@initialized
102
- parent_bid = Thread.current[CURRENT_BATCH_THREAD_KEY]&.bid
101
+ persist!
103
102
 
104
- redis.multi do |r|
105
- r.hset(@bidkey, "parent_bid", parent_bid.to_s) if parent_bid
106
- r.expire(@bidkey, BID_EXPIRE_TTL)
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
- def increment_job_queue(jid)
137
- assert_batch_is_open
138
- append_jobs([jid])
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
- redis.hset(@bidkey, 'keep_open', "true")
170
- end
171
- end
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
- def let_close!
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
- r.scard("BID-#{bid}-failed")
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
- all_success = pending.to_i.zero? && children == success
185
- # if complete or successfull call complete callback (the complete callback may then call successful)
186
- if (pending.to_i == failed.to_i && children == complete) || all_success
187
- self.class.enqueue_callbacks(:complete, bid)
188
- self.class.enqueue_callbacks(:success, bid) if all_success
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, 'success') == 'true'
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 append_jobs(jids)
227
- jids = jids.uniq
228
- return unless jids.size > 0
229
+ def persist!
230
+ if !@existing && !@initialized
231
+ parent_bid = Thread.current[CURRENT_BATCH_THREAD_KEY]&.bid
229
232
 
230
- redis do |r|
231
- tme = Time.now.utc.to_f
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
- r.expire(@bidkey + "-jids", BID_EXPIRE_TTL)
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
- if r.hget(k, "created_at").present?
464
- r.multi do |r|
465
- suffixes.each do |suffix|
466
- r.expire(key + suffix, BID_EXPIRE_TTL)
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
- end
469
- false
470
- else
471
- r.multi do |r|
472
- suffixes.each do |suffix|
473
- r.unlink(key + suffix)
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
- cleanup_index = ->(index) {
485
- r.zrangebyscore(index, "0", BID_EXPIRE_TTL.seconds.ago.to_i).each do |bid|
486
- r.zrem(index, bid) if cleanup_redis_index_for("BID-#{bid}", suffixes)
487
- end
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
- cleanup_index.call("BID-ROOT-bids")
491
- cleanup_index.call("batches")
511
+ cleanup_index.("BID-ROOT-bids")
512
+ cleanup_index.("batches")
513
+ end
492
514
  end
493
515
 
494
516
  def redis(&blk)
@@ -28,7 +28,7 @@ module CanvasSync::JobBatches
28
28
  around_enqueue do |job, block|
29
29
  if (batch = Thread.current[CURRENT_BATCH_THREAD_KEY])
30
30
  @bid = batch.bid
31
- batch.increment_job_queue(job_id) if @bid
31
+ batch.append_jobs(job_id) if @bid
32
32
  end
33
33
  block.call
34
34
  end
@@ -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.increment_job_queue(msg['jid']) if (msg['bid'] = batch.bid)
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.jobs {}
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.jobs {}
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.hset(redis_key, 'keep_open', 'true')
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
- redis.hset(redis_key, 'keep_open', 'false')
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
- base_key << config[:hash].call(*args, **kwargs)
127
+ hash = config[:hash].call(*args, **kwargs)
127
128
  elsif config[:hash].nil?
128
- base_key << OpenSSL::Digest::MD5.hexdigest(JSON.dump([ args, kwargs.sort]))
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
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.22.1".freeze
2
+ VERSION = "0.22.3".freeze
3
3
  end
@@ -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) { FactoryGirl.create(:account, canvas_id: 1) }
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) { FactoryGirl.create(: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) { FactoryGirl.create(:account, canvas_id: 1) }
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) { FactoryGirl.create(: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 Account, type: :model do
4
- let(:subject) { FactoryGirl.create(:account) }
4
+ let(:subject) { FactoryBot.create(:account) }
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 Admin, type: :model do
4
- let(:subject) { FactoryGirl.create(:admin) }
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) { FactoryGirl.create(:admin, canvas_id: admin_params['id']) }
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) { FactoryGirl.create(:assignment_group) }
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) { FactoryGirl.create(:assignment) }
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) { FactoryGirl.create(:course) }
35
- let!(:matching_course) { FactoryGirl.create(: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) { FactoryGirl.create(:context_module_item) }
4
+ let(:subject) { FactoryBot.create(:context_module_item) }
5
5
 
6
6
  describe "associations" do
7
7
  it { should belong_to(:context_module) }
@@ -1,7 +1,7 @@
1
1
  require "spec_helper"
2
2
 
3
3
  RSpec.describe ContextModule, type: :model do
4
- let(:subject) { FactoryGirl.create(:context_module) }
4
+ let(:subject) { FactoryBot.create(:context_module) }
5
5
 
6
6
  describe "associations" do
7
7
  it do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe Course, type: :model do
4
- let(:subject) { FactoryGirl.create(:course) }
4
+ let(:subject) { FactoryBot.create(:course) }
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 Enrollment, type: :model do
4
- let(:subject) { FactoryGirl.create(:enrollment) }
4
+ let(:subject) { FactoryBot.create(:enrollment) }
5
5
 
6
6
  describe 'validations' do
7
7
  it { should validate_presence_of(:canvas_id) }
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  RSpec.describe GroupMembership, type: :model do
4
4
  let(:subject) {
5
- FactoryGirl.create(:group_membership, group: FactoryGirl.create(:group), user: FactoryGirl.create(:user))
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 Group, type: :model do
4
- let(:subject) { FactoryGirl.create(:group) }
4
+ let(:subject) { FactoryBot.create(: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 Role, type: :model do
4
- let(:subject) { FactoryGirl.create(:role) }
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) { FactoryGirl.create(:role, canvas_id: role_params['id']) }
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 Section, type: :model do
4
- let(:subject) { FactoryGirl.create(:section) }
4
+ let(:subject) { FactoryBot.create(:section) }
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 Submission, type: :model do
4
- let(:subject) { FactoryGirl.create(:submission) }
4
+ let(:subject) { FactoryBot.create(:submission) }
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 Term, type: :model do
4
- let(:subject) { FactoryGirl.create(:term) }
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) { FactoryGirl.create(:term, workflow_state: 'active', start_at: 2.days.ago, end_at: 3.days.from_now) }
22
- let!(:inactive_term) { FactoryGirl.create(:term, workflow_state: 'inactive') }
23
- let!(:completed_term) { FactoryGirl.create(:term, start_at: 2.months.ago, end_at: 1.month.ago) }
24
- let!(:unstarted_term) { FactoryGirl.create(:term, start_at: 6.months.from_now, end_at: 1.year.from_now) }
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) { FactoryGirl.create(:term, canvas_id: term_params['id']) }
53
+ let!(:existing_term) { FactoryBot.create(:term, canvas_id: term_params['id']) }
54
54
 
55
55
  it 'updates it' do
56
56
  expect {
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  RSpec.describe User, type: :model do
4
- let(:subject) { FactoryGirl.create(:user) }
4
+ let(:subject) { FactoryBot.create(:user) }
5
5
 
6
6
  describe 'validations' do
7
7
  it { should validate_presence_of(:canvas_id) }
@@ -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) { FactoryGirl.create(:context_module, canvas_id: payload["body"][:module_id]) }
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) { FactoryGirl.create(:context_module_item, canvas_id: payload["body"][:module_item_id]) }
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
- FactoryGirl.define do
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
- FactoryGirl.define do
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,4 +1,4 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  factory :assignment do
3
3
  sequence(:canvas_id) { |n| n }
4
4
  sequence(:title) { |n| "Assignment #{n}" }
@@ -1,8 +1,8 @@
1
- FactoryGirl.define do
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
- FactoryGirl.define do
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 { FactoryGirl.create(:course) }
4
+ name { "Context Module" }
5
+ workflow_state { "available" }
6
+ context { FactoryBot.create(:course) }
7
7
  end
8
8
  end
@@ -1,7 +1,7 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  factory :context_module_item do
3
3
  sequence(:canvas_id) { |n| n }
4
4
  assignment
5
- workflow_state "active"
5
+ workflow_state { "active" }
6
6
  end
7
7
  end
@@ -1,9 +1,9 @@
1
- FactoryGirl.define do
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,4 +1,4 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  factory :enrollment do
3
3
  canvas_id { SecureRandom.random_number(100_000_000) }
4
4
  end
@@ -1,8 +1,8 @@
1
- FactoryGirl.define do
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,6 +1,6 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  factory :group_membership do
3
3
  canvas_id { SecureRandom.random_number(100_000_000) }
4
- workflow_state 'accepted'
4
+ workflow_state { 'accepted' }
5
5
  end
6
6
  end
@@ -1,8 +1,8 @@
1
- FactoryGirl.define do
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,4 +1,4 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  factory :section do
3
3
  canvas_id { SecureRandom.random_number(100_000_000) }
4
4
  end
@@ -1,4 +1,4 @@
1
- FactoryGirl.define do
1
+ FactoryBot.define do
2
2
  factory :submission do
3
3
  sequence(:canvas_id) { |n| n }
4
4
  submitted_at { 1.week.ago }
@@ -1,10 +1,10 @@
1
- FactoryGirl.define do
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
- FactoryGirl.define do
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.increment_job_queue(bid) }
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 '#increment_job_queue' do
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(:increment_job_queue)
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(:increment_job_queue)
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(:increment_job_queue).with(bid)
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.increment_job_queue(bid) }
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 'factory_girl_rails'
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 FactoryGirl::Syntax::Methods
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.1
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-12 00:00:00.000000000 Z
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: factory_girl_rails
98
+ name: factory_bot_rails
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="