- 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)、配对四类失败、路由器、 传输往返与各类拒绝路径
51 lines
1.8 KiB
Swift
51 lines
1.8 KiB
Swift
import XCTest
|
|
|
|
@testable import MultiSimAdmin
|
|
|
|
final class InstanceSessionServiceTests: XCTestCase {
|
|
func testLoginStoresAndClearsSessionCookie() async throws {
|
|
let store = InstanceSessionStore()
|
|
let client = MockSessionClient(status: 204)
|
|
let service = InstanceSessionService(store: store, client: client)
|
|
let instance = Self.instance()
|
|
|
|
let result = try await service.login(instance, password: "secret")
|
|
XCTAssertTrue(result.authenticated)
|
|
let cookie = await store.cookie(for: instance.id, origin: instance.endpoint.directOrigin!)
|
|
XCTAssertEqual(cookie, "simadmin_session=token")
|
|
|
|
let rejected = try await service.login(instance, password: "bad")
|
|
XCTAssertFalse(rejected.authenticated)
|
|
let removed = await store.cookie(for: instance.id, origin: instance.endpoint.directOrigin!)
|
|
XCTAssertEqual(removed, nil)
|
|
}
|
|
|
|
private static func instance() -> Instance {
|
|
Instance(
|
|
id: UUID(),
|
|
name: "Session",
|
|
origin: URL(string: "http://192.168.1.20:3000")!,
|
|
configRevision: 1,
|
|
createdAt: Date(),
|
|
updatedAt: Date()
|
|
)
|
|
}
|
|
|
|
private struct MockSessionClient: SimAdminClientProtocol {
|
|
let status: Int
|
|
|
|
func send(
|
|
_ route: UpstreamRoute,
|
|
baseURL: URL,
|
|
cookie: String?
|
|
) async throws -> UpstreamResponse {
|
|
guard case let .authLogin(submitted) = route else {
|
|
return UpstreamResponse(status: 200, data: Data("{}.utf8".utf8), setCookie: nil)
|
|
}
|
|
let authorized = submitted == "secret"
|
|
let setCookie = authorized ? "simadmin_session=token; Path=/" : nil
|
|
return UpstreamResponse(status: authorized ? status : 200, data: Data("{}".utf8), setCookie: setCookie)
|
|
}
|
|
}
|
|
}
|