canvas_sync 0.27.11 → 0.27.15

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 (243) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/canvas_sync/api/v1/health_check_controller.rb +0 -0
  3. data/app/models/canvas_sync/job_log.rb +0 -0
  4. data/app/models/canvas_sync/sync_batch.rb +0 -0
  5. data/config/initializers/apartment.rb +0 -0
  6. data/config/routes.rb +0 -0
  7. data/db/migrate/20170915210836_create_canvas_sync_job_log.rb +0 -0
  8. data/db/migrate/20180725155729_add_job_id_to_canvas_sync_job_logs.rb +0 -0
  9. data/db/migrate/20190916154829_add_fork_count_to_canvas_sync_job_logs.rb +0 -0
  10. data/db/migrate/20201018210836_create_canvas_sync_sync_batches.rb +0 -0
  11. data/db/migrate/20201030210836_add_full_sync_to_canvas_sync_sync_batch.rb +0 -0
  12. data/lib/canvas_sync/api_syncable.rb +0 -0
  13. data/lib/canvas_sync/batch_processor.rb +0 -0
  14. data/lib/canvas_sync/class_callback_executor.rb +0 -0
  15. data/lib/canvas_sync/concerns/ability_helper.rb +43 -2
  16. data/lib/canvas_sync/concerns/account/ancestry.rb +0 -0
  17. data/lib/canvas_sync/concerns/account/base.rb +0 -0
  18. data/lib/canvas_sync/concerns/api_syncable.rb +0 -0
  19. data/lib/canvas_sync/concerns/canvas_id_as_primary.rb +0 -0
  20. data/lib/canvas_sync/concerns/jit_syncing.rb +0 -0
  21. data/lib/canvas_sync/concerns/legacy_columns.rb +0 -0
  22. data/lib/canvas_sync/concerns/role/base.rb +0 -0
  23. data/lib/canvas_sync/generators/install_generator.rb +0 -0
  24. data/lib/canvas_sync/generators/install_live_events_generator.rb +0 -0
  25. data/lib/canvas_sync/generators/templates/migrations/create_accounts.rb +0 -0
  26. data/lib/canvas_sync/generators/templates/migrations/create_admins.rb +0 -0
  27. data/lib/canvas_sync/generators/templates/migrations/create_assignment_groups.rb +0 -0
  28. data/lib/canvas_sync/generators/templates/migrations/create_assignments.rb +0 -0
  29. data/lib/canvas_sync/generators/templates/migrations/create_content_migrations.rb +0 -0
  30. data/lib/canvas_sync/generators/templates/migrations/create_context_module_items.rb +0 -0
  31. data/lib/canvas_sync/generators/templates/migrations/create_context_modules.rb +0 -0
  32. data/lib/canvas_sync/generators/templates/migrations/create_course_nicknames.rb +0 -0
  33. data/lib/canvas_sync/generators/templates/migrations/create_course_progresses.rb +0 -0
  34. data/lib/canvas_sync/generators/templates/migrations/create_grading_period_groups.rb +0 -0
  35. data/lib/canvas_sync/generators/templates/migrations/create_grading_periods.rb +0 -0
  36. data/lib/canvas_sync/generators/templates/migrations/create_group_memberships.rb +0 -0
  37. data/lib/canvas_sync/generators/templates/migrations/create_groups.rb +0 -0
  38. data/lib/canvas_sync/generators/templates/migrations/create_learning_outcome_results.rb +0 -0
  39. data/lib/canvas_sync/generators/templates/migrations/create_learning_outcomes.rb +0 -0
  40. data/lib/canvas_sync/generators/templates/migrations/create_pseudonyms.rb +0 -0
  41. data/lib/canvas_sync/generators/templates/migrations/create_rubric_assessments.rb +0 -0
  42. data/lib/canvas_sync/generators/templates/migrations/create_rubric_associations.rb +0 -0
  43. data/lib/canvas_sync/generators/templates/migrations/create_rubrics.rb +0 -0
  44. data/lib/canvas_sync/generators/templates/migrations/create_sections.rb +0 -0
  45. data/lib/canvas_sync/generators/templates/migrations/create_submissions.rb +0 -0
  46. data/lib/canvas_sync/generators/templates/migrations/create_terms.rb +0 -0
  47. data/lib/canvas_sync/generators/templates/migrations/create_user_observers.rb +0 -0
  48. data/lib/canvas_sync/generators/templates/migrations/create_users.rb +0 -0
  49. data/lib/canvas_sync/generators/templates/models/content_migration.rb +0 -0
  50. data/lib/canvas_sync/generators/templates/models/course_nickname.rb +0 -0
  51. data/lib/canvas_sync/generators/templates/models/rubric.rb +0 -0
  52. data/lib/canvas_sync/generators/templates/models/rubric_assessment.rb +0 -0
  53. data/lib/canvas_sync/generators/templates/models/rubric_association.rb +0 -0
  54. data/lib/canvas_sync/generators/templates/services/live_events/assignment_event.rb +0 -0
  55. data/lib/canvas_sync/generators/templates/services/live_events/assignment_group_event.rb +0 -0
  56. data/lib/canvas_sync/generators/templates/services/live_events/course_event.rb +0 -0
  57. data/lib/canvas_sync/generators/templates/services/live_events/course_section_event.rb +0 -0
  58. data/lib/canvas_sync/generators/templates/services/live_events/enrollment_event.rb +0 -0
  59. data/lib/canvas_sync/generators/templates/services/live_events/grade_event.rb +0 -0
  60. data/lib/canvas_sync/generators/templates/services/live_events/module_event.rb +0 -0
  61. data/lib/canvas_sync/generators/templates/services/live_events/module_item_event.rb +0 -0
  62. data/lib/canvas_sync/generators/templates/services/live_events/submission_event.rb +0 -0
  63. data/lib/canvas_sync/generators/templates/services/live_events/syllabus_event.rb +0 -0
  64. data/lib/canvas_sync/generators/templates/services/live_events/user_event.rb +0 -0
  65. data/lib/canvas_sync/importers/legacy_importer.rb +0 -0
  66. data/lib/canvas_sync/jobs/begin_sync_chain_job.rb +0 -0
  67. data/lib/canvas_sync/jobs/canvas_process_waiter.rb +0 -0
  68. data/lib/canvas_sync/jobs/report_starter.rb +5 -1
  69. data/lib/canvas_sync/jobs/report_sync_task.rb +12 -4
  70. data/lib/canvas_sync/jobs/sync_accounts_job.rb +2 -1
  71. data/lib/canvas_sync/jobs/sync_admins_job.rb +0 -0
  72. data/lib/canvas_sync/jobs/sync_assignment_groups_job.rb +0 -0
  73. data/lib/canvas_sync/jobs/sync_assignments_job.rb +0 -0
  74. data/lib/canvas_sync/jobs/sync_content_migrations_job.rb +0 -0
  75. data/lib/canvas_sync/jobs/sync_context_module_items_job.rb +0 -0
  76. data/lib/canvas_sync/jobs/sync_context_modules_job.rb +0 -0
  77. data/lib/canvas_sync/jobs/sync_provisioning_report_job.rb +1 -1
  78. data/lib/canvas_sync/jobs/sync_rubrics_job.rb +0 -0
  79. data/lib/canvas_sync/jobs/sync_simple_table_job.rb +0 -0
  80. data/lib/canvas_sync/live_events/base_handler.rb +0 -0
  81. data/lib/canvas_sync/live_events.rb +0 -0
  82. data/lib/canvas_sync/misc_helper.rb +0 -0
  83. data/lib/canvas_sync/processors/normal_processor.rb +0 -0
  84. data/lib/canvas_sync/sidekiq_job.rb +0 -0
  85. data/lib/canvas_sync/version.rb +1 -1
  86. data/lib/canvas_sync.rb +66 -86
  87. data/spec/canvas_sync/concerns/ability_helper_spec.rb +165 -0
  88. data/spec/canvas_sync/jobs/begin_sync_chain_job_spec.rb +9 -3
  89. data/spec/canvas_sync/jobs/canvas_process_waiter_spec.rb +0 -0
  90. data/spec/canvas_sync/jobs/report_starter_spec.rb +0 -0
  91. data/spec/canvas_sync/jobs/report_sync_task_spec.rb +56 -0
  92. data/spec/canvas_sync/jobs/sync_accounts_job_spec.rb +41 -0
  93. data/spec/canvas_sync/jobs/sync_admins_job_spec.rb +0 -0
  94. data/spec/canvas_sync/jobs/sync_provisioning_report_job_spec.rb +2 -2
  95. data/spec/canvas_sync/jobs/sync_roles_job_spec.rb +0 -0
  96. data/spec/canvas_sync/jobs/sync_simple_table_job_spec.rb +0 -0
  97. data/spec/canvas_sync/jobs/sync_terms_job_spec.rb +0 -0
  98. data/spec/canvas_sync/live_events/live_event_sync_spec.rb +0 -0
  99. data/spec/canvas_sync/live_events/live_events_controller_spec.rb +0 -0
  100. data/spec/canvas_sync/misc_helper_spec.rb +0 -0
  101. data/spec/canvas_sync/models/accounts_spec.rb +0 -0
  102. data/spec/canvas_sync/models/admins_spec.rb +0 -0
  103. data/spec/canvas_sync/models/assignment_group_spec.rb +0 -0
  104. data/spec/canvas_sync/models/assignment_spec.rb +0 -0
  105. data/spec/canvas_sync/models/context_module_item_spec.rb +0 -0
  106. data/spec/canvas_sync/models/context_module_spec.rb +0 -0
  107. data/spec/canvas_sync/models/course_spec.rb +0 -0
  108. data/spec/canvas_sync/models/enrollment_spec.rb +0 -0
  109. data/spec/canvas_sync/models/group_membership_spec.rb +0 -0
  110. data/spec/canvas_sync/models/group_spec.rb +0 -0
  111. data/spec/canvas_sync/models/roles_spec.rb +0 -0
  112. data/spec/canvas_sync/models/section_spec.rb +0 -0
  113. data/spec/canvas_sync/models/submission_spec.rb +0 -0
  114. data/spec/canvas_sync/models/term_spec.rb +0 -0
  115. data/spec/canvas_sync/models/user_spec.rb +0 -0
  116. data/spec/canvas_sync/processors/assignment_groups_processor_spec.rb +0 -0
  117. data/spec/canvas_sync/processors/assignments_processor_spec.rb +0 -0
  118. data/spec/canvas_sync/processors/content_migrations_processor_spec.rb +0 -0
  119. data/spec/canvas_sync/processors/context_module_items_processor_spec.rb +0 -0
  120. data/spec/canvas_sync/processors/context_modules_processor_spec.rb +0 -0
  121. data/spec/canvas_sync/processors/course_completion_report_processor_spec.rb +0 -0
  122. data/spec/canvas_sync/processors/rubric_assessments_spec.rb +0 -0
  123. data/spec/canvas_sync/processors/rubric_associations_spec.rb +0 -0
  124. data/spec/canvas_sync/processors/rubrics_processor_spec.rb +0 -0
  125. data/spec/canvas_sync/processors/submissions_processor_spec.rb +0 -0
  126. data/spec/canvas_sync/services/module_event_spec.rb +0 -0
  127. data/spec/canvas_sync/services/module_item_event_spec.rb +0 -0
  128. data/spec/factories/admin_factory.rb +0 -0
  129. data/spec/factories/assignment_factory.rb +0 -0
  130. data/spec/factories/assignment_group_factory.rb +0 -0
  131. data/spec/factories/context_module_factory.rb +0 -0
  132. data/spec/factories/context_module_item_factory.rb +0 -0
  133. data/spec/factories/course_factory.rb +0 -0
  134. data/spec/factories/enrollment_factory.rb +0 -0
  135. data/spec/factories/group_factory.rb +0 -0
  136. data/spec/factories/group_membership_factory.rb +0 -0
  137. data/spec/factories/role_factory.rb +0 -0
  138. data/spec/factories/section_factory.rb +0 -0
  139. data/spec/factories/submission_factory.rb +0 -0
  140. data/spec/factories/term_factory.rb +0 -0
  141. data/spec/factories/user_factory.rb +0 -0
  142. data/spec/spec_helper.rb +3 -1
  143. data/spec/support/fake_canvas.rb +4 -0
  144. data/spec/support/fixtures/canvas_responses/account.json +12 -0
  145. data/spec/support/fixtures/canvas_responses/admins.json +0 -0
  146. data/spec/support/fixtures/canvas_responses/module.json +0 -0
  147. data/spec/support/fixtures/canvas_responses/module_item.json +0 -0
  148. data/spec/support/fixtures/canvas_responses/roles.json +0 -0
  149. data/spec/support/fixtures/canvas_responses/terms.json +0 -0
  150. data/spec/support/fixtures/reports/assignment_groups.csv +0 -0
  151. data/spec/support/fixtures/reports/assignments.csv +0 -0
  152. data/spec/support/fixtures/reports/content_migrations.csv +0 -0
  153. data/spec/support/fixtures/reports/context_module_items.csv +0 -0
  154. data/spec/support/fixtures/reports/context_modules.csv +0 -0
  155. data/spec/support/fixtures/reports/course_nicknames.csv +0 -0
  156. data/spec/support/fixtures/reports/course_progresses.csv +0 -0
  157. data/spec/support/fixtures/reports/course_progresses_conflict.csv +0 -0
  158. data/spec/support/fixtures/reports/grading_period_groups.csv +0 -0
  159. data/spec/support/fixtures/reports/grading_periods.csv +0 -0
  160. data/spec/support/fixtures/reports/group_memberships.csv +0 -0
  161. data/spec/support/fixtures/reports/groups.csv +0 -0
  162. data/spec/support/fixtures/reports/learning_outcome_results.csv +0 -0
  163. data/spec/support/fixtures/reports/learning_outcomes.csv +0 -0
  164. data/spec/support/fixtures/reports/provisioning_csv +0 -0
  165. data/spec/support/fixtures/reports/rubric_assessments.csv +0 -0
  166. data/spec/support/fixtures/reports/rubric_associations.csv +0 -0
  167. data/spec/support/fixtures/reports/rubrics.csv +0 -0
  168. data/spec/support/fixtures/reports/sections.csv +0 -0
  169. data/spec/support/fixtures/reports/submissions.csv +0 -0
  170. data/spec/support/fixtures/reports/user_observers.csv +0 -0
  171. data/spec/support/fixtures/reports/users.csv +0 -0
  172. data/spec/support/fixtures/reports/xlist.csv +0 -0
  173. metadata +9 -143
  174. data/spec/internal/app/models/account.rb +0 -35
  175. data/spec/internal/app/models/admin.rb +0 -35
  176. data/spec/internal/app/models/assignment.rb +0 -39
  177. data/spec/internal/app/models/assignment_group.rb +0 -31
  178. data/spec/internal/app/models/assignment_override.rb +0 -34
  179. data/spec/internal/app/models/content_migration.rb +0 -18
  180. data/spec/internal/app/models/context_module.rb +0 -69
  181. data/spec/internal/app/models/context_module_item.rb +0 -33
  182. data/spec/internal/app/models/course.rb +0 -57
  183. data/spec/internal/app/models/course_nickname.rb +0 -20
  184. data/spec/internal/app/models/course_progress.rb +0 -29
  185. data/spec/internal/app/models/enrollment.rb +0 -48
  186. data/spec/internal/app/models/grading_period.rb +0 -17
  187. data/spec/internal/app/models/grading_period_group.rb +0 -16
  188. data/spec/internal/app/models/group.rb +0 -28
  189. data/spec/internal/app/models/group_membership.rb +0 -26
  190. data/spec/internal/app/models/learning_outcome.rb +0 -30
  191. data/spec/internal/app/models/learning_outcome_result.rb +0 -54
  192. data/spec/internal/app/models/pseudonym.rb +0 -19
  193. data/spec/internal/app/models/role.rb +0 -27
  194. data/spec/internal/app/models/rubric.rb +0 -36
  195. data/spec/internal/app/models/rubric_assessment.rb +0 -23
  196. data/spec/internal/app/models/rubric_association.rb +0 -20
  197. data/spec/internal/app/models/score.rb +0 -18
  198. data/spec/internal/app/models/section.rb +0 -36
  199. data/spec/internal/app/models/submission.rb +0 -32
  200. data/spec/internal/app/models/term.rb +0 -46
  201. data/spec/internal/app/models/user.rb +0 -43
  202. data/spec/internal/app/models/user_observer.rb +0 -22
  203. data/spec/internal/app/services/live_events/assignment_event.rb +0 -43
  204. data/spec/internal/app/services/live_events/course_event.rb +0 -28
  205. data/spec/internal/app/services/live_events/course_section_event.rb +0 -28
  206. data/spec/internal/app/services/live_events/enrollment_event.rb +0 -30
  207. data/spec/internal/app/services/live_events/grade_event.rb +0 -36
  208. data/spec/internal/app/services/live_events/module_event.rb +0 -26
  209. data/spec/internal/app/services/live_events/module_item_event.rb +0 -25
  210. data/spec/internal/app/services/live_events/submission_event.rb +0 -28
  211. data/spec/internal/app/services/live_events/syllabus_event.rb +0 -31
  212. data/spec/internal/app/services/live_events/user_event.rb +0 -27
  213. data/spec/internal/db/migrate/20251029222307_create_users.rb +0 -26
  214. data/spec/internal/db/migrate/20251029222308_create_pseudonyms.rb +0 -24
  215. data/spec/internal/db/migrate/20251029222309_create_courses.rb +0 -30
  216. data/spec/internal/db/migrate/20251029222310_create_groups.rb +0 -29
  217. data/spec/internal/db/migrate/20251029222311_create_group_memberships.rb +0 -24
  218. data/spec/internal/db/migrate/20251029222312_create_accounts.rb +0 -23
  219. data/spec/internal/db/migrate/20251029222313_create_terms.rb +0 -24
  220. data/spec/internal/db/migrate/20251029222314_create_enrollments.rb +0 -31
  221. data/spec/internal/db/migrate/20251029222315_create_sections.rb +0 -27
  222. data/spec/internal/db/migrate/20251029222316_create_assignments.rb +0 -37
  223. data/spec/internal/db/migrate/20251029222317_create_submissions.rb +0 -32
  224. data/spec/internal/db/migrate/20251029222318_create_roles.rb +0 -24
  225. data/spec/internal/db/migrate/20251029222319_create_admins.rb +0 -26
  226. data/spec/internal/db/migrate/20251029222320_create_assignment_groups.rb +0 -28
  227. data/spec/internal/db/migrate/20251029222321_create_context_modules.rb +0 -28
  228. data/spec/internal/db/migrate/20251029222322_create_context_module_items.rb +0 -26
  229. data/spec/internal/db/migrate/20251029222324_create_grading_periods.rb +0 -28
  230. data/spec/internal/db/migrate/20251029222325_create_grading_period_groups.rb +0 -24
  231. data/spec/internal/db/migrate/20251029222326_create_content_migrations.rb +0 -30
  232. data/spec/internal/db/migrate/20251029222327_create_learning_outcomes.rb +0 -36
  233. data/spec/internal/db/migrate/20251029222328_create_learning_outcome_results.rb +0 -52
  234. data/spec/internal/db/migrate/20251029222330_create_rubrics.rb +0 -44
  235. data/spec/internal/db/migrate/20251029222331_create_rubric_associations.rb +0 -42
  236. data/spec/internal/db/migrate/20251029222332_create_rubric_assessments.rb +0 -37
  237. data/spec/internal/db/migrate/20251029222334_create_assignment_overrides.rb +0 -25
  238. data/spec/internal/db/migrate/20251029222335_create_scores.rb +0 -42
  239. data/spec/internal/db/migrate/20251029223411_create_user_observers.rb +0 -23
  240. data/spec/internal/db/migrate/20251029224009_create_course_nicknames.rb +0 -23
  241. data/spec/internal/db/migrate/20251029224010_create_course_progresses.rb +0 -26
  242. data/spec/support/fixtures/reports/provisioning_csv_unzipped/courses.csv +0 -3
  243. data/spec/support/fixtures/reports/provisioning_csv_unzipped/users.csv +0 -4
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "spec_helper"
4
+
5
+ # Focused on what this concern contributes on top of PandaPal: that it asks for
6
+ # INHERITED role labels, and that it keys its Session-persisted cache so the
7
+ # pre-traversal answers don't outlive the change. The traversal itself belongs
8
+ # to (and is tested by) PandaPal::Session#canvas_account_role_labels.
9
+ #
10
+ # panda_pal is not in this gem's bundle, so the session is a hand-rolled double
11
+ # — which is also the only way to assert the exact call that gets made.
12
+ RSpec.describe CanvasSync::Concerns::AbilityHelper do
13
+ let(:helper_class) do
14
+ Class.new do
15
+ include CanvasSync::Concerns::AbilityHelper
16
+
17
+ attr_reader :panda_pal_session
18
+
19
+ def initialize(session)
20
+ @panda_pal_session = session
21
+ end
22
+ end
23
+ end
24
+
25
+ # Shared bookkeeping: real #cache semantics (memoised into the session under
26
+ # an arbitrary key), plus a record of every call made.
27
+ session_base = Class.new do
28
+ attr_reader :cache_keys, :label_calls
29
+
30
+ def initialize(labels: [], site_admin: false, preset_cache: {})
31
+ @labels = labels
32
+ @site_admin = site_admin
33
+ @cache = preset_cache
34
+ @cache_keys = []
35
+ @label_calls = []
36
+ end
37
+
38
+ def cache(key)
39
+ @cache_keys << key
40
+ return @cache[key] if @cache.key?(key)
41
+
42
+ @cache[key] = yield
43
+ end
44
+
45
+ def canvas_site_admin?
46
+ @site_admin
47
+ end
48
+ end
49
+
50
+ # Declared exactly as PandaPal >= 5.17 declares it, so keyword detection runs
51
+ # against a real signature rather than a catch-all that would pass for the
52
+ # wrong reason.
53
+ let(:modern_session) do
54
+ Class.new(session_base) do
55
+ def canvas_account_role_labels(account = 'self', inherited: false)
56
+ @label_calls << [account, { inherited: inherited }]
57
+ @labels
58
+ end
59
+ end
60
+ end
61
+
62
+ # PandaPal < 5.17: no keyword at all. Passing one must not reach it.
63
+ let(:legacy_session) do
64
+ Class.new(session_base) do
65
+ def canvas_account_role_labels(account = 'self')
66
+ @label_calls << [account, {}]
67
+ @labels
68
+ end
69
+ end
70
+ end
71
+
72
+ # A host app decorating the Session and forwarding keywords.
73
+ let(:forwarding_session) do
74
+ Class.new(session_base) do
75
+ def canvas_account_role_labels(account = 'self', **opts)
76
+ @label_calls << [account, opts]
77
+ @labels
78
+ end
79
+ end
80
+ end
81
+
82
+ def helper_for(klass = nil, **kwargs)
83
+ helper_class.new((klass || modern_session).new(**kwargs))
84
+ end
85
+
86
+ describe "#canvas_account_admin?" do
87
+ it "asks PandaPal for inherited role labels" do
88
+ helper = helper_for(labels: ["AccountAdmin"])
89
+
90
+ expect(helper.canvas_account_admin?(300)).to be true
91
+ expect(helper.panda_pal_session.label_calls).to eq([[300, { inherited: true }]])
92
+ end
93
+
94
+ it "accepts the human-readable \"Account Admin\" label" do
95
+ expect(helper_for(labels: ["Account Admin"]).canvas_account_admin?(300)).to be true
96
+ end
97
+
98
+ it "is false when the user holds only non-admin roles anywhere in the chain" do
99
+ expect(helper_for(labels: ["Ops", "Department Admin"]).canvas_account_admin?(300)).to be false
100
+ end
101
+
102
+ it "is true for a Canvas site admin regardless of role labels" do
103
+ expect(helper_for(labels: [], site_admin: true).canvas_account_admin?(300)).to be true
104
+ end
105
+
106
+ it "keys its cache separately from the pre-traversal answer" do
107
+ helper = helper_for(labels: ["AccountAdmin"])
108
+
109
+ helper.canvas_account_admin?(:root)
110
+
111
+ expect(helper.panda_pal_session.cache_keys).to eq([[:canvas_account_admin?, :root, :inherited]])
112
+ end
113
+
114
+ # The regression this guards: a Session launched before the traversal
115
+ # existed has `false` persisted under the OLD key. Reusing that key would
116
+ # keep denying an admin who should now pass.
117
+ it "ignores a stale pre-traversal cache entry" do
118
+ helper = helper_class.new(
119
+ modern_session.new(labels: ["AccountAdmin"], preset_cache: { [:canvas_account_admin?, :root] => false })
120
+ )
121
+
122
+ expect(helper.canvas_account_admin?(:root)).to be true
123
+ end
124
+
125
+ it "still memoises within its own key" do
126
+ helper = helper_for(labels: ["AccountAdmin"])
127
+
128
+ helper.canvas_account_admin?(:root)
129
+ helper.canvas_account_admin?(:root)
130
+
131
+ expect(helper.panda_pal_session.label_calls.size).to eq(1)
132
+ end
133
+ end
134
+
135
+ describe "#canvas_super_user?" do
136
+ it "delegates to canvas_account_admin? for the install Account" do
137
+ helper = helper_for(labels: ["AccountAdmin"])
138
+
139
+ expect(helper.canvas_super_user?).to be true
140
+ expect(helper.panda_pal_session.label_calls).to eq([[:root, { inherited: true }]])
141
+ end
142
+
143
+ it "is false for a user with no admin role above or at the install Account" do
144
+ expect(helper_for(labels: []).canvas_super_user?).to be false
145
+ end
146
+ end
147
+
148
+ # Upgrade-order safety: an app on a CanvasSync with this change but PandaPal
149
+ # < 5.17 must not blow up an authorization check with ArgumentError.
150
+ describe "keyword detection" do
151
+ it "falls back to the non-inherited call against an older PandaPal" do
152
+ helper = helper_for(legacy_session, labels: ["AccountAdmin"])
153
+
154
+ expect(helper.canvas_account_admin?(300)).to be true
155
+ expect(helper.panda_pal_session.label_calls).to eq([[300, {}]])
156
+ end
157
+
158
+ it "treats a keyword-forwarding Session as supporting inherited" do
159
+ helper = helper_for(forwarding_session, labels: ["AccountAdmin"])
160
+
161
+ expect(helper.canvas_account_admin?(300)).to be true
162
+ expect(helper.panda_pal_session.label_calls).to eq([[300, { inherited: true }]])
163
+ end
164
+ end
165
+ end
@@ -170,22 +170,28 @@ RSpec.describe CanvasSync::Jobs::BeginSyncChainJob do
170
170
  end
