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
92 lines
3.1 KiB
Swift
92 lines
3.1 KiB
Swift
import Foundation
|
|
import Testing
|
|
import HostRegistry
|
|
|
|
// UserDefaultsLastSessionStore over an injectable, per-test-unique suite
|
|
// (plan §3.3: 非机密;lastSessionId 存取/清除)。
|
|
|
|
@Test("lastSessionId 存取:set 后读回同一 UUID")
|
|
func setThenGetReturnsSameSessionId() throws {
|
|
// Arrange
|
|
let suiteName = Fixtures.makeSuiteName()
|
|
let defaults = try #require(UserDefaults(suiteName: suiteName))
|
|
defer { defaults.removePersistentDomain(forName: suiteName) }
|
|
let store = UserDefaultsLastSessionStore(defaults: defaults)
|
|
let host = UUID()
|
|
let session = UUID()
|
|
|
|
// Act
|
|
store.setLastSessionId(session, host: host)
|
|
|
|
// Assert
|
|
#expect(store.lastSessionId(host: host) == session)
|
|
}
|
|
|
|
@Test("未设置过的 host:lastSessionId 返回 nil")
|
|
func unknownHostReturnsNil() throws {
|
|
// Arrange
|
|
let suiteName = Fixtures.makeSuiteName()
|
|
let defaults = try #require(UserDefaults(suiteName: suiteName))
|
|
defer { defaults.removePersistentDomain(forName: suiteName) }
|
|
let store = UserDefaultsLastSessionStore(defaults: defaults)
|
|
|
|
// Act / Assert
|
|
#expect(store.lastSessionId(host: UUID()) == nil)
|
|
}
|
|
|
|
@Test("set nil:清除既有 lastSessionId")
|
|
func settingNilClearsStoredSessionId() throws {
|
|
// Arrange
|
|
let suiteName = Fixtures.makeSuiteName()
|
|
let defaults = try #require(UserDefaults(suiteName: suiteName))
|
|
defer { defaults.removePersistentDomain(forName: suiteName) }
|
|
let store = UserDefaultsLastSessionStore(defaults: defaults)
|
|
let host = UUID()
|
|
store.setLastSessionId(UUID(), host: host)
|
|
|
|
// Act
|
|
store.setLastSessionId(nil, host: host)
|
|
|
|
// Assert
|
|
#expect(store.lastSessionId(host: host) == nil)
|
|
}
|
|
|
|
@Test("不同 host 键隔离:互不覆盖")
|
|
func distinctHostsDoNotCollide() throws {
|
|
// Arrange
|
|
let suiteName = Fixtures.makeSuiteName()
|
|
let defaults = try #require(UserDefaults(suiteName: suiteName))
|
|
defer { defaults.removePersistentDomain(forName: suiteName) }
|
|
let store = UserDefaultsLastSessionStore(defaults: defaults)
|
|
let hostA = UUID()
|
|
let hostB = UUID()
|
|
let sessionA = UUID()
|
|
let sessionB = UUID()
|
|
|
|
// Act
|
|
store.setLastSessionId(sessionA, host: hostA)
|
|
store.setLastSessionId(sessionB, host: hostB)
|
|
|
|
// Assert
|
|
#expect(store.lastSessionId(host: hostA) == sessionA)
|
|
#expect(store.lastSessionId(host: hostB) == sessionB)
|
|
}
|
|
|
|
@Test("UserDefaults 中的非 UUID 垃圾值:读回 nil 而非 crash(防御性解析)")
|
|
func garbageStoredValueReadsBackAsNil() throws {
|
|
// Arrange — discover the store's key via the suite's own domain, then poison it
|
|
let suiteName = Fixtures.makeSuiteName()
|
|
let defaults = try #require(UserDefaults(suiteName: suiteName))
|
|
defer { defaults.removePersistentDomain(forName: suiteName) }
|
|
let store = UserDefaultsLastSessionStore(defaults: defaults)
|
|
let host = UUID()
|
|
store.setLastSessionId(UUID(), host: host)
|
|
let key = try #require(defaults.persistentDomain(forName: suiteName)?.keys.first)
|
|
|
|
// Act
|
|
defaults.set("not-a-uuid", forKey: key)
|
|
|
|
// Assert
|
|
#expect(store.lastSessionId(host: host) == nil)
|
|
}
|