apple_kit

Apple-native services for Radroots iOS and macOS apps
git clone https://radroots.dev/git/apple_kit.git
Log | Files | Refs | README | LICENSE

RadrootsLegacyMigrationCancellationTests.swift (2344B)


      1 import Foundation
      2 import RadrootsKitTesting
      3 import Testing
      4 
      5 @testable import RadrootsKit
      6 
      7 @Test func cancellationAfterImportRetainsCommittedKeyAndLegacyUntilExplicitRecovery() async throws {
      8     let key = RadrootsSecureStoreKey(namespace: "legacy", name: "selected_secret_hex")
      9     let store = CancelOnActiveReadStore()
     10     let legacy = Data(String(repeating: "01", count: 32).utf8)
     11     try store.put(legacy, for: key)
     12     let custody = try RadrootsIdentityCustody(
     13         configuration: RadrootsIdentityCustodyConfiguration(namespace: UUID().uuidString.lowercased()),
     14         secureStore: store, metadataStore: RadrootsInMemoryIdentityMetadataStore(),
     15         userPresence: RadrootsFakeUserPresence()
     16     )
     17     let task = Task { try await custody.migrateLegacyIdentity(from: key) }
     18     await #expect(throws: RadrootsIdentityCustodyError.cancelled) { try await task.value }
     19     let retained = await custody.snapshot()
     20     let expected = try #require(retained.identity?.publicKeyHex)
     21     #expect(retained.state == .unlocked)
     22     #expect(try store.get(key) == legacy)
     23     let recovered = try await custody.migrateLegacyIdentity(from: key, expectedPublicKeyHex: expected)
     24     #expect(recovered.identity == retained.identity)
     25     #expect(try store.get(key) == nil)
     26 }
     27 
     28 // The backing store synchronizes bytes; the lock protects the one-shot fault.
     29 private final class CancelOnActiveReadStore: RadrootsSecureStore, @unchecked Sendable {
     30     private let backing = RadrootsInMemorySecureStore()
     31     private let lock = NSLock()
     32     private var shouldCancel = true
     33 
     34     func put(_ value: Data, for key: RadrootsSecureStoreKey, policy: RadrootsSecretAccessPolicy) throws {
     35         try backing.put(value, for: key, policy: policy)
     36     }
     37     func contains(_ key: RadrootsSecureStoreKey) throws -> Bool { try backing.contains(key) }
     38     func delete(_ key: RadrootsSecureStoreKey) throws { try backing.delete(key) }
     39     func deleteNamespace(_ namespace: String) throws { try backing.deleteNamespace(namespace) }
     40     func get(_ key: RadrootsSecureStoreKey) throws -> Data? {
     41         let value = try backing.get(key)
     42         lock.lock()
     43         let cancel = key.name == "active_secret_v1" && shouldCancel
     44         if cancel { shouldCancel = false }
     45         lock.unlock()
     46         if cancel { withUnsafeCurrentTask { $0?.cancel() } }
     47         return value
     48     }
     49 }