canvas_sync 0.21.1 → 0.22.0.beta1

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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/lib/canvas_sync/concerns/auto_relations.rb +11 -0
  3. data/lib/canvas_sync/config.rb +3 -5
  4. data/lib/canvas_sync/generators/templates/models/rubric.rb +2 -1
  5. data/lib/canvas_sync/job_batches/batch.rb +432 -402
  6. data/lib/canvas_sync/job_batches/callback.rb +100 -114
  7. data/lib/canvas_sync/job_batches/chain_builder.rb +194 -196
  8. data/lib/canvas_sync/job_batches/{active_job.rb → compat/active_job.rb} +2 -2
  9. data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/helpers.rb +1 -1
  10. data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web.rb +3 -3
  11. data/lib/canvas_sync/job_batches/{sidekiq.rb → compat/sidekiq.rb} +35 -22
  12. data/lib/canvas_sync/job_batches/compat.rb +20 -0
  13. data/lib/canvas_sync/job_batches/context_hash.rb +124 -126
  14. data/lib/canvas_sync/job_batches/jobs/base_job.rb +2 -4
  15. data/lib/canvas_sync/job_batches/jobs/concurrent_batch_job.rb +14 -16
  16. data/lib/canvas_sync/job_batches/jobs/managed_batch_job.rb +125 -127
  17. data/lib/canvas_sync/job_batches/jobs/serial_batch_job.rb +14 -16
  18. data/lib/canvas_sync/job_batches/pool.rb +193 -195
  19. data/lib/canvas_sync/job_batches/redis_model.rb +50 -52
  20. data/lib/canvas_sync/job_batches/redis_script.rb +129 -131
  21. data/lib/canvas_sync/job_batches/status.rb +85 -87
  22. data/lib/canvas_sync/job_uniqueness/compat/active_job.rb +75 -0
  23. data/lib/canvas_sync/job_uniqueness/compat/sidekiq.rb +135 -0
  24. data/lib/canvas_sync/job_uniqueness/compat.rb +20 -0
  25. data/lib/canvas_sync/job_uniqueness/configuration.rb +25 -0
  26. data/lib/canvas_sync/job_uniqueness/job_uniqueness.rb +47 -0
  27. data/lib/canvas_sync/job_uniqueness/lock_context.rb +171 -0
  28. data/lib/canvas_sync/job_uniqueness/locksmith.rb +92 -0
  29. data/lib/canvas_sync/job_uniqueness/on_conflict/base.rb +32 -0
  30. data/lib/canvas_sync/job_uniqueness/on_conflict/log.rb +13 -0
  31. data/lib/canvas_sync/job_uniqueness/on_conflict/null_strategy.rb +9 -0
  32. data/lib/canvas_sync/job_uniqueness/on_conflict/raise.rb +11 -0
  33. data/lib/canvas_sync/job_uniqueness/on_conflict/reject.rb +21 -0
  34. data/lib/canvas_sync/job_uniqueness/on_conflict/reschedule.rb +20 -0
  35. data/lib/canvas_sync/job_uniqueness/on_conflict.rb +41 -0
  36. data/lib/canvas_sync/job_uniqueness/strategy/base.rb +104 -0
  37. data/lib/canvas_sync/job_uniqueness/strategy/until_and_while_executing.rb +35 -0
  38. data/lib/canvas_sync/job_uniqueness/strategy/until_executed.rb +20 -0
  39. data/lib/canvas_sync/job_uniqueness/strategy/until_executing.rb +20 -0
  40. data/lib/canvas_sync/job_uniqueness/strategy/until_expired.rb +16 -0
  41. data/lib/canvas_sync/job_uniqueness/strategy/while_executing.rb +26 -0
  42. data/lib/canvas_sync/job_uniqueness/strategy.rb +27 -0
  43. data/lib/canvas_sync/job_uniqueness/unique_job_common.rb +79 -0
  44. data/lib/canvas_sync/misc_helper.rb +1 -1
  45. data/lib/canvas_sync/version.rb +1 -1
  46. data/lib/canvas_sync.rb +4 -3
  47. data/spec/dummy/app/models/rubric.rb +2 -1
  48. data/spec/dummy/config/environments/test.rb +1 -1
  49. data/spec/job_batching/batch_spec.rb +49 -7
  50. data/spec/job_batching/{active_job_spec.rb → compat/active_job_spec.rb} +2 -2
  51. data/spec/job_batching/{sidekiq_spec.rb → compat/sidekiq_spec.rb} +14 -12
  52. data/spec/job_batching/flow_spec.rb +1 -1
  53. data/spec/job_batching/integration_helper.rb +1 -1
  54. data/spec/job_batching/status_spec.rb +2 -2
  55. data/spec/job_uniqueness/compat/active_job_spec.rb +49 -0
  56. data/spec/job_uniqueness/compat/sidekiq_spec.rb +68 -0
  57. data/spec/job_uniqueness/lock_context_spec.rb +95 -0
  58. data/spec/job_uniqueness/on_conflict/log_spec.rb +11 -0
  59. data/spec/job_uniqueness/on_conflict/raise_spec.rb +10 -0
  60. data/spec/job_uniqueness/on_conflict/reschedule_spec.rb +24 -0
  61. data/spec/job_uniqueness/on_conflict_spec.rb +16 -0
  62. data/spec/job_uniqueness/spec_helper.rb +14 -0
  63. data/spec/job_uniqueness/strategy/base_spec.rb +100 -0
  64. data/spec/job_uniqueness/strategy/until_and_while_executing_spec.rb +48 -0
  65. data/spec/job_uniqueness/strategy/until_executed_spec.rb +23 -0
  66. data/spec/job_uniqueness/strategy/until_executing_spec.rb +23 -0
  67. data/spec/job_uniqueness/strategy/until_expired_spec.rb +23 -0
  68. data/spec/job_uniqueness/strategy/while_executing_spec.rb +33 -0
  69. data/spec/job_uniqueness/support/lock_strategy.rb +28 -0
  70. data/spec/job_uniqueness/support/on_conflict.rb +24 -0
  71. data/spec/job_uniqueness/support/test_worker.rb +19 -0
  72. data/spec/job_uniqueness/unique_job_common_spec.rb +45 -0
  73. data/spec/spec_helper.rb +1 -1
  74. metadata +278 -204
  75. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/batches_assets/css/styles.less +0 -0
  76. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/batches_assets/js/batch_tree.js +0 -0
  77. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/batches_assets/js/util.js +0 -0
  78. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_batch_tree.erb +0 -0
  79. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_batches_table.erb +0 -0
  80. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_common.erb +0 -0
  81. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_jobs_table.erb +0 -0
  82. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/_pagination.erb +0 -0
  83. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/batch.erb +0 -0
  84. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/batches.erb +0 -0
  85. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/pool.erb +0 -0
  86. /data/lib/canvas_sync/job_batches/{sidekiq → compat/sidekiq}/web/views/pools.erb +0 -0
