canvas_sync 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/canvas_sync.rb +16 -15
  4. data/lib/canvas_sync/api_syncable.rb +4 -162
  5. data/lib/canvas_sync/class_callback_executor.rb +35 -0
  6. data/lib/canvas_sync/concerns/account/ancestry.rb +60 -0
  7. data/lib/canvas_sync/concerns/api_syncable.rb +189 -0
  8. data/lib/canvas_sync/concerns/legacy_columns.rb +34 -0
  9. data/lib/canvas_sync/generators/templates/models/account.rb +7 -1
  10. data/lib/canvas_sync/generators/templates/models/admin.rb +2 -1
  11. data/lib/canvas_sync/generators/templates/models/assignment.rb +2 -1
  12. data/lib/canvas_sync/generators/templates/models/assignment_group.rb +2 -1
  13. data/lib/canvas_sync/generators/templates/models/context_module.rb +2 -1
  14. data/lib/canvas_sync/generators/templates/models/context_module_item.rb +2 -1
  15. data/lib/canvas_sync/generators/templates/models/course.rb +3 -2
  16. data/lib/canvas_sync/generators/templates/models/enrollment.rb +2 -1
  17. data/lib/canvas_sync/generators/templates/models/role.rb +2 -1
  18. data/lib/canvas_sync/generators/templates/models/section.rb +2 -1
  19. data/lib/canvas_sync/generators/templates/models/submission.rb +2 -1
  20. data/lib/canvas_sync/generators/templates/models/term.rb +2 -1
  21. data/lib/canvas_sync/generators/templates/models/user.rb +2 -1
  22. data/lib/canvas_sync/importers/bulk_importer.rb +7 -1
  23. data/lib/canvas_sync/importers/legacy_importer.rb +4 -2
  24. data/lib/canvas_sync/job.rb +3 -1
  25. data/lib/canvas_sync/job_chain.rb +57 -0
  26. data/lib/canvas_sync/jobs/sync_accounts_job.rb +31 -0
  27. data/lib/canvas_sync/record.rb +9 -0
  28. data/lib/canvas_sync/version.rb +1 -1
  29. data/spec/canvas_sync/canvas_sync_spec.rb +14 -14
  30. data/spec/dummy/app/models/account.rb +7 -1
  31. data/spec/dummy/app/models/admin.rb +2 -1
  32. data/spec/dummy/app/models/assignment.rb +2 -1
  33. data/spec/dummy/app/models/assignment_group.rb +2 -1
  34. data/spec/dummy/app/models/context_module.rb +2 -1
  35. data/spec/dummy/app/models/context_module_item.rb +2 -1
  36. data/spec/dummy/app/models/course.rb +3 -2
  37. data/spec/dummy/app/models/enrollment.rb +2 -1
  38. data/spec/dummy/app/models/role.rb +2 -1
  39. data/spec/dummy/app/models/section.rb +2 -1
  40. data/spec/dummy/app/models/submission.rb +2 -1
  41. data/spec/dummy/app/models/term.rb +2 -1
  42. data/spec/dummy/app/models/user.rb +2 -1
  43. data/spec/dummy/config/application.rb +12 -1
  44. data/spec/dummy/config/database.yml +11 -11
  45. data/spec/dummy/config/environments/development.rb +3 -3
  46. data/spec/dummy/config/initializers/assets.rb +1 -1
  47. metadata +9 -2
@@ -0,0 +1,34 @@
1
+ # Concern that can be used when dealing with Legacy code.
2
+ #
3
+ # It automatically aliases `canvas_id` -> `canvas_<model>_id` or vice-versa.
4
+ module CanvasSync::Concerns
5
+ module LegacyColumns
6
+ extend ActiveSupport::Concern
7
+
8
+ class_methods do
9
+ def inherited(subclass)
10
+ super.tap do
11
+ legacy_column_apply(subclass)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def legacy_column_apply(cls)
18
+ cid_column = "canvas_#{subclass.name.downcase}_id"
19
+ column_names = subclass.columns.map(&:name)
20
+ return if column_names.include?('canvas_id') && column_names.include?(cid_column)
21
+ if column_names.include?('canvas_id')
22
+ subclass.alias_attribute(cid_column.to_sym, :canvas_id)
23
+ elsif column_names.include?(cid_column)
24
+ subclass.alias_attribute(:canvas_id, cid_column.to_sym)
25
+ end
26
+ rescue ActiveRecord::StatementInvalid
27
+ end
28
+ end
29
+
30
+ included do
31
+ legacy_column_apply(self)
32
+ end
33
+ end
34
+ end
@@ -1,11 +1,17 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Account < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
6
+ # include CanvasSync::Concerns::Account::Ancestry # Add support for the ancestry Gem
5
7
 
