AuthorIdentityTests.swift (3403B)
1 import CloudKit 2 import Foundation 3 import Testing 4 5 @testable import Crossmate 6 7 @Suite("AuthorIdentity") 8 @MainActor 9 struct AuthorIdentityTests { 10 11 private func makeIsolatedStorage() -> UserDefaults { 12 UserDefaults(suiteName: "test-authoridentity-\(UUID().uuidString)")! 13 } 14 15 @Test("Before any account resolves, falls back to a device-local id") 16 func initialStateUsesFallback() { 17 let identity = AuthorIdentity(storage: makeIsolatedStorage()) 18 #expect(identity.isFallback) 19 #expect(identity.currentID?.hasPrefix("local-") == true) 20 } 21 22 @Test("Fallback id is stable across instances sharing storage") 23 func fallbackPersistsAcrossInstances() { 24 let storage = makeIsolatedStorage() 25 let first = AuthorIdentity(storage: storage).currentID 26 let second = AuthorIdentity(storage: storage).currentID 27 #expect(first?.hasPrefix("local-") == true) 28 #expect(first == second) 29 } 30 31 @Test("Testing initializer seeds currentID") 32 func testingInitializerSeedsValue() { 33 let identity = AuthorIdentity(testing: "_abc") 34 #expect(identity.currentID == "_abc") 35 #expect(!identity.isFallback) 36 } 37 38 @Test("refresh with unavailable account leaves the fallback in place") 39 func refreshFailurePreservesFallback() async { 40 let identity = AuthorIdentity(storage: makeIsolatedStorage()) 41 let fallback = identity.currentID 42 // The default CKContainer without a signed-in account fails the fetch. 43 // The contract is: on failure, currentID is unchanged — still the 44 // fallback — so offline writes keep a consistent author. 45 let containerWithNoAccount = CloudContainer.container 46 await identity.refresh(using: containerWithNoAccount) 47 #expect(identity.currentID == fallback) 48 #expect(identity.isFallback) 49 } 50 51 @Test("A resolved iCloud id takes precedence over the fallback") 52 func resolvedIDPersistsAcrossInstances() { 53 let storage = makeIsolatedStorage() 54 storage.set("_persisted", forKey: "AuthorIdentity.currentID") 55 let identity = AuthorIdentity(storage: storage) 56 #expect(identity.currentID == "_persisted") 57 #expect(!identity.isFallback) 58 } 59 } 60 61 /// `AppServices.accountSwitchRequiresPurge` gates the local-store wipe on an 62 /// account-change event. Only a switch between two *known, different* accounts 63 /// should purge. 64 @Suite("Account switch purge gate") 65 @MainActor 66 struct AccountSwitchPurgeTests { 67 68 @Test("Switch to a different account purges") 69 func differentAccountPurges() { 70 #expect(AppServices.accountSwitchRequiresPurge(previousID: "_alice", newID: "_bob")) 71 } 72 73 @Test("Same account does not purge") 74 func sameAccountKeeps() { 75 #expect(!AppServices.accountSwitchRequiresPurge(previousID: "_alice", newID: "_alice")) 76 } 77 78 @Test("First sign-in (no previous ID) does not purge") 79 func firstSignInKeeps() { 80 #expect(!AppServices.accountSwitchRequiresPurge(previousID: nil, newID: "_bob")) 81 } 82 83 @Test("Transient sign-out (no new ID) does not purge") 84 func signOutKeeps() { 85 #expect(!AppServices.accountSwitchRequiresPurge(previousID: "_alice", newID: nil)) 86 } 87 88 @Test("Both unknown does not purge") 89 func bothUnknownKeeps() { 90 #expect(!AppServices.accountSwitchRequiresPurge(previousID: nil, newID: nil)) 91 } 92 }