@@ -0,0 +1,104 @@
1
+ module CanvasSync::JobUniqueness
2
+ module Strategy
3
+ class Base
4
+ attr_reader :lock_context
5
+
6
+ def initialize(lock_context)
7
+ @lock_context = lock_context
8
+ @conflict_strategies = {}
9
+ end
10
+
11
+ class_attribute :_locks_on, instance_writer: false
12
+
13
+ def self.locks_on(*origins)
14
+ if origins.present?
15
+ orgins = Array(origins).map(&:to_sym)
16
+ self._locks_on = origins
17
+ else
18
+ self._locks_on || [:enqueue, :perform]
19
+ end
20
+ end
21
+
22
+ def on_enqueue; end
23
+ def on_perform; end
24
+
25
+ def batch_callback(event, batch_status)
26
+ if event == :success
27
+ unlock
28
+ else
29
+ unlock_cond = lock_context.config[:unlock_on_failure]
30
+
31
+ if (event == :complete && unlock_cond == :any) || (event == :death && unlock_cond == :death) || (event == :stagnated && unlock_cond == :stagnant)
32
+ unlock
33
+ end
34
+ end
35
+ end
36
+
37
+ delegate :locked?, to: :locksmith
38
+
39
+ protected
40
+
41
+ def key
42
+ lock_context.base_key
43
+ end
44
+
45
+ def wrap_in_batch(&blk)
46
+ if Thread.current[:unique_jobs_previous_jid] # Ensure we don't re-wrap in a batch when rescheduling
47
+ return blk.call
48
+ end
49
+
50
+ batch = CanvasSync::JobBatches::Batch.new
51
+ batch.context = {
52
+ uniqueness_lock_key: key,
53
+ }
54
+
55
+ CanvasSync::JobBatches::Batch::Callback::VALID_CALLBACKS.each do |callback|
56
+ callback = callback.to_sym
57
+ batch.on(callback, self.class.to_s + ".internal_batch_callback", {
58
+ event: callback,
59
+ lock_strategy: self.class.to_s,
60
+ lock_key: key,
61
+ lock_context: lock_context.serialize,
62
+ })
63
+ end
64
+
65
+ batch.jobs do
66
+ return blk.call
67
+ end
68
+ end
69
+
70
+ def self.internal_batch_callback(batch_status, opts)
71
+ strategy_class = opts[:lock_strategy].constantize
72
+ lock_context = LockContext.from_serialized(opts[:lock_context])
73
+ strategy = strategy_class.new(lock_context)
74
+ # TODO Should this route through LockContext#handle_lifecycle!?
75
+ strategy.batch_callback(opts[:event].to_sym, batch_status)
76
+ end
77
+
78
+ def lock!(purpose, wait: nil)
79
+ locked = nil
80
+ if purpose == :enqueue
81
+ if Thread.current[:unique_jobs_previous_jid].present?
82
+ locked = locksmith.swap_locks(Thread.current[:unique_jobs_previous_jid])
83
+ else
84
+ locked = locksmith.lock()
85
+ end
86
+ elsif purpose == :perform
87
+ locked = locksmith.execute { lock_context.job_id }
88
+ end
89
+
90
+ CanvasSync::JobUniqueness.logger.debug { "Requested lock of #{key} for #{purpose} - (#{locked || 'Not Obtained!'})" }
91
+
92
+ raise CouldNotLockError.new(lock_context, source: purpose) if !locked
93
+ end
94
+
95
+ def unlock()
96
+ locksmith.unlock
97
+ end
98
+
99
+ def locksmith
100
+ @locksmith ||= Locksmith.new(key, lock_context)
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,35 @@
1
+ module CanvasSync::JobUniqueness
2
+ module Strategy
3
+ # Implements two locks - one while enqueued and one while performing
4
+ class UntilAndWhileExecuting < Base
5
+ locks_on :enqueue, :perform
6
+
7
+ def on_enqueue
8
+ # Obtain lock
9
+ lock!(:enqueue)
10
+
11
+ # Proceed with enqueuing the job, wrapping it in a batch
12
+ runtime_lock.on_enqueue do
13
+ yield
14
+ end
15
+ end
16
+
17
+ def on_perform
18
+ # Obtain Runtime lock
19
+ runtime_lock.on_perform do
20
+ # Release Queue lock
21
+ unlock()
22
+
23
+ # Run the job
24
+ yield
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def runtime_lock
31
+ @runtime_lock ||= Strategy::WhileExecuting.new(lock_context)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,20 @@
1
+ module CanvasSync::JobUniqueness
2
+ module Strategy
3
+ class UntilExecuted < Base
4
+ locks_on :enqueue, :perform
5
+
6
+ def on_enqueue
7
+ lock!(:enqueue)
8
+
9
+ wrap_in_batch do
10
+ yield
11
+ end
12
+ end
13
+
14
+ def on_perform
15
+ lock!(:perform)
16
+ yield
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ module CanvasSync::JobUniqueness
2
+ module Strategy
3
+ class UntilExecuting < Base
4
+ locks_on :enqueue
5
+
6
+ def on_enqueue
7
+ lock!(:enqueue)
8
+ yield
9
+ end
10
+
11
+ def on_perform
12
+ unlock
13
+ yield
14
+ end
15
+
16
+ # TODO Define behavior when an error occurs during perform().
17
+ # SUJ's behavior is to relock, but this has some edge-cases (like how do we handle if another job already took the lock?)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,16 @@
1
+ module CanvasSync::JobUniqueness
2
+ module Strategy
3
+ class UntilExpired < UntilExecuted
4
+ locks_on :enqueue, :perform
5
+
6
+ def on_enqueue
7
+ lock!(:enqueue)
8
+ yield
9
+ end
10
+
11
+ def on_perform
12
+ lock!(:perform)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,26 @@
1
+ module CanvasSync::JobUniqueness
2
+ module Strategy
3
+ class WhileExecuting < Base
4
+ locks_on :perform
5
+
6
+ RUN_SUFFIX = ":RUN"
7
+
8
+ def on_enqueue
9
+ wrap_in_batch do
10
+ yield
11
+ end
12
+ end
13
+
14
+ def on_perform
15
+ lock!(:perform)
16
+ yield
17
+ end
18
+
19
+ protected
20
+
21
+ def key
22
+ super + RUN_SUFFIX
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,27 @@
1
+ module CanvasSync::JobUniqueness
2
+ module Strategy
3
+ extend ActiveSupport::Autoload
4
+
5
+ autoload :Base
6
+
7
+ autoload :UntilExpired
8
+ autoload :UntilExecuted
9
+ autoload :UntilExecuting
10
+ autoload :UntilAndWhileExecuting
11
+ autoload :WhileExecuting
12
+
13
+ class << self
14
+ def lookup(strategy)
15
+ matching_strategy(strategy.to_s.camelize) ||
16
+ CanvasSync::JobUniqueness.config.lock_strategies[strategy] ||
17
+ raise(ArgumentError, "strategy: #{strategy} is not found. Is it declared in the configuration?")
18
+ end
19
+
20
+ private
21
+
22
+ def matching_strategy(const)
23
+ const_get(const, false) if const_defined?(const, false)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,79 @@
1
+
2
+ module CanvasSync::JobUniqueness
3
+ module UniqueJobCommon
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ class_attribute :unique_job_options, instance_writer: false
8
+ end
9
+
10
+ class_methods do
11
+ # ensure_uniqueness(
12
+ # strategy: :until_executed, # :until_executed, :until_executing, :until_expired, :until_and_while_executing, :while_executing
13
+ # on_conflict: :raise, # :raise, :replace, :log, :reject, :reschedule, { enqueue: ..., perform: ... }, proc
14
+ # lock_ttl: 7.days, # seconds
15
+ # lock_timeout: 0, # seconds
16
+
17
+ # scope: :per_queue, # :global, :per_queue, string ("<class>-<queue>"), proc
18
+ # hash: ->{ { ... } },
19
+
20
+ # # In the case of UntilExecuted and WhileExecuting, how should the execution lock be released in an error condition?
21
+ # # :any - Release the lock when the Job's (implicit) Batch is :complete
22
+ # # :death - Release the lock when the Job's Batch receives the :death callback
23
+ # # :stagnant - Release the lock when the Job's Batch receives the :stagnant callback
24
+ # # :expire - Do not release the lock until it expires
25
+ # unlock_on_failure: :stagnant,
26
+ # )
27
+ def ensure_uniqueness(**kwargs)
28
+ if self.unique_job_options.present?
29
+ raise ArgumentError, "ensure_uniqueness can only be called once per job class"
30
+ end
31
+
32
+ begin
33
+ require "sidekiq_unique_jobs"
34
+ rescue LoadError
35
+ raise LoadError, "SidekiqUniqueJobs is required for ensure_uniqueness"
36
+ end
37
+
38
+ OnConflict.validate!(kwargs[:on_conflict], kwargs[:strategy]) if kwargs[:on_conflict].present?
39
+
40
+ kwargs[:scope] ||= :per_queue
41
+ kwargs[:ttl] ||= 30.days.to_i
42
+ kwargs[:timeout] ||= 0
43
+ kwargs[:limit] ||= 1
44
+
45
+ self.unique_job_options = kwargs
46
+
47
+ include UniqueJobMethods
48
+ end
49
+ end
50
+
51
+ module UniqueJobMethods
52
+ extend ActiveSupport::Concern
53
+
54
+ class_methods do
55
+ def unlock!(jid = nil, args: nil, kwargs: nil, queue: nil)
56
+ queue = self.try(:default_queue_name) || try(:queue)
57
+ raise ArgumentError, "Must specify queue:" unless queue.is_a?(String) || queue.is_a?(Symbol)
58
+
59
+ temp_context = LockContext.new({ job_clazz: self, jid: jid, queue: queue, args: args, kwargs: kwargs })
60
+ strategy = temp_context.lock_strategy
61
+ locksmith = strategy.send(:locksmith)
62
+
63
+ if jid
64
+ locksmith.unlock!()
65
+ else
66
+ locksmith.locked_jids.each do |jid|
67
+ unlock!(jid, args: args, kwargs: kwargs, queue: queue)
68
+ end
69
+ end
70
+ end
71
+
72
+ # def unlock_all!(queue: :all)
73
+ # # TODO Public API to manually remove all locks for this class
74
+ # end
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -34,7 +34,7 @@ module CanvasSync
34
34
  # TODO Support globalid