171
171
 
172
172
  context 'a "<weekday>/<skip weeks>" schedule (e.g. "sunday/2")' do
173
+ # should_full_sync? asks DateTime.now (the system zone) which weekday it is, so
174
+ # these travel to local noon. Midnight in any single zone lands on the adjacent
175
+ # day for someone, which flips the weekday and the assertion with it.
176
+ let(:sunday) { Time.local(2024, 1, 7, 12, 0, 0) }
177
+ let(:monday) { Time.local(2024, 1, 8, 12, 0, 0) }
178
+
173
179
  it 'is true only on the matching weekday once the skip period has elapsed' do
174
- Timecop.travel(DateTime.new(2024, 1, 7)) do # a Sunday
180
+ Timecop.travel(sunday) do
175
181
  CanvasSync::SyncBatch.create!(status: 'completed', full_sync: true, batch_genre: 'default', started_at: 3.weeks.ago)
176
182
  expect(job.should_full_sync?('sunday/2')).to eq(true)
177
183
  end
178
184
  end
179
185
 
180
186
  it 'is false on the matching weekday if the skip period has not yet elapsed' do
181
- Timecop.travel(DateTime.new(2024, 1, 7)) do # a Sunday
187
+ Timecop.travel(sunday) do
182
188
  CanvasSync::SyncBatch.create!(status: 'completed', full_sync: true, batch_genre: 'default', started_at: 1.week.ago)