6
8
  validates :canvas_id, uniqueness: true, presence: true
7
9
 
8
10
  has_many :admins, primary_key: :canvas_id, foreign_key: :canvas_account_id
11
+ belongs_to :canvas_parent, class_name: 'Account', optional: true,
12
+ primary_key: :canvas_id, foreign_key: :canvas_parent_account_id
13
+ has_many :sub_accounts, class_name: 'Account',
14
+ primary_key: :canvas_id, foreign_key: :canvas_parent_account_id
9
15
 
10
16
  api_syncable({
11
17
  name: :name,
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Admin < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  belongs_to :account, primary_key: :canvas_id, foreign_key: :canvas_account_id, optional: true
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Assignment < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  belongs_to :context, polymorphic: true, optional: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class AssignmentGroup < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  belongs_to :course, primary_key: :canvas_id, foreign_key: :canvas_course_id, optional: true
@@ -4,7 +4,8 @@
4
4
  # 1 - Module is a reserved word in Rails and you can't call a model a Module
5
5
  # 2 - Canvas calls them ContextModules
6
6
  class ContextModule < ApplicationRecord
7
- include CanvasSync::ApiSyncable
7
+ include CanvasSync::Record
8
+ include CanvasSync::Concerns::ApiSyncable
8
9
 
9
10
  belongs_to :context, polymorphic: true, optional: true, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type
10
11
  has_many :context_module_items, primary_key: :canvas_id, foreign_key: :canvas_context_module_id
@@ -1,7 +1,8 @@
1
1
  # # <%= autogenerated_migration_warning %>
2
2
 
3
3
  class ContextModuleItem < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  belongs_to :context_module, primary_key: :canvas_id, foreign_key: :canvas_context_module_id, optional: true
7
8
  belongs_to :content, polymorphic: true, optional: true, primary_key: :canvas_id, foreign_key: :canvas_content_id, foreign_type: :canvas_content_type
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Course < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  belongs_to :term, foreign_key: :canvas_term_id, primary_key: :canvas_id, optional: true
@@ -10,7 +11,7 @@ class Course < ApplicationRecord
10
11
  has_many :assignments, as: :context, primary_key: :canvas_id, foreign_key: :canvas_context_id, foreign_type: :canvas_context_type
11
12
  has_many :submissions, primary_key: :canvas_id, foreign_key: :canvas_course_id
12
13
  has_many :assignment_groups, primary_key: :canvas_id, foreign_key: :canvas_course_id
13
-
14
+
14
15
  api_syncable({
15
16
  sis_id: :sis_course_id,
16
17
  course_code: :course_code,
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Enrollment < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  belongs_to :user, primary_key: :canvas_id, foreign_key: :canvas_user_id, optional: true
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Role < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  has_many :admins, foreign_key: :canvas_role_id, primary_key: :canvas_id
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Section < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  belongs_to :course, primary_key: :canvas_id, foreign_key: :canvas_course_id, optional: true
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Submission < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  belongs_to :assignment, primary_key: :canvas_id, foreign_key: :canvas_assignment_id, optional: true
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class Term < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  has_many :courses, foreign_key: :canvas_term_id, primary_key: :canvas_id
@@ -1,7 +1,8 @@
1
1
  # <%= autogenerated_model_warning %>
2
2
 
3
3
  class User < ApplicationRecord
4
- include CanvasSync::ApiSyncable
4
+ include CanvasSync::Record
5
+ include CanvasSync::Concerns::ApiSyncable
5
6
 
6
7
  validates :canvas_id, uniqueness: true, presence: true
7
8
  has_many :enrollments, primary_key: :canvas_id, foreign_key: :canvas_user_id
@@ -17,7 +17,13 @@ module CanvasSync
17
17
  # Note: passing the key [:on_duplicate_key_ignore] will override the default behavior of [:on_duplicate_key_update]
18
18
  # @yieldparam [Array] row if a block is passed in it will yield the current row from the CSV.
19
19
  # This can be used if you need to filter or massage the data in any way.
20
- def self.import(report_file_path, mapping, klass, conflict_target, import_args: {}) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
20
+ def self.import(report_file_path, mapping, klass, conflict_target, import_args: {}, &blk) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/LineLength
21
+ ClassCallbackExecutor.run_if_defined(klass, :sync_import) do
22
+ perform_in_batches(report_file_path, mapping, klass, conflict_target, import_args: import_args, &blk)
23
+ end
24
+ end
25
+
26
+ def self.perform_in_batches(report_file_path, mapping, klass, conflict_target, import_args: {})
21
27
  csv_column_names = mapping.keys
22
28
  database_column_names = mapping.values.map { |value| value[:database_column_name] }
23
29
  rows = []
@@ -8,8 +8,10 @@ module CanvasSync
8
8
  # @param report_file_path [String]
9
9
  # @param klass [Object]
10
10
  def self.import(report_file_path, klass, account_id, options)
11
- CSV.foreach(report_file_path, headers: true, header_converters: :symbol) do |row|
12
- klass.create_or_update_from_csv(row, account_id, options)
11
+ ClassCallbackExecutor.run_if_defined(klass, :sync_import) do
12
+ CSV.foreach(report_file_path, headers: true, header_converters: :symbol) do |row|
13
+ klass.create_or_update_from_csv(row, account_id, options)
14
+ end
13
15
  end
14
16
  end
15
17
  end
@@ -61,7 +61,9 @@ module CanvasSync
61
61
  if model.respond_to? :create_or_update
62
62
  model.create_or_update(params)
63
63
  elsif model.method_defined? :update_from_api_params!
64
- model.find_or_initialize_by(canvas_id: params['id']).update_from_api_params!(params)
64
+ instance = model.find_or_initialize_by(canvas_id: params['id'])
65
+ instance.update_from_api_params!(params)
66
+ instance
65
67
  else
66
68
  raise "Could not create/update #{model.name}. It must have a create_or_update(params) ClassMethod or implement ApiSyncable."
67
69
  end
@@ -0,0 +1,57 @@
1
+ module CanvasSync
2
+ class JobChain
3
+ attr_reader :chain_data
4
+
5
+ delegate_missing_to :chain_data
6
+
7
+ VALID_PLACEMENT_PARAMETERS = %i[before after].freeze
8
+
9
+ def initialize(chain_data)
10
+ @chain_data = chain_data
11
+ end
12
+
13
+ def jobs
14
+ chain_data[:jobs]
15
+ end
16
+
17
+ def global_options
18
+ chain_data[:global_options]
19
+ end
20
+
21
+ def merge_options(job, options)
22
+ jobs.each do |j|
23
+ j[:options].deep_merge!(options) if job_matches_pattern(j, job)
24
+ end
25
+ end
26
+
27
+ def insert(new_job, **kwargs)
28
+ invalid_params = kwargs.keys - VALID_PLACEMENT_PARAMETERS
29
+ raise "Invalid placement parameters: #{invalid_params.map(&:to_s).join(', ')}" if invalid_params.present?
30
+ raise "Exactly one placement parameter may be provided" if kwargs.values.compact!.length > 1
31
+
32
+ if !kwargs.present?
33
+ jobs << new_job
34
+ else
35
+ placement = kwargs.keys[0]
36
+ relative_to = kwargs.values[0]
37
+
38
+ index = jobs.index { |job| job_matches_pattern(job, relative_to) }
39
+ raise "Could not find a \"#{relative_to}\" job in the chain" if index.nil?
40
+
41
+ index += 1 if placement == :after
42
+ new_job[:job] = new_job[:job].to_s
43
+ jobs.insert(index, new_job)
44
+ end
45
+ end
46
+
47
+ def process!(extra_options: {})
48
+ CanvasSync::invoke_next(self, extra_options: extra_options)
49
+ end
50
+
51
+ private
52
+
53
+ def job_matches_pattern(job_entry, pattern)
54
+ job_entry[:job].to_s == pattern.to_s
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ module CanvasSync
2
+ module Jobs
3
+ class SyncAccountsJob < ReportStarter
4
+ # Starts a provisioning report for just accounts.
5
+ #
6
+ # Provisioning reports do not scope accounts by term, so when we are
7
+ # running provisioning by term we sync users first so we don't duplicate
8
+ # the work of syncing all accounts for each term.
9
+ #
10
+ # @param job_chain [Hash]
11
+ # @param options [Hash]
12
+ def perform(job_chain, options)
13
+ unless options[:root_account] == false
14
+ acc_params = CanvasSync.get_canvas_sync_client(job_chain[:global_options]).account("self")
15
+ update_or_create_model(Account, acc_params)
16
+ end
17
+
18
+ super(
19
+ job_chain,
20
+ "proservices_provisioning_csv",
21
+ merge_report_params(job_chain, options, {
22
+ accounts: true,
23
+ include_deleted: true,
24
+ }, term_scope: false),
25
+ CanvasSync::Processors::ProvisioningReportProcessor.to_s,
26
+ { models: ["accounts"] },
27
+ )
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,9 @@
1
+ module CanvasSync
2
+ module Record
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ define_model_callbacks :sync_import
7
+ end
8
+ end
9
+ end
@@ -1,3 +1,3 @@
1
1
  module CanvasSync
2
- VERSION = "0.12.0".freeze
2
+ VERSION = "0.13.0".freeze
3
3
  end
@@ -3,7 +3,7 @@ require 'spec_helper'
3
3
  RSpec.describe CanvasSync do
4
4
  describe '.provisioning_sync' do
5
5
  it 'invokes the first job in the queue and passes on the rest of the job chain' do
6
- expected_job_chain = CanvasSync.default_provisioning_report_chain(['courses'], nil)
6
+ expected_job_chain = CanvasSync.default_provisioning_report_chain(['courses'], nil).chain_data
7
7
  first_job = expected_job_chain[:jobs].shift
8
8
 
9
9
  expect(CanvasSync::Jobs::SyncTermsJob).to receive(:perform_later)
@@ -31,7 +31,7 @@ RSpec.describe CanvasSync do
31
31
  provisioning: { c: 3 },
32
32
  global: { d: 4 },
33
33
  })
34
- expect(chain).to eq({
34
+ expect(chain.chain_data).to eq({
35
35
  jobs: [
36
36
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: { a: 1 } },
37
37
  { job: CanvasSync::Jobs::SyncUsersJob.to_s, options: { b: 2 } },
@@ -44,7 +44,7 @@ RSpec.describe CanvasSync do
44
44
  context 'we are syncing users with a term scope' do
45
45
  it 'syncs the users in a separate job that runs first' do
46
46
  chain = CanvasSync.default_provisioning_report_chain(['users', 'courses'], :active)
47
- expect(chain).to eq({
47
+ expect(chain.chain_data).to eq({
48
48
  jobs: [
49
49
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
50
50
  { job: CanvasSync::Jobs::SyncUsersJob.to_s, options: {} },
@@ -58,7 +58,7 @@ RSpec.describe CanvasSync do
58
58
  context 'we are syncing users without a term scope' do
59
59
  it 'syncs users along with the rest of the provisioning report' do
60
60
  chain = CanvasSync.default_provisioning_report_chain(['users', 'courses'])
61
- expect(chain).to eq({
61
+ expect(chain.chain_data).to eq({
62
62
  jobs: [
63
63
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
64
64
  { job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: nil, models: ['users', 'courses'] } }
@@ -71,7 +71,7 @@ RSpec.describe CanvasSync do
71
71
  context 'we are syncing roles with a term scope' do
72
72
  it 'syncs the roles in a separate job that runs first' do
73
73
  chain = CanvasSync.default_provisioning_report_chain(['roles', 'courses'], :active)
74
- expect(chain).to eq({
74
+ expect(chain.chain_data).to eq({
75
75
  jobs: [
76
76
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
77
77
  { job: CanvasSync::Jobs::SyncRolesJob.to_s, options: {} },
@@ -85,7 +85,7 @@ RSpec.describe CanvasSync do
85
85
  context 'we are syncing roles without a term scope' do
86
86
  it 'syncs roles separately even with no term scope' do
87
87
  chain = CanvasSync.default_provisioning_report_chain(['roles', 'courses'])
88
- expect(chain).to eq({
88
+ expect(chain.chain_data).to eq({
89
89
  jobs: [
90
90
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
91
91
  { job: CanvasSync::Jobs::SyncRolesJob.to_s, options: {} },
@@ -99,7 +99,7 @@ RSpec.describe CanvasSync do
99
99
  context 'we are syncing admins with a term scope' do
100
100
  it 'syncs the admins in a separate job that runs first' do
101
101
  chain = CanvasSync.default_provisioning_report_chain(['admins', 'courses'], :active)
102
- expect(chain).to eq({
102
+ expect(chain.chain_data).to eq({
103
103
  jobs: [
104
104
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
105
105
  { job: CanvasSync::Jobs::SyncAdminsJob.to_s, options: {} },
@@ -113,7 +113,7 @@ RSpec.describe CanvasSync do
113
113
  context 'we are syncing admins without a term scope' do
114
114
  it 'syncs admins separately even with no term scope' do
115
115
  chain = CanvasSync.default_provisioning_report_chain(['admins', 'courses'])
116
- expect(chain).to eq({
116
+ expect(chain.chain_data).to eq({
117
117
  jobs: [
118
118
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
119
119
  { job: CanvasSync::Jobs::SyncAdminsJob.to_s, options: {} },
@@ -128,7 +128,7 @@ RSpec.describe CanvasSync do
128
128
  it "appends the SyncAssignmentsJob" do
129
129
  chain = CanvasSync.default_provisioning_report_chain(%w[users enrollments assignments])
130
130
 
131
- expect(chain).to eq(
131
+ expect(chain.chain_data).to eq(
132
132
  jobs: [
133
133
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
134
134
  { job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: nil, models: %w[users enrollments] } },
@@ -143,7 +143,7 @@ RSpec.describe CanvasSync do
143
143
  it "appends the SyncSubmissionsJob" do
144
144
  chain = CanvasSync.default_provisioning_report_chain(%w[users enrollments submissions])
145
145
 
146
- expect(chain).to eq(
146
+ expect(chain.chain_data).to eq(
147
147
  jobs: [
148
148
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
149
149
  { job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: nil, models: %w[users enrollments] } },
@@ -158,7 +158,7 @@ RSpec.describe CanvasSync do
158
158
  it "appends the SyncAssignmentGroupsJob" do
159
159
  chain = CanvasSync.default_provisioning_report_chain(%w[users enrollments assignment_groups])
160
160
 
161
- expect(chain).to eq(
161
+ expect(chain.chain_data).to eq(
162
162
  jobs: [
163
163
  { job: CanvasSync::Jobs::SyncTermsJob.to_s, options: {} },
164
164
  { job: CanvasSync::Jobs::SyncProvisioningReportJob.to_s, options: { term_scope: nil, models: %w[users enrollments] } },
@@ -185,7 +185,7 @@ RSpec.describe CanvasSync do
185
185
  }
186
186
  }
187
187
  ]
188
- )
188
+ ).chain_data
189
189
  first_job = expected_job_chain[:jobs].shift
190
190
 
191
191
  expect(CanvasSync::Jobs::SyncSimpleTableJob).to receive(:perform_later)
@@ -242,9 +242,9 @@ RSpec.describe CanvasSync do
242
242
  global_options: {}
243
243
  }
244
244
 
245
- expect(chain).to eq(expected_job_chain)
245
+ expect(chain.chain_data).to eq(expected_job_chain)
246
246
 
247
247
  end
248
248
  end
249
-
249
+
250
250
  end