35
35
  model_class = load_constant(job[:model])
36
36
  find_by = job[:find_by]
37
- target = find_by.is_a?(Hash) ? model_class.find_by(find_by) : model_class.find_by(id: find_by)
37
+ target = find_by.is_a?(Hash) ? model_class.find_by(**find_by) : model_class.find_by(id: find_by)
38
38
  target.send(job[:method], *job_args, **job_kwargs)
39
39
  elsif job[:class]
40
40
  target = load_constant(job[:class])
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.21.1".freeze
2
+ VERSION = "0.22.0.beta1".freeze
3
3
  end
data/lib/canvas_sync.rb CHANGED
@@ -16,6 +16,7 @@ require "canvas_sync/batch_processor"
16
16
  require "canvas_sync/config"
17
17
 
18
18
  require "canvas_sync/job_batches/batch"
19
+ require "canvas_sync/job_uniqueness/job_uniqueness"
19
20
 
20
21
  Dir[File.dirname(__FILE__) + "/canvas_sync/jobs/*.rb"].each { |file| require file }
21
22
  Dir[File.dirname(__FILE__) + "/canvas_sync/processors/*.rb"].each { |file| require file }
@@ -249,7 +250,7 @@ module CanvasSync
249
250
  global_options[:account_id] = account_id if account_id.present?