183
189
  expect(job.should_full_sync?('sunday/2')).to eq(false)
184
190
  end
185
191
  end
186
192
 
187
193
  it 'is false on a non-matching weekday even if the skip period has elapsed' do
188
- Timecop.travel(DateTime.new(2024, 1, 8)) do # a Monday
194
+ Timecop.travel(monday) do
189
195
  CanvasSync::SyncBatch.create!(status: 'completed', full_sync: true, batch_genre: 'default', started_at: 3.weeks.ago)
190
196
  expect(job.should_full_sync?('sunday/2')).to eq(false)
191
197
  end
File without changes
File without changes
@@ -86,6 +86,30 @@ RSpec.describe CanvasSync::Jobs::ReportSyncTask do
86
86
  end
87
87
 
88
88
  describe ".from_context" do
89
+ # Stands in for JobBatches::ContextHash, which reads keys via #[] but routes every other Hash
90
+ # method through #flatten. Flattening loads (and locks down) the whole ancestor chain, so
91
+ # from_context has to stay on #[] - anything else costs the Batch the ability to record its
92
+ # own progress later in the job.
93
+ class FlattenTattlingContext
94
+ attr_reader :flatten_count
95
+
96
+ def initialize(hash)
97
+ @hash = hash.with_indifferent_access
98
+ @flatten_count = 0
99
+ end
100
+
101
+ def [](key)
102
+ @hash[key]
103
+ end
104
+
105
+ def flatten
106
+ @flatten_count += 1
107
+ @hash
108
+ end
109
+
110
+ delegate_missing_to :flatten
111
+ end
112
+
89
113
  it "reconstructs task from batch context" do
