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
78 lines
2.3 KiB
Swift
78 lines
2.3 KiB
Swift
import Foundation
|
|
import Testing
|
|
import HostRegistry
|
|
|
|
// HostStore protocol contract, tested against the in-Sources InMemory double
|
|
// (plan T-iOS-7: "用 InMemory 替身测协议契约").
|
|
|
|
@Test("upsert 新 host:返回的新集合含它,loadAll 一致")
|
|
func upsertNewHostAppearsInReturnedCollectionAndLoadAll() async throws {
|
|
// Arrange
|
|
let store = InMemoryHostStore()
|
|
let host = Fixtures.makeHost()
|
|
|
|
// Act
|
|
let result = try await store.upsert(host)
|
|
|
|
// Assert
|
|
#expect(result == [host])
|
|
#expect(try await store.loadAll() == [host])
|
|
}
|
|
|
|
@Test("同 id 再 upsert:替换不重复,且保持原位置")
|
|
func upsertSameIdReplacesWithoutDuplicatingAndKeepsPosition() async throws {
|
|
// Arrange
|
|
let first = Fixtures.makeHost(name: "old-name")
|
|
let second = Fixtures.makeHost(name: "other", urlString: "https://mac.ts.net")
|
|
let store = InMemoryHostStore(hosts: [first, second])
|
|
let renamed = Host(id: first.id, name: "new-name", endpoint: first.endpoint)
|
|
|
|
// Act
|
|
let result = try await store.upsert(renamed)
|
|
|
|
// Assert
|
|
#expect(result == [renamed, second])
|
|
#expect(try await store.loadAll() == [renamed, second])
|
|
}
|
|
|
|
@Test("remove 已存在 id:集合不再含它")
|
|
func removeExistingIdDropsItFromCollection() async throws {
|
|
// Arrange
|
|
let keep = Fixtures.makeHost(name: "keep")
|
|
let doomed = Fixtures.makeHost(name: "doomed", urlString: "http://10.0.0.7:3000")
|
|
let store = InMemoryHostStore(hosts: [keep, doomed])
|
|
|
|
// Act
|
|
let result = try await store.remove(id: doomed.id)
|
|
|
|
// Assert
|
|
#expect(result == [keep])
|
|
#expect(try await store.loadAll() == [keep])
|
|
}
|
|
|
|
@Test("remove 不存在的 id:集合不变、不 throw(显式返回原集合)")
|
|
func removeUnknownIdReturnsUnchangedCollectionWithoutThrowing() async throws {
|
|
// Arrange
|
|
let host = Fixtures.makeHost()
|
|
let store = InMemoryHostStore(hosts: [host])
|
|
|
|
// Act
|
|
let result = try await store.remove(id: UUID())
|
|
|
|
// Assert
|
|
#expect(result == [host])
|
|
#expect(try await store.loadAll() == [host])
|
|
}
|
|
|
|
@Test("init 种子集合:loadAll 原样返回")
|
|
func initSeedsCollectionVerbatim() async throws {
|
|
// Arrange
|
|
let hosts = [Fixtures.makeHost(name: "a"), Fixtures.makeHost(name: "b")]
|
|
|
|
// Act
|
|
let store = InMemoryHostStore(hosts: hosts)
|
|
|
|
// Assert
|
|
#expect(try await store.loadAll() == hosts)
|
|
}
|