"oAI" reads as easily confused with OpenAI, both visually and in casual conversation. Renamed to "Confab" throughout: Xcode target/scheme/bundle ID (com.oai.Confab), Info.plist and Help Book identity, all user-facing UI text, internal Log subsystem and color identifiers, localization catalogs (6 languages, including a proper reworded/retranslated Intel-deprecation notice), Help Book HTML content, and docs (README/DEVELOPMENT/PRIVACY/SECURITY). Deliberately cosmetic-only: the on-disk data folder (~/Library/Application Support/oAI/), database/backup filenames, Keychain service identifiers, and EncryptionService's key-derivation inputs are all left untouched so existing conversations, settings, and stored API keys survive the update with zero migration and no re-entering credentials. Verified live: a real signed build successfully decrypted a stored API key and loaded an existing conversation database after the bundle ID change. Also includes a small already-completed, previously uncommitted model-release-date feature (ModelInfo/OpenRouterModels/ OpenRouterProvider/ModelInfoView) that happened to share several files with this rename. Gitignored on this branch and updated on disk but not part of this commit: CLAUDE.md, RELEASE_NOTES.md, and the build*.sh scripts.
106 lines
4.1 KiB
Swift
106 lines
4.1 KiB
Swift
//
|
|
// BackupServiceTests.swift
|
|
// oAITests
|
|
//
|
|
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
|
|
// Copyright (C) 2026 Rune Olsen
|
|
|
|
import Testing
|
|
import Foundation
|
|
@testable import Confab
|
|
|
|
@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))
|
|
}
|
|
}
|