250
251
  global_options.merge!(globals) if globals
251
252
 
252
- JobBatches::ChainBuilder.build(CanvasSync::Jobs::BeginSyncChainJob, [], global_options, &blk)
253
+ JobBatches::ChainBuilder.build(CanvasSync::Jobs::BeginSyncChainJob, *[], **global_options, &blk)
253
254
  end
254
255
 
255
256
  def group_by_job_options(model_list, options_hash, only_split: nil, default_key: :provisioning)
@@ -338,7 +339,7 @@ module CanvasSync
338
339
  def logger
339
340
  return @logger if defined? @logger
340
341
  @logger = Logger.new(STDOUT)
341
- @logger.level = Logger::DEBUG
342
+ @logger.level = Logger::WARN
342
343
  @logger
343
344
  end
344
345
 
@@ -347,7 +348,7 @@ module CanvasSync
347
348
  end
348
349
 
349
350
  def redis_prefix
350
- pfx = "cs"
351
+ pfx = config.redis_key_prefix
351
352
  pfx = "#{Apartment::Tenant.current}:#{pfx}" if defined?(Apartment)
352
353
  pfx
353
354
  end
@@ -17,8 +17,9 @@ class Rubric < ApplicationRecord
17
17
  belongs_to :rubric, primary_key: :canvas_id, foreign_key: :canvas_rubric_id , optional: true # based on another rubric
