import Testing @testable import WebTerm /// T-iOS-15 · Fan-out adapter tests. `SessionEngine.events` is a /// single-consumer `AsyncStream`, but TerminalViewModel, GateViewModel AND the /// session-activity bridge must all observe it — `EventFanOut` is the wiring /// layer's broadcast seam. These tests pin its contract: every branch receives /// every element, in order, and source termination / cancel() propagate. @Suite("EventFanOut") struct EventFanOutTests { private static let branchCount = 3 @Test("每个分支都按序收到全部元素") func allBranchesReceiveAllElementsInOrder() async { // Arrange let (source, continuation) = AsyncStream.makeStream() let fanOut = EventFanOut(source: source, branchCount: Self.branchCount) // Act for value in 1...5 { continuation.yield(value) } continuation.finish() // Assert: unbounded buffering — late consumption still sees everything. for branch in fanOut.branches { var received: [Int] = [] for await value in branch { received.append(value) } #expect(received == [1, 2, 3, 4, 5]) } } @Test("分支数量与请求一致") func branchCountMatchesRequest() { let (source, _) = AsyncStream.makeStream() let fanOut = EventFanOut(source: source, branchCount: Self.branchCount) #expect(fanOut.branches.count == Self.branchCount) } @Test("源结束 → 所有分支结束(消费循环退出,不悬挂)") func sourceFinishFinishesEveryBranch() async { // Arrange let (source, continuation) = AsyncStream.makeStream() let fanOut = EventFanOut(source: source, branchCount: Self.branchCount) // Act continuation.yield("only") continuation.finish() // Assert: every branch loop terminates after draining. for branch in fanOut.branches { var count = 0 for await _ in branch { count += 1 } #expect(count == 1) } } @Test("cancel() → 分支立即终止(teardown 不泄漏消费任务)") func cancelTerminatesBranches() async { // Arrange let (source, continuation) = AsyncStream.makeStream() let fanOut = EventFanOut(source: source, branchCount: Self.branchCount) // Act: cancel without ever finishing the source. fanOut.cancel() continuation.yield(42) // post-cancel input must not hang consumers // Assert: iteration completes (content is unspecified mid-flight — // termination is the contract under test). for branch in fanOut.branches { for await _ in branch {} } #expect(Bool(true)) // reaching here = no hang } }