90
114
  stub_batch_context({ canvas_term_id: 1 })
91
115
  allow(CanvasSync::JobBatches::Batch.current).to receive(:context)
@@ -96,6 +120,38 @@ RSpec.describe CanvasSync::Jobs::ReportSyncTask do
96
120
  expect(task).to be_a(SyncSpecsTask)
97
121
  expect(task.options).to eq({ include_all: true })
98
122
  end
123
+
124
+ it "returns nil when the context belongs to a non-report Batch" do
125
+ # A Batch elsewhere in the sync chain inherits a context from its parents, so the context is
126
+ # populated - it just has no :report_class.
127
+ stub_batch_context({ canvas_term_id: 1 })
128
+ allow(CanvasSync::JobBatches::Batch.current).to receive(:context)
129
+ .and_return({ sync_batch_id: 42, account_id: 'self' }.with_indifferent_access)
130
+
131
+ expect(CanvasSync::Jobs::ReportSyncTask.from_context).to be_nil
132
+ end
133
+
134
+ it "returns nil when there is no current Batch" do
135
+ allow(CanvasSync::JobBatches::Batch).to receive(:current).and_return(nil)
136
+
137
+ expect(CanvasSync::Jobs::ReportSyncTask.from_context).to be_nil
138
+ end
139
+
140
+ it "does not flatten the context, for a report Batch or any other" do
141
+ report_context = FlattenTattlingContext.new(
142
+ report_class: 'SyncSpecsTask', report_options: { include_all: true }
143
+ )
144
+ other_context = FlattenTattlingContext.new(sync_batch_id: 42)
145
+
146
+ [report_context, other_context].each do |context|
147
+ stub_batch_context({ canvas_term_id: 1 })
148
+ allow(CanvasSync::JobBatches::Batch.current).to receive(:context).and_return(context)
149
+
150
+ CanvasSync::Jobs::ReportSyncTask.from_context
151
+
152
+ expect(context.flatten_count).to eq(0)
153
+ end
154
+ end
99
155
  end
