import Testing @testable import Kordant import BackgroundTasks import SwiftUI // MARK: - SyncStatus Tests struct SyncStatusTests { @Test("SyncStatus defaults are correct") func defaults() { let status = SyncStatus() #expect(status.lastSuccessfulSync == nil) #expect(status.lastSyncAttempt == nil) #expect(status.currentSyncState == .idle) #expect(status.syncError == nil) #expect(status.totalBytesTransferred == 0) #expect(status.deltaSyncSavings == 0) #expect(status.isLowPowerMode == false) #expect(status.isOffline == false) } @Test("SyncStatus lastSyncDescription shows Never when no sync") func lastSyncDescriptionNever() { let status = SyncStatus() #expect(status.lastSyncDescription == "Never") } @Test("SyncStatus lastSyncDescription shows relative time") func lastSyncDescriptionRelative() { var status = SyncStatus() status.lastSuccessfulSync = Date() // Should show something like "now" or "0s" #expect(!status.lastSyncDescription.isEmpty) #expect(status.lastSyncDescription != "Never") } @Test("SyncStatus bytesTransferredString formats bytes") func bytesTransferredString() { var status = SyncStatus() status.totalBytesTransferred = 1024 #expect(status.bytesTransferredString == "1 KB") } @Test("SyncStatus deltaSyncSavingsPercent is 0 when no data") func deltaSyncSavingsZero() { let status = SyncStatus() #expect(status.deltaSyncSavingsPercent == 0) } @Test("SyncStatus deltaSyncSavingsPercent calculates correctly") func deltaSyncSavingsCalculation() { var status = SyncStatus() status.totalBytesTransferred = 500 status.deltaSyncSavings = 500 // 500 / (500 + 500) * 100 = 50% #expect(status.deltaSyncSavingsPercent == 50.0) } @Test("SyncStatus equality compares key fields") func equality() { var status1 = SyncStatus() var status2 = SyncStatus() status1.lastSuccessfulSync = Date() status2.lastSuccessfulSync = status1.lastSuccessfulSync status1.currentSyncState = .completed status2.currentSyncState = .completed #expect(status1 == status2) } } // MARK: - SyncStatusManager Tests @MainActor struct SyncStatusManagerTests { @Test("SyncStatusManager starts with idle state") func initialState() { let manager = SyncStatusManager(defaults: UserDefaults(suiteName: UUID().uuidString)!) #expect(manager.status.currentSyncState == .idle) } @Test("SyncStatusManager startSync updates state") func startSync() { let manager = SyncStatusManager(defaults: UserDefaults(suiteName: UUID().uuidString)!) manager.startSync(.appRefresh) #expect(manager.status.currentSyncState == .syncing) #expect(manager.status.lastSyncAttempt != nil) #expect(manager.status.lastSyncOperation == .appRefresh) #expect(manager.status.syncError == nil) } @Test("SyncStatusManager completeSync updates state") func completeSync() { let manager = SyncStatusManager(defaults: UserDefaults(suiteName: UUID().uuidString)!) manager.startSync(.appRefresh) manager.completeSync(bytesTransferred: 1024, deltaSavings: 512) #expect(manager.status.currentSyncState == .completed) #expect(manager.status.lastSuccessfulSync != nil) #expect(manager.status.syncError == nil) #expect(manager.status.totalBytesTransferred == 1024) #expect(manager.status.deltaSyncSavings == 512) } @Test("SyncStatusManager failSync updates state") func failSync() { let manager = SyncStatusManager(defaults: UserDefaults(suiteName: UUID().uuidString)!) manager.startSync(.appRefresh) manager.failSync(with: "Network error") #expect(manager.status.currentSyncState == .failed) #expect(manager.status.syncError == "Network error") } @Test("SyncStatusManager setOffline updates state") func setOffline() { let manager = SyncStatusManager(defaults: UserDefaults(suiteName: UUID().uuidString)!) manager.setOffline(true) #expect(manager.status.isOffline == true) #expect(manager.status.currentSyncState == .offline) manager.setOffline(false) #expect(manager.status.isOffline == false) #expect(manager.status.currentSyncState == .idle) } @Test("SyncStatusManager accumulates bytes across syncs") func accumulateBytes() { let manager = SyncStatusManager(defaults: UserDefaults(suiteName: UUID().uuidString)!) manager.completeSync(bytesTransferred: 100, deltaSavings: 50) manager.completeSync(bytesTransferred: 200, deltaSavings: 100) #expect(manager.status.totalBytesTransferred == 300) #expect(manager.status.deltaSyncSavings == 150) } @Test("SyncStatusManager persists and restores state") func persistence() { let defaults = UserDefaults(suiteName: UUID().uuidString)! let manager1 = SyncStatusManager(defaults: defaults) manager1.completeSync(bytesTransferred: 500, deltaSavings: 200) let manager2 = SyncStatusManager(defaults: defaults) #expect(manager2.status.totalBytesTransferred == 500) #expect(manager2.status.deltaSyncSavings == 200) } @Test("SyncStatusManager resets syncing state on launch") func resetSyncingStateOnLaunch() { let defaults = UserDefaults(suiteName: UUID().uuidString)! let manager1 = SyncStatusManager(defaults: defaults) manager1.startSync(.appRefresh) // Simulate crash — state is syncing let manager2 = SyncStatusManager(defaults: defaults) // Should reset to idle #expect(manager2.status.currentSyncState == .idle) } } // MARK: - BackgroundTaskID Tests struct BackgroundTaskIDTests { @Test("BackgroundTaskID has correct raw values") func rawValues() { #expect(BackgroundTaskID.appRefresh.rawValue == "com.frenocorp.kordant.refresh") #expect(BackgroundTaskID.darkWebScan.rawValue == "com.frenocorp.kordant.darkWebScan") #expect(BackgroundTaskID.spamDatabaseUpdate.rawValue == "com.frenocorp.kordant.spamDatabaseUpdate") } } // MARK: - BackgroundSyncService Tests struct BackgroundSyncServiceTests { @Test("BackgroundSyncService minimumFetchInterval is 15 minutes") func minimumFetchInterval() { #expect(BackgroundSyncService.minimumFetchInterval == 15 * 60) } @Test("BackgroundSyncService lowPowerFetchInterval is 30 minutes") func lowPowerFetchInterval() { #expect(BackgroundSyncService.lowPowerFetchInterval == 30 * 60) } @Test("DeltaSyncResult hasChanges is true when any data changed") func deltaSyncResultHasChanges() { let result = DeltaSyncResult( alertsChanged: true, exposuresChanged: false, watchlistChanged: false, bytesTransferred: 100, deltaSavings: 50, newAlerts: [], newExposures: [] ) #expect(result.hasChanges == true) } @Test("DeltaSyncResult hasChanges is false when nothing changed") func deltaSyncResultNoChanges() { let result = DeltaSyncResult( alertsChanged: false, exposuresChanged: false, watchlistChanged: false, bytesTransferred: 0, deltaSavings: 0, newAlerts: [], newExposures: [] ) #expect(result.hasChanges == false) } @Test("SyncOperation raw values are correct") func syncOperationRawValues() { #expect(SyncOperation.appRefresh.rawValue == "app_refresh") #expect(SyncOperation.darkWebScan.rawValue == "dark_web_scan") #expect(SyncOperation.spamDatabaseUpdate.rawValue == "spam_database_update") #expect(SyncOperation.pushNotificationSync.rawValue == "push_notification_sync") #expect(SyncOperation.manual.rawValue == "manual") } @Test("SyncState raw values are correct") func syncStateRawValues() { #expect(SyncState.idle.rawValue == "idle") #expect(SyncState.syncing.rawValue == "syncing") #expect(SyncState.completed.rawValue == "completed") #expect(SyncState.failed.rawValue == "failed") #expect(SyncState.offline.rawValue == "offline") } } // MARK: - BackgroundTaskScheduler Tests struct BackgroundTaskSchedulerTests { @Test("BackgroundTaskScheduler minimumRefreshInterval is 15 minutes") func minimumRefreshInterval() { let scheduler = BackgroundTaskScheduler() // We can't directly access private properties, but we can verify // the scheduling logic works through the public API #expect(scheduler.shouldDeferBackgroundTasks() == false) } @Test("BackgroundTaskScheduler should not defer when recently synced in normal mode") func shouldNotDeferNormalMode() { let scheduler = BackgroundTaskScheduler() #expect(scheduler.shouldDeferBackgroundTasks() == false) } @Test("BackgroundTaskScheduler registerAllTasks does not crash") func registerAllTasks() { let scheduler = BackgroundTaskScheduler() // Should not throw scheduler.registerAllTasks() } @Test("BackgroundTaskScheduler scheduleAllTasks does not crash") func scheduleAllTasks() { let scheduler = BackgroundTaskScheduler() // Should not throw scheduler.scheduleAllTasks() } } // MARK: - ETagCacheEntry Tests struct ETagCacheEntryTests { @Test("ETagCacheEntry is not stale when fresh") func freshEntry() { let entry = ETagCacheEntry(etag: "abc123", lastModified: "2024-01-01", timestamp: Date()) #expect(entry.isStale == false) } @Test("ETagCacheEntry is stale after 10 minutes") func staleEntry() { let entry = ETagCacheEntry( etag: "abc123", lastModified: "2024-01-01", timestamp: Date().addingTimeInterval(-601) // Just over 10 minutes ) #expect(entry.isStale == true) } @Test("ETagCacheEntry is stale at exactly 10 minutes") func exactlyStale() { let entry = ETagCacheEntry( etag: "abc123", lastModified: "2024-01-01", timestamp: Date().addingTimeInterval(-600) // Exactly 10 minutes ) #expect(entry.isStale == true) } @Test("ETagCacheEntry is Codable") func codable() throws { let entry = ETagCacheEntry(etag: "abc123", lastModified: "2024-01-01", timestamp: Date()) let data = try JSONEncoder().encode(entry) let decoded = try JSONDecoder().decode(ETagCacheEntry.self, from: data) #expect(decoded.etag == "abc123") #expect(decoded.lastModified == "2024-01-01") } } // MARK: - SyncOperation Codable Tests struct SyncOperationCodableTests { @Test("SyncOperation encodes and decodes correctly") func encodeDecode() throws { let operation = SyncOperation.appRefresh let data = try JSONEncoder().encode(operation) let decoded = try JSONDecoder().decode(SyncOperation.self, from: data) #expect(decoded == .appRefresh) } } // MARK: - SyncState Codable Tests struct SyncStateCodableTests { @Test("SyncState encodes and decodes correctly") func encodeDecode() throws { let state = SyncState.syncing let data = try JSONEncoder().encode(state) let decoded = try JSONDecoder().decode(SyncState.self, from: data) #expect(decoded == .syncing) } }