18
18
  belongs_to :context, polymorphic: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type, optional: true
19
19
 
20
- has_many :rubric_associations, primary_key: :canvas_id, foreign_key: :canvas_association_id, foreign_type: :canvas_association_type
21
20
  has_many :rubric_assessments, through: :rubric_associations, dependent: :destroy
21
+ has_many :rubric_associations, primary_key: :canvas_id, foreign_key: :canvas_rubric_id
22
+ has_many :associated_objects, through: :rubric_associations, source: :association_object
22
23
 
23
24
  api_syncable({
24
25
  canvas_id: :id,
@@ -5,7 +5,7 @@ Rails.application.configure do
5
5
  # test suite. You never need to work with it otherwise. Remember that
6
6
  # your test database is "scratch space" for the test suite and is wiped
7
7
  # and recreated between test runs. Don't rely on the data there!
8
- config.cache_classes = true
8
+ config.cache_classes = false
9
9
 
10
10
  # Do not eager load code on boot. This avoids loading your whole application
11
11
  # just for the purpose of running a single test. If you are using a tool that
@@ -1,6 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
- class TestWorker < BatchTestJobBase
3
+ class BatchTestWorker < BatchTestJobBase
4
4
  def perform
5
5
  end
6
6
  end
@@ -173,6 +173,48 @@ RSpec.describe CanvasSync::JobBatches::Batch do
173
173
  end
174
174
  end
175
175
 
176
+ describe '#process_dead_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 'dead' do
183
+ let(:failed_jid) { 'xxx' }
184
+
185
+ it 'tries to call death callback' do
186
+ allow(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:stagnated, bid)
187
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:death, bid)
188
+ CanvasSync::JobBatches::Batch.process_dead_job(bid, failed_jid)
189
+ end
190
+
191
+ it 'add job to failed list' do
192
+ CanvasSync::JobBatches::Batch.process_dead_job(bid, 'failed-job-id')
193
+ CanvasSync::JobBatches::Batch.process_dead_job(bid, failed_jid)
194
+ failed = CanvasSync::JobBatches::Batch.redis { |r| r.smembers("BID-#{bid}-dead") }
195
+ expect(failed).to contain_exactly('xxx', 'failed-job-id')
196
+ end
197
+
198
+ it 'automattically triggers the :stagnated callback' do
199
+ expect(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:stagnated, bid)
200
+ allow(CanvasSync::JobBatches::Batch).to receive(:enqueue_callbacks).with(:death, bid)
201
+ CanvasSync::JobBatches::Batch.process_dead_job(bid, failed_jid)
202
+ end
203
+
204
+ it 'does not trigger :stagnated if pending jobs are still present' do
205
+ CanvasSync::JobBatches::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 2) }
206
+ expect(CanvasSync::JobBatches::Batch).to_not receive(:enqueue_callbacks).with(:stagnated, bid)
207
+ CanvasSync::JobBatches::Batch.process_dead_job(bid, failed_jid)
208
+ end
209
+
210
+ it 'does not trigger :stagnated if pending batches are still present' do
211
+ CanvasSync::JobBatches::Batch.redis { |r| r.hset("BID-#{bid}", 'pending', 2) }
212
+ expect(CanvasSync::JobBatches::Batch).to_not receive(:enqueue_callbacks).with(:stagnated, bid)
213
+ CanvasSync::JobBatches::Batch.process_dead_job(bid, failed_jid)
214
+ end
215
+ end
216
+ end
217
+
176
218
  describe '#process_successful_job' do