100
156
 
101
157
  describe described_class::StarterJob do
@@ -0,0 +1,41 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe CanvasSync::Jobs::SyncAccountsJob do
4
+ describe '#perform' do
5
+ before do
6
+ allow(CanvasSync::Jobs::SyncAccountsJob::ReportTask).to receive(:perform_later)
7
+ end
8
+
9
+ it 'syncs the root account and then invokes the report task' do
10
+ expect {
11
+ described_class.perform_now({})
12
+ }.to change { Account.count }.by(1)
13
+
14
+ expect(a_request(:get, 'http://test.instructure.com/api/v1/accounts/self')).to have_been_made
15
+ expect(CanvasSync::Jobs::SyncAccountsJob::ReportTask).to have_received(:perform_later).with({})
16
+ end
17
+
18
+ it 'uses account_id from the options when present' do
19
+ described_class.perform_now({ account_id: 42 })
20
+
21
+ expect(a_request(:get, 'http://test.instructure.com/api/v1/accounts/42')).to have_been_made
22
+ end
23
+
24
+ it 'falls back to account_id from the batch context' do
25
+ set_batch_context(account_id: 7)
26
+
27
+ described_class.perform_now({})
28
+
29
+ expect(a_request(:get, 'http://test.instructure.com/api/v1/accounts/7')).to have_been_made
30
+ end
31
+
32
+ it 'skips the root account sync when root_account is false' do
33
+ expect {
34
+ described_class.perform_now({ root_account: false })
35
+ }.not_to change { Account.count }
36
+
37
+ expect(a_request(:get, %r{/api/v1/accounts/})).not_to have_been_made
38
+ expect(CanvasSync::Jobs::SyncAccountsJob::ReportTask).to have_received(:perform_later)
39
+ end
40
+ end
41
+ end
File without changes
@@ -17,7 +17,7 @@ RSpec.describe CanvasSync::Jobs::SyncProvisioningReportJob do
17
17
  'courses' => true,
