canvas_sync 0.12.0 → 0.13.0

Sign up to get free protection for your applications and to get access to all the features.
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