177
219
  let(:batch) { CanvasSync::JobBatches::Batch.new }
178
220
  let(:bid) { batch.bid }
@@ -182,7 +224,7 @@ RSpec.describe CanvasSync::JobBatches::Batch do
182
224
  context 'complete' do
183
225
  before { batch.on(:complete, Object) }
184
226
  # before { batch.increment_job_queue(bid) }
185
- # before { batch.jobs do TestWorker.perform_async end }
227
+ # before { batch.jobs do BatchTestWorker.perform_async end }
186
228
  # before { CanvasSync::JobBatches::Batch.process_failed_job(bid, 'failed-job-id') }
187
229
 
188
230
  it 'tries to call complete callback' do
@@ -211,7 +253,7 @@ RSpec.describe CanvasSync::JobBatches::Batch do
211
253
 
212
254
  it 'triggers callbacks as expected' do
213
255
  ActiveJob::Base.queue_adapter = :sidekiq
214
- CanvasSync::JobBatches::Batch::Callback.worker_class = CanvasSync::JobBatches::Sidekiq::SidekiqCallbackWorker
256
+ CanvasSync::JobBatches::Batch::Callback.worker_class = CanvasSync::JobBatches::Compat::Sidekiq::SidekiqCallbackWorker
215
257
 
216
258
  callback_instance = double('SampleCallback')
217
259
  expect(SampleCallback).to receive(:new).at_least(1).times.and_return(callback_instance)
@@ -229,7 +271,7 @@ RSpec.describe CanvasSync::JobBatches::Batch do
229
271
 
230
272
  it 'delays triggering callbacks if keep_open is set' do
231
273
  ActiveJob::Base.queue_adapter = :sidekiq
232
- CanvasSync::JobBatches::Batch::Callback.worker_class = CanvasSync::JobBatches::Sidekiq::SidekiqCallbackWorker
274
+ CanvasSync::JobBatches::Batch::Callback.worker_class = CanvasSync::JobBatches::Compat::Sidekiq::SidekiqCallbackWorker
233
275
 
234
276
  callback_instance = double('SampleCallback')
235
277
  expect(SampleCallback).to receive(:new).at_least(1).times.and_return(callback_instance)
@@ -303,7 +345,7 @@ RSpec.describe CanvasSync::JobBatches::Batch do
303
345
  let(:batch) { CanvasSync::JobBatches::Batch.new }
304
346
 
305
347
  it 'increments pending' do
306
- batch.jobs do TestWorker.perform_async end
348
+ batch.jobs do BatchTestWorker.perform_async end
307
349
  pending = CanvasSync::JobBatches::Batch.redis { |r| r.hget("BID-#{batch.bid}", 'pending') }
308
350
  expect(pending).to eq('1')
309
351
  end
@@ -338,7 +380,7 @@ RSpec.describe CanvasSync::JobBatches::Batch do
338
380
 
339
381
  context 'With ActiveJob Adapter' do
340
382
  around(:all) do |block|
341
- CanvasSync::JobBatches::Batch::Callback.worker_class = CanvasSync::JobBatches::ActiveJob::ActiveJobCallbackWorker
383
+ CanvasSync::JobBatches::Batch::Callback.worker_class = CanvasSync::JobBatches::Compat::ActiveJob::ActiveJobCallbackWorker
342
384
  block.run