18
18
  enrollment_term_id: term.canvas_id,
19
19
  }
20
- })
20
+ }.with_indifferent_access)
21
21
  end
22
22
  end
23
23
 
@@ -33,7 +33,7 @@ RSpec.describe CanvasSync::Jobs::SyncProvisioningReportJob do
33
33
  'users' => true,
34
34
  'courses' => true,
35
35
  }
36
- })
36
+ }.with_indifferent_access)
37
37
  end
38
38
  end
39
39
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
data/spec/spec_helper.rb CHANGED
@@ -78,7 +78,9 @@ def open_canvas_fixture(name)
78
78
  JSON.parse(data)
79
79
  end
80
80
 
81
- def canvas_sync_client
81
+ # Mirrors the host-app hook CanvasSync.get_canvas_sync_client calls. It passes an
82
+ # account ID when one is configured, so this has to accept an optional argument.
83
+ def canvas_sync_client(account_id = nil)
82
84
  Bearcat::Client.new(token: 'cool-token', prefix: 'http://test.instructure.com')
83
85
  end
84
86
 
@@ -16,6 +16,10 @@ class FakeCanvas < Sinatra::Base
16
16
  json_response 200, 'admins.json'
17
17
  end
18
18
 
19
+ get '/api/v1/accounts/:id' do
20
+ json_response 200, 'account.json'
21
+ end
22
+
19
23
  get '/sample_report_download' do
20
24
  json_response 200, 'terms.json'
21
25
  end
@@ -0,0 +1,12 @@
1
+ {
2
+ "id": 1,
3
+ "name": "Test Root Account",
4
+ "workflow_state": "active",
5
+ "parent_account_id": null,
6
+ "root_account_id": null,
7
+ "uuid": "abcDEF1234567890abcDEF1234567890abcDEF12",
8
+ "default_storage_quota_mb": 500,
9
+ "default_user_storage_quota_mb": 50,
10
+ "default_group_storage_quota_mb": 50,
11
+ "default_time_zone": "America/Denver"
12
+ }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes