T-iOS-5: pure reconnect reducer (1s→30s cap, mirrors terminal-session.ts) + 25s PingScheduler, 16 tests
T-iOS-6: gate epoch tracker (rising-edge semantics, canDecide guard) + AwayDigest reducer, 25 tests
T-iOS-7: HostRegistry with SecItemShim keychain seam, 30 tests, 88.1% own-src coverage
T-iOS-8: APIClient (Origin iff-G invariant) + two-step pairing probe, 45 tests, 98.1% coverage
Contract ruling: probe returns Result<HostEndpoint,_> (Host{id,name} built by pairing VM) —
resolves frozen-contract contradiction reported via BLOCKED protocol; adds Tunables.pairingProbeTimeout(10s)
Verified: 178 tests green across 5 packages; coverage gates pass; zero Owns violations
98 lines
4.2 KiB
Swift
98 lines
4.2 KiB
Swift
import Foundation
|
|
import Security
|
|
import os
|
|
import HostRegistry
|
|
|
|
/// In-memory keychain double behind the `SecItemShim` seam (plan §3.3):
|
|
/// unsigned `swift test` binaries get errSecMissingEntitlement (-34018) from
|
|
/// the real data-protection keychain, so store logic is tested against this.
|
|
///
|
|
/// Behavioral single-slot model (the store uses exactly one service+account):
|
|
/// - add: duplicate if a value is stored; otherwise stores kSecValueData
|
|
/// - update: itemNotFound when empty; otherwise replaces the stored value
|
|
/// - copyMatching: itemNotFound when empty; otherwise returns the stored value
|
|
/// - delete: itemNotFound when empty; otherwise clears
|
|
///
|
|
/// `force*` knobs override the next status/result (forced statuses do NOT
|
|
/// touch storage — the fake stays dumb). All calls are recorded verbatim so
|
|
/// tests can assert the §5.3 attribute dictionaries character-for-character.
|
|
final class FakeSecItemShim: SecItemShim, Sendable {
|
|
struct UpdateCall: Sendable {
|
|
let query: [String: any Sendable]
|
|
let attributes: [String: any Sendable]
|
|
}
|
|
|
|
private struct State: Sendable {
|
|
var storedData: Data?
|
|
var addCalls: [[String: any Sendable]] = []
|
|
var updateCalls: [UpdateCall] = []
|
|
var copyCalls: [[String: any Sendable]] = []
|
|
var deleteCalls: [[String: any Sendable]] = []
|
|
var forcedAddStatus: OSStatus?
|
|
var forcedUpdateStatus: OSStatus?
|
|
var forcedCopyStatus: OSStatus?
|
|
var forcedDeleteStatus: OSStatus?
|
|
var forcedCopyData: Data?
|
|
}
|
|
|
|
private let state = OSAllocatedUnfairLock(initialState: State())
|
|
|
|
// MARK: - SecItemShim
|
|
|
|
func add(_ attributes: [String: any Sendable]) -> OSStatus {
|
|
state.withLock { s in
|
|
s.addCalls.append(attributes)
|
|
if let forced = s.forcedAddStatus { return forced }
|
|
guard s.storedData == nil else { return errSecDuplicateItem }
|
|
s.storedData = attributes[kSecValueData as String] as? Data
|
|
return errSecSuccess
|
|
}
|
|
}
|
|
|
|
func update(query: [String: any Sendable],
|
|
attributesToUpdate: [String: any Sendable]) -> OSStatus {
|
|
state.withLock { s in
|
|
s.updateCalls.append(UpdateCall(query: query, attributes: attributesToUpdate))
|
|
if let forced = s.forcedUpdateStatus { return forced }
|
|
guard s.storedData != nil else { return errSecItemNotFound }
|
|
s.storedData = attributesToUpdate[kSecValueData as String] as? Data
|
|
return errSecSuccess
|
|
}
|
|
}
|
|
|
|
func copyMatching(_ query: [String: any Sendable]) -> (status: OSStatus, data: Data?) {
|
|
state.withLock { s in
|
|
s.copyCalls.append(query)
|
|
if let forced = s.forcedCopyStatus { return (forced, nil) }
|
|
if let forcedData = s.forcedCopyData { return (errSecSuccess, forcedData) }
|
|
guard let data = s.storedData else { return (errSecItemNotFound, nil) }
|
|
return (errSecSuccess, data)
|
|
}
|
|
}
|
|
|
|
func delete(_ query: [String: any Sendable]) -> OSStatus {
|
|
state.withLock { s in
|
|
s.deleteCalls.append(query)
|
|
if let forced = s.forcedDeleteStatus { return forced }
|
|
guard s.storedData != nil else { return errSecItemNotFound }
|
|
s.storedData = nil
|
|
return errSecSuccess
|
|
}
|
|
}
|
|
|
|
// MARK: - Test controls
|
|
|
|
func forceAddStatus(_ status: OSStatus) { state.withLock { $0.forcedAddStatus = status } }
|
|
func forceUpdateStatus(_ status: OSStatus) { state.withLock { $0.forcedUpdateStatus = status } }
|
|
func forceCopyStatus(_ status: OSStatus) { state.withLock { $0.forcedCopyStatus = status } }
|
|
func forceDeleteStatus(_ status: OSStatus) { state.withLock { $0.forcedDeleteStatus = status } }
|
|
func forceCopyData(_ data: Data) { state.withLock { $0.forcedCopyData = data } }
|
|
|
|
// MARK: - Recorded calls
|
|
|
|
var recordedAdds: [[String: any Sendable]] { state.withLock { $0.addCalls } }
|
|
var recordedUpdates: [UpdateCall] { state.withLock { $0.updateCalls } }
|
|
var recordedCopies: [[String: any Sendable]] { state.withLock { $0.copyCalls } }
|
|
var recordedDeletes: [[String: any Sendable]] { state.withLock { $0.deleteCalls } }
|
|
}
|