T-iOS-11: TerminalScreen/KeyBar/TerminalViewModel + KeyByteMap (byte-for-byte keybar.ts, arrows excluded from UIKeyCommand to preserve DECCKM) T-iOS-12: PairingScreen/VM — confirm-before-network (zero-call assertions), §5.4 four-tier warnings, Host construction per contract ruling T-iOS-13: SessionListScreen/VM — Tunables-paced polling with leak-free teardown, optimistic kill+rollback, pending via overlay (LiveSessionInfo has no pending field) T-iOS-14: GateBanner/PlanGateSheet/AwayDigestView/GateViewModel — three-way mapping from SessionCore Affordance single source, tap-epoch guard, per-epoch haptics T-iOS-16: IntegrationTests vs real Node server (10 tests: origin guards, mirror, kill-close vs exit-frame differential, 16MiB+ESC/C0 replay) + ios.yml own-sources coverage gate (red-once demoed) T-iOS-17: ios/README.md ntfy chapter (read-only verification, file:line cites) Verified: 224 unit + 10 integration tests green; 5/5 semantic spot-checks; zero Owns violations
75 lines
3.5 KiB
Swift
75 lines
3.5 KiB
Swift
//
|
||
// ProtocolFlowTests.swift — T-iOS-16 测试 1-2:基本协议流程(吸收 spike②)
|
||
//
|
||
// 1. attach(null) → attached → input "echo hi\r" → output 含 "hi"
|
||
// (附加强断言 RUN:42:证明命令确被 shell 执行,而不是只看到输入回显)
|
||
// 2. resize(120,40) → `stty size` 输出 "40 120";边界 resize 1/1000 各一发,
|
||
// 以 stty 实测尺寸生效证明"被接受"(服务器对非法 resize 是静默丢弃,
|
||
// src/protocol.ts:113-115 —— 只有尺寸真变了才证明帧被采纳),且连接存活。
|
||
//
|
||
// 客户端帧一律 MessageCodec 编码、服务器帧一律 MessageCodec 解码 —— 本 suite
|
||
// 同时是冻结契约与真服务器之间的防漂移闸门(plan §9)。
|
||
|
||
import Foundation
|
||
import Testing
|
||
import WireProtocol
|
||
|
||
@Suite("T-iOS-16 协议流程(真服务器)", .serialized, .timeLimit(.minutes(5)))
|
||
struct ProtocolFlowTests {
|
||
|
||
@Test("attach(null)→attached(101);echo hi → output 含 hi 且命令确被执行")
|
||
func attachEchoRoundtrip() async throws {
|
||
// Arrange
|
||
let server = try await ServerHarness.shared.server()
|
||
let client = WSTestClient(server: server, origin: server.origin)
|
||
defer { client.close() }
|
||
|
||
// Act: attach(null) → attached(spike② 阳性对照:自定义 Origin 升级 101)
|
||
let sessionId = try await attachAndAwaitAttached(client: client, sessionId: nil)
|
||
#expect(client.handshakeStatusCode == 101,
|
||
"升级应 101,实际 \(String(describing: client.handshakeStatusCode))")
|
||
|
||
var reader = TranscriptReader(client: client)
|
||
try await client.send(shellCommand("echo hi"))
|
||
try await reader.awaitContains("hi")
|
||
|
||
// 强断言:$((6*7)) 只有 shell 真执行才会变成 42(回显里只有字面量表达式)。
|
||
try await client.send(shellCommand("echo \"RUN:$((6*7))\""))
|
||
try await reader.awaitContains("RUN:42")
|
||
|
||
// Cleanup
|
||
client.close()
|
||
await killSessionBestEffort(server: server, id: sessionId)
|
||
}
|
||
|
||
@Test("resize(120,40) → stty size = 40 120;边界 1/1000 均被采纳且连接存活")
|
||
func resizeTakesEffectIncludingBoundaries() async throws {
|
||
// Arrange
|
||
let server = try await ServerHarness.shared.server()
|
||
let client = WSTestClient(server: server, origin: server.origin)
|
||
defer { client.close() }
|
||
let sessionId = try await attachAndAwaitAttached(client: client, sessionId: nil)
|
||
var reader = TranscriptReader(client: client)
|
||
|
||
// Act + Assert: 常规尺寸(任务字面用例)。SZ: 哨兵防止把输入回显误判成输出
|
||
//(回显里是字面量 $(stty size),只有命令执行结果才含 "SZ:<rows> <cols>")。
|
||
try await client.send(.resize(cols: 120, rows: 40))
|
||
try await client.send(shellCommand("echo \"SZ:$(stty size)\""))
|
||
try await reader.awaitContains("SZ:40 120")
|
||
|
||
// 边界下限:1×1 被采纳(stty 实测 1 1),连接未断。
|
||
try await client.send(.resize(cols: 1, rows: 1))
|
||
try await client.send(shellCommand("echo \"SZ:$(stty size)\""))
|
||
try await reader.awaitContains("SZ:1 1")
|
||
|
||
// 边界上限:1000×1000 被采纳,连接未断。
|
||
try await client.send(.resize(cols: 1000, rows: 1000))
|
||
try await client.send(shellCommand("echo \"SZ:$(stty size)\""))
|
||
try await reader.awaitContains("SZ:1000 1000")
|
||
|
||
// Cleanup
|
||
client.close()
|
||
await killSessionBestEffort(server: server, id: sessionId)
|
||
}
|
||
}
|