343
385
  end
344
386
 
@@ -396,7 +438,7 @@ RSpec.describe CanvasSync::JobBatches::Batch do
396
438
 
397
439
  context 'With Sidekiq Adapter' do
398
440
  around(:all) do |block|
399
- CanvasSync::JobBatches::Batch::Callback.worker_class = CanvasSync::JobBatches::Sidekiq::SidekiqCallbackWorker
441
+ CanvasSync::JobBatches::Batch::Callback.worker_class = CanvasSync::JobBatches::Compat::Sidekiq::SidekiqCallbackWorker
400
442
  block.run
401
443
  end
402
444
 
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe CanvasSync::JobBatches::ActiveJob do
4
- describe CanvasSync::JobBatches::ActiveJob::BatchAwareJob do
3
+ RSpec.describe CanvasSync::JobBatches::Compat::ActiveJob do
4
+ describe CanvasSync::JobBatches::Compat::ActiveJob::BatchAwareJob do
5
5
  include ActiveJob::TestHelper
6
6
 
7
7
  after do
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
- RSpec.describe CanvasSync::JobBatches::Sidekiq do
4
- describe CanvasSync::JobBatches::Sidekiq::ServerMiddleware do
3
+ RSpec.describe CanvasSync::JobBatches::Compat::Sidekiq do
4
+ describe CanvasSync::JobBatches::Compat::Sidekiq::ServerMiddleware do
5
5
  context 'when without batch' do
6
6
  it 'just yields' do
7
7
  yielded = false
@@ -44,7 +44,7 @@ RSpec.describe CanvasSync::JobBatches::Sidekiq do
44
44
  end
45
45
  end
46
46
 
47
- describe CanvasSync::JobBatches::Sidekiq::ClientMiddleware do
47
+ describe CanvasSync::JobBatches::Compat::Sidekiq::ClientMiddleware do
48
48
  context 'when without batch' do
49
49
  it 'just yields' do
50
50
  yielded = false
@@ -80,16 +80,17 @@ RSpec.describe CanvasSync::JobBatches::Sidekiq do
80
80
  end
81
81
  end
82
82
 
83
- RSpec.describe CanvasSync::JobBatches::Sidekiq do
84
- let(:config) { class_double(Sidekiq) }
83
+ RSpec.describe CanvasSync::JobBatches::Compat::Sidekiq do
84
+ let(:config) { defined?(Sidekiq::Config) ? double(Sidekiq::Config) : class_double(Sidekiq) }
85
85
  let(:client_middleware) { double(Sidekiq::Middleware::Chain) }
86
86
 
87
87
  context 'client' do
88
88
  it 'adds client middleware' do
89
- expect(Sidekiq).to receive(:configure_client).and_yield(config)
89
+ allow(Sidekiq).to receive(:configure_client).and_yield(config)
90
90
  expect(config).to receive(:client_middleware).and_yield(client_middleware)
91
- expect(client_middleware).to receive(:add).with(CanvasSync::JobBatches::Sidekiq::ClientMiddleware)
92
- CanvasSync::JobBatches::Sidekiq.configure
91
+ expect(client_middleware).to receive(:add).with(CanvasSync::JobBatches::Compat::Sidekiq::ClientMiddleware)
92
+ CanvasSync::JobBatches::Compat::Sidekiq.instance_variable_set(:@already_configured, false)
93
+ CanvasSync::JobBatches::Compat::Sidekiq.configure
93
94
  end
94
95
  end
95
96
 
@@ -98,14 +99,15 @@ RSpec.describe CanvasSync::JobBatches::Sidekiq do
98
99
  let(:death_handlers) { double(Array) }
99
100
 
100
101
  it 'adds client and server middleware' do
101
- expect(Sidekiq).to receive(:configure_server).and_yield(config)
102
+ allow(Sidekiq).to receive(:configure_server).and_yield(config)
102
103
  expect(config).to receive(:client_middleware).and_yield(client_middleware)
103
104
  expect(config).to receive(:server_middleware).and_yield(server_middleware)
104
105
  expect(config).to receive(:death_handlers).and_return(death_handlers)
