// // BackupServiceTests.swift // oAITests // // SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0 // Copyright (C) 2026 Rune Olsen import Testing import Foundation @testable import oAI @Suite("BackupService.decideFavoritesSync") struct BackupServiceFavoritesSyncTests { private let now = Date(timeIntervalSince1970: 1_700_000_000) @Test("No remote copy yet -- push local state") func noRemoteCopy() { let decision = BackupService.decideFavoritesSync(localIds: ["a", "b"], localUpdatedAt: "", remote: nil, now: now) #expect(decision == .pushLocal) } @Test("Remote is newer -- apply it locally") func remoteNewer() { let remote = FavoritesPayload(updatedAt: "2026-02-01T00:00:00Z", ids: ["x", "y"]) let decision = BackupService.decideFavoritesSync( localIds: ["a"], localUpdatedAt: "2026-01-01T00:00:00Z", remote: remote, now: now ) #expect(decision == .applyRemote(ids: ["x", "y"], updatedAt: "2026-02-01T00:00:00Z")) } @Test("Local is newer -- push local state, don't touch remote") func localNewer() { let remote = FavoritesPayload(updatedAt: "2026-01-01T00:00:00Z", ids: ["x"]) let decision = BackupService.decideFavoritesSync( localIds: ["a"], localUpdatedAt: "2026-02-01T00:00:00Z", remote: remote, now: now ) #expect(decision == .pushLocal) } @Test("Tied timestamps with different sets -- merge via union and push") func tiedTimestampsDifferentSets() { let remote = FavoritesPayload(updatedAt: "", ids: ["b", "c"]) let decision = BackupService.decideFavoritesSync(localIds: ["a", "b"], localUpdatedAt: "", remote: remote, now: now) guard case .mergeAndPush(let ids, let updatedAt) = decision else { Issue.record("Expected .mergeAndPush") return } #expect(ids == ["a", "b", "c"]) #expect(!updatedAt.isEmpty) // stamped with `now`, not left blank } @Test("Tied timestamps with identical sets -- no-op, already in sync") func tiedTimestampsIdenticalSets() { let remote = FavoritesPayload(updatedAt: "2026-01-01T00:00:00Z", ids: ["a", "b"]) let decision = BackupService.decideFavoritesSync( localIds: ["a", "b"], localUpdatedAt: "2026-01-01T00:00:00Z", remote: remote, now: now ) #expect(decision == .noop) } } @Suite("BackupService.isBackupDue") struct BackupServiceAutoBackupTests { private let now = Date(timeIntervalSince1970: 1_700_000_000) private let oneHour: TimeInterval = 3600 @Test("Manual frequency is never due") func manualNeverDue() { #expect(!BackupService.isBackupDue(frequency: "manual", lastBackupDate: nil, now: now)) #expect(!BackupService.isBackupDue(frequency: "manual", lastBackupDate: now.addingTimeInterval(-1_000_000), now: now)) } @Test("No prior backup at all is always due, regardless of frequency") func noPriorBackupIsAlwaysDue() { #expect(BackupService.isBackupDue(frequency: "daily", lastBackupDate: nil, now: now)) #expect(BackupService.isBackupDue(frequency: "weekly", lastBackupDate: nil, now: now)) } @Test("Daily: not due before 24 hours have passed, due at or after") func dailyThreshold() { let justUnder = now.addingTimeInterval(-(24 * oneHour - 1)) let exactly = now.addingTimeInterval(-(24 * oneHour)) #expect(!BackupService.isBackupDue(frequency: "daily", lastBackupDate: justUnder, now: now)) #expect(BackupService.isBackupDue(frequency: "daily", lastBackupDate: exactly, now: now)) } @Test("Weekly: not due before 7 days have passed, due at or after") func weeklyThreshold() { let justUnder = now.addingTimeInterval(-(7 * 24 * oneHour - 1)) let exactly = now.addingTimeInterval(-(7 * 24 * oneHour)) #expect(!BackupService.isBackupDue(frequency: "weekly", lastBackupDate: justUnder, now: now)) #expect(BackupService.isBackupDue(frequency: "weekly", lastBackupDate: exactly, now: now)) } }