import APIClient import Foundation import HostRegistry import SessionCore import TestSupport import Testing import WireProtocol @testable import WebTerm /// T-iOS-26 · "在此仓库开新会话" 的接线证明:`TerminalSessionController` 以 /// `spawnCwd` + `bootstrapInput` 启动时,线上帧序 = `attach(null, cwd)` → /// `input("claude\r")`(engine 的 attach-first 队列语义保证 bootstrap 绝不 /// 先于 attach 出线);带 sessionId 的常规打开则既无 cwd 也无 bootstrap。 /// /// 真 `SessionEngine` over `FakeTransport`(零真实等待):帧断言经 /// `openTask` + `waitUntilProcessed` 双屏障(open+send 已提交 ∧ attach /// 握手已完成 → 帧必已落地)。 @MainActor @Suite("ProjectOpenWiring") struct ProjectOpenWiringTests { private struct Fixture { let transport: FakeTransport let host: HostRegistry.Host let environment: AppEnvironment } private func makeFixture() throws -> Fixture { let baseURL = try #require(URL(string: "http://192.168.1.5:3000")) let endpoint = try #require(HostEndpoint(baseURL: baseURL)) let transport = FakeTransport() let defaults = try #require(UserDefaults(suiteName: "ProjectOpenWiringTests")) return Fixture( transport: transport, host: HostRegistry.Host(id: UUID(), name: "书房 Mac", endpoint: endpoint), environment: AppEnvironment( hostStore: InMemoryHostStore(), lastSessionStore: UserDefaultsLastSessionStore(defaults: defaults), http: FakeHTTPTransport(), termTransport: transport, probe: PairingViewModel.Probe(verifyHost: { _, _ in .failure(.timeout) }) ) ) } /// 双屏障:controller 的 open+bootstrap Task 完成(send 已提交/入队)∧ /// attach 握手完成(.connecting/.connected 已到 VM → 队列已冲刷)。 private func awaitAttachSettled(_ controller: TerminalSessionController) async { await controller.openTask?.value await controller.terminalViewModel.waitUntilProcessed(eventCount: 2) } @Test("spawn 变体:帧序 = attach(null, cwd) → input(claude\\r)") func spawnSendsAttachWithCwdThenBootstrapInput() async throws { let fixture = try makeFixture() let controller = TerminalSessionController( host: fixture.host, sessionId: nil, environment: fixture.environment, onPendingChanged: { _, _ in }, spawnCwd: "/repos/api", bootstrapInput: ProjectLaunch.claudeBootstrapInput ) controller.start() await awaitAttachSettled(controller) let frames = await fixture.transport.sentFrames #expect(frames == [ MessageCodec.encode(.attach(sessionId: nil, cwd: "/repos/api")), MessageCodec.encode(.input(data: ProjectLaunch.claudeBootstrapInput)), ]) controller.teardown() } @Test("常规打开(带 sessionId):只有 attach,无 cwd、无 bootstrap") func plainOpenSendsBareAttach() async throws { let fixture = try makeFixture() let sessionId = UUID() let controller = TerminalSessionController( host: fixture.host, sessionId: sessionId, environment: fixture.environment, onPendingChanged: { _, _ in } ) controller.start() await awaitAttachSettled(controller) let frames = await fixture.transport.sentFrames #expect(frames == [MessageCodec.encode(.attach(sessionId: sessionId, cwd: nil))]) controller.teardown() } }