105
- expect(client_middleware).to receive(:add).with(CanvasSync::JobBatches::Sidekiq::ClientMiddleware)
106
- expect(server_middleware).to receive(:add).with(CanvasSync::JobBatches::Sidekiq::ServerMiddleware)
106
+ expect(client_middleware).to receive(:add).with(CanvasSync::JobBatches::Compat::Sidekiq::ClientMiddleware)
107
+ expect(server_middleware).to receive(:add).with(CanvasSync::JobBatches::Compat::Sidekiq::ServerMiddleware)
107
108
  expect(death_handlers).to receive(:<<)
108
- CanvasSync::JobBatches::Sidekiq.configure
109
+ CanvasSync::JobBatches::Compat::Sidekiq.instance_variable_set(:@already_configured, false)
110
+ CanvasSync::JobBatches::Compat::Sidekiq.configure
109
111
  end
110
112
  end
111
113
 
@@ -29,7 +29,7 @@ RSpec.describe 'Batch flow' do
29
29
 
30
30
  batch.jobs do
31
31
  3.times do
32
- TestWorker.perform_async
32
+ BatchTestWorker.perform_async
33
33
  end
34
34
  end
35
35
 
@@ -2,7 +2,7 @@ require 'spec_helper'
2
2
  require 'sidekiq/testing'
3
3
 
4
4
  Sidekiq::Testing.server_middleware do |chain|
5
- chain.add CanvasSync::JobBatches::Sidekiq::ServerMiddleware
5
+ chain.add CanvasSync::JobBatches::Compat::Sidekiq::ServerMiddleware
6
6
  end
7
7
 
8
8
  CanvasSync.redis { |r| r.flushdb }
@@ -19,7 +19,7 @@ RSpec.describe CanvasSync::JobBatches::Batch::Status do
19
19
  end
20
20
 
21
21
  context 'when more than 0' do
22
- before { batch.jobs do TestWorker.perform_async end }
22
+ before { batch.jobs do BatchTestWorker.perform_async end }
23
23
  it 'returns pending jobs' do
24
24
  expect(subject.pending).to eq(1)
25
25
  end
@@ -68,7 +68,7 @@ RSpec.describe CanvasSync::JobBatches::Batch::Status do
68
68
  describe '#created_at' do
69
69
  it 'returns time' do
70
70
  batch = CanvasSync::JobBatches::Batch.new
71
- batch.jobs do TestWorker.perform_async end
71
+ batch.jobs do BatchTestWorker.perform_async end
72
72
  status = described_class.new(batch.bid)
73
73
  expect(status.created_at).not_to be_nil
74
74
  end
@@ -0,0 +1,49 @@
1
+ require 'job_uniqueness/spec_helper'
2
+
3
+ RSpec.describe CanvasSync::JobUniqueness::Compat::ActiveJob do
4
+ context 'Job Extension' do
5
+ it 'includes UniqueJobExtension' do
6
+ expect(ActiveJob::Base < CanvasSync::JobUniqueness::Compat::ActiveJob::JobExtension).to be true
7
+ end
8
+
9
+ it 'has the ensure_uniqueness method' do
10
+ expect(ActiveJob::Base.method(:ensure_uniqueness)).to be_present
11
+ end
12
+ end
13
+
14
+ context 'Job' do
15
+ let(:test_job) do
16
+ Class.new(ActiveJob::Base) do
17
+ ensure_uniqueness(
18
+ strategy: :until_executed,
19
+ )
20
+ def perform; end
21
+ end
22
+ end
23
+
24
+ before(:each) do
25
+ stub_const('TestJob', test_job)
26
+ ActiveJob::Base.queue_adapter = :sidekiq
27
+ end
28
+
29
+ it 'runs as expected' do
30
+ strategy = CanvasSync::JobUniqueness::Strategy::UntilExecuted.new(nil)
31
+ allow_any_instance_of(CanvasSync::JobUniqueness::LockContext).to receive(:lock_strategy) do |lock_context|
32
+ strategy.instance_variable_set(:@lock_context, lock_context)
33
+ strategy
34
+ end
35
+ allow(CanvasSync::JobUniqueness::Strategy::UntilExecuted).to receive(:new).and_return(strategy)
36
+
37
+ expect(strategy).to receive(:on_enqueue).and_call_original
38
+ expect(strategy).to receive(:on_perform).and_call_original
39
+ expect(strategy).to receive(:batch_callback).with(:complete, anything).and_call_original
40
+ expect(strategy).to receive(:batch_callback).with(:success, anything).and_call_original
41
+
42
+ expect_any_instance_of(test_job).to receive(:perform)
43
+
44
+ Sidekiq::Testing.inline! do
45
+ test_job.perform_later
46
+ end
47
+ end
48
+ end
49
+ end