[CLXR-462][S] Add carrer offline sync#3975
[CLXR-462][S] Add carrer offline sync#3975Ahmed-Naguib93 wants to merge 6 commits intofeature/career-offline-modefrom
Conversation
refs: CLXR-462 builds: student affects: student release note: none test plan: none
refs: CLXR-462
refs: builds: affects: release note: test plan:
There was a problem hiding this comment.
Review: Horizon Offline Sync Background Task
Overall the implementation is clean and follows existing project patterns well. Good test coverage across the new interactors. A few issues worth addressing:
Issues
-
Failed syncs update the next-sync date as if they succeeded —
HSyncBackgroundTask.syncNextSession(HSyncBackgroundTask.swift~line 57): thereceiveCompletionclosure is invoked for both.finishedand.failure(_:)without distinguishing between them, soscheduleInteractor.updateNextSyncDateis called even when the network call fails. This pushes the next sync window forward even though no data was actually refreshed. -
Concrete class subclassing used for mocking instead of protocols —
HSyncNextDateInteractorandHSyncScheduleInteractorarepublic classtypes that the test suite mocks by subclassing (MockHSyncNextDateInteractor: HSyncNextDateInteractor). This is a testing anti-pattern that prevents the classes from beingfinaland tightly couples the tests to the concrete implementations. Prefer extracting a protocol (e.g.HSyncNextDateInteractorProtocol) and injecting it, which is how the existingCourseSyncInteractorfamily is structured. -
Hardcoded
uniqueIDformat in test assertion —HSyncBackgroundTaskRequestTests.swiftline 43 assertsmock.receivedSessionIDs.sorted() == ["testurl.com-user-1", "testurl2.com-user-2"]. This bakes in the internalLoginSession.uniqueIDformat (host + "-" + userID). If that format ever changes the test fails for the wrong reason. A more robust check would verify that the IDs passed in match the IDs of the two sessions being used, without hardcoding the derived string. -
OfflineType.swiftcopyright year is 2026 — all other new files in this PR use2025-present. Likely a copy/paste inconsistency. -
OfflineTypehas no access modifier — theenumdefaults tointernal, which may be intentional, but it sits alongsidepublictypes in the same directory. Worth confirming this is deliberate and not an accidental omission.
Positive notes
- The sequential-session sync loop in
HSyncBackgroundTask(processing one session at a time viasyncNextSession) is a sensible approach for background tasks with limited OS budget. SessionDefaultsextensions are well-structured and consistent with existing patterns.HSyncAccountsInteractorTeststeardown correctly resetsSessionDefaultsfor each session, avoiding test pollution.- Test coverage for
HSyncNextDateInteractoris thorough — edge cases (empty list, disabled sync, missing date, empty items) are all exercised.
Affected Apps: StudentBuilds: Student❌ Swift lint |
BuildsCommit: feat: add more test cases (ba12e8c) |
Affected Apps: StudentBuilds: Student
|
| import Core | ||
|
|
||
| public final class HSyncBackgroundTaskRequest: BGProcessingTaskRequest { | ||
| public static let ID = "com.instructure.icanvas.horizon-sync" |
There was a problem hiding this comment.
let's call this com.instructure.icanvas.career-sync
| <key>BGTaskSchedulerPermittedIdentifiers</key> | ||
| <array> | ||
| <string>$(PRODUCT_BUNDLE_IDENTIFIER).offline-sync</string> | ||
| <string>$(PRODUCT_BUNDLE_IDENTIFIER).horizon-sync</string> |
There was a problem hiding this comment.
let's call this career-offline-sync
| if AppEnvironment.shared.app == .horizon { | ||
| HSyncScheduleInteractorLive().scheduleNextSync() | ||
| } else { | ||
| OfflineSyncScheduleInteractor().scheduleNextSync() | ||
| } |
There was a problem hiding this comment.
- With this if-else condition based on the currently logged in user, either academic or horizon accounts will sync but if we have mixed academic/horizon users one of them won't sync.
- If we trigger two background syncs, the OS will throttle down and one of them won't run because the background wake-up frequency would be too high.
- The academic sync logic already iterates through all accounts and syncs each one where this feature is turned on. I think the best approach would be to share the offline sync background task between the two experiences and execute the proper sync logic based on the user's experience.
feat: add offline sync
refs: CLXR-462
builds: student
affects: student
release note: none