- InstanceEndpoint 取代 Instance.origin(旧数据 init(from:) 读成 .direct, 写盘只输出 endpoint;dedupKey 直通用 origin、中继用 relay:<nodeID>) - RelayWire:Swift 侧重算 contracts/test-vectors.json(CanonicalJSON 手写解析器, 因为 Foundation 的解码器会抹掉'拼写',而 intent 摘要定义的正是拼写) - NodeTransport 抽象 + DirectTransport / RelayTransport / NodeTransportRouter; DirectTransport 是薄适配,直通路径行为不变 - RelayTransport:信封构造、签名、加密、验签、解密;响应验签失败即丢弃, 四类以上失败各有独立 UpstreamError 分支与用户文案 - DeviceIdentity(沙盒文件 0600,配对时由节点分配 deviceID)、RelayPairing、配对 sheet - 卡片通道徽标 + freshness 副标题;危险写 intent 在发送瞬间生成 - scripts/export-app-routes.swift 生成 vendored 路由表,AppRoutesContractTests 反向守卫 - 测试 78→92:向量互算、迁移(字面量旧 JSON)、配对四类失败、路由器、 传输往返与各类拒绝路径
106 lines
4.0 KiB
Swift
106 lines
4.0 KiB
Swift
import XCTest
|
|
|
|
@testable import MultiSimAdmin
|
|
|
|
final class InstanceRepositoryTests: XCTestCase {
|
|
private var location: URL!
|
|
|
|
override func setUp() {
|
|
super.setUp()
|
|
location = FileManager.default.temporaryDirectory
|
|
.appendingPathComponent("multi-simadmin-\(UUID().uuidString).json")
|
|
}
|
|
|
|
override func tearDown() {
|
|
try? FileManager.default.removeItem(at: location)
|
|
super.tearDown()
|
|
}
|
|
|
|
func testCreateListUpdateAndDeleteInstance() async throws {
|
|
let repository = FileInstanceRepository(location: location)
|
|
let input = InstanceInput(
|
|
name: "Lab Alpha",
|
|
origin: URL(string: "http://192.168.3.55:3000/")!,
|
|
tags: ["primary", "lab"],
|
|
password: "secret"
|
|
)
|
|
|
|
let created = try await repository.create(input)
|
|
XCTAssertEqual(created.name, "Lab Alpha")
|
|
XCTAssertEqual(created.endpoint.displayAddress, "http://192.168.3.55:3000")
|
|
XCTAssertEqual(created.tags, ["primary", "lab"])
|
|
XCTAssertEqual(created.configRevision, 1)
|
|
XCTAssertTrue(created.credentialConfigured)
|
|
|
|
let patch = InstancePatch(name: "Alpha", tags: ["lab"])
|
|
let updated = try await repository.update(created.id, expectedRevision: 1, patch)
|
|
XCTAssertEqual(updated.name, "Alpha")
|
|
XCTAssertEqual(updated.tags, ["lab"])
|
|
XCTAssertEqual(updated.configRevision, 2)
|
|
XCTAssertTrue(updated.credentialConfigured)
|
|
|
|
try await repository.delete(updated.id, expectedRevision: 2)
|
|
let afterDelete = try await repository.list(InstanceQuery())
|
|
XCTAssertTrue(afterDelete.isEmpty)
|
|
}
|
|
|
|
func testRejectsInvalidOriginDuplicateOriginAndStaleRevision() async throws {
|
|
let repository = FileInstanceRepository(location: location)
|
|
let origin = URL(string: "http://192.168.1.20:3000")!
|
|
let input = InstanceInput(name: "One", origin: origin, tags: [], password: nil)
|
|
let first = try await repository.create(input)
|
|
|
|
do {
|
|
let nested = InstanceInput(
|
|
name: "Nested",
|
|
origin: URL(string: "http://192.168.1.20:3000/admin")!,
|
|
tags: [],
|
|
password: nil
|
|
)
|
|
_ = try await repository.create(nested)
|
|
XCTFail("Expected nested path rejection")
|
|
} catch let error as RepositoryError {
|
|
XCTAssertEqual(error, .invalidOrigin)
|
|
}
|
|
|
|
do {
|
|
_ = try await repository.create(input)
|
|
XCTFail("Expected duplicate-origin failure")
|
|
} catch let error as RepositoryError {
|
|
XCTAssertEqual(error, .duplicateOrigin)
|
|
}
|
|
|
|
do {
|
|
_ = try await repository.update(first.id, expectedRevision: 99, InstancePatch(name: "New"))
|
|
XCTFail("Expected stale-revision failure")
|
|
} catch let error as RepositoryError {
|
|
XCTAssertEqual(error, .revisionConflict)
|
|
}
|
|
}
|
|
|
|
func testListFiltersBySearchAndTag() async throws {
|
|
let repository = FileInstanceRepository(location: location)
|
|
_ = try await repository.create(InstanceInput(
|
|
name: "Lab Alpha",
|
|
origin: URL(string: "http://192.168.1.10:3000")!,
|
|
tags: ["production"],
|
|
password: nil
|
|
))
|
|
_ = try await repository.create(InstanceInput(
|
|
name: "Home Beta",
|
|
origin: URL(string: "http://192.168.1.20:3000")!,
|
|
tags: ["lab"],
|
|
password: nil
|
|
))
|
|
|
|
let search = try await repository.list(InstanceQuery(search: "alpha"))
|
|
XCTAssertEqual(search.map(\.name), ["Lab Alpha"])
|
|
let tagged = try await repository.list(InstanceQuery(tags: ["lab"]))
|
|
XCTAssertEqual(tagged.map(\.name), ["Home Beta"])
|
|
let production = try await repository.list(InstanceQuery(tags: ["production"]))
|
|
XCTAssertEqual(production.map(\.name), ["Lab Alpha"])
|
|
let sorted = try await repository.list(InstanceQuery())
|
|
XCTAssertEqual(sorted.map(\.name), ["Home Beta", "Lab Alpha"])
|
|
}
|
|
}
|