import Foundation import Testing import WireProtocol // T-iOS-3 · TimelineEvent(GET /live-sessions/:id/events 条目,src/types.ts:428-433 镜像)。 // 服务器是不可信输入源:decodeList 永不 throw,非法/未知 class 条目静默丢弃。 @Test("TimelineEvent:合法条目全字段解码") func decodesValidEntryWithAllFields() throws { // Arrange let json = Data("{\"at\":1751600000000,\"class\":\"tool\",\"toolName\":\"Bash\",\"label\":\"ran Bash\"}".utf8) // Act let event = try JSONDecoder().decode(TimelineEvent.self, from: json) // Assert #expect(event == TimelineEvent(at: 1_751_600_000_000, class: "tool", toolName: "Bash", label: "ran Bash")) #expect(event.hasKnownClass) } @Test("TimelineEvent:toolName 可选缺省") func decodesEntryWithoutToolName() throws { // Arrange let json = Data("{\"at\":1,\"class\":\"waiting\",\"label\":\"waiting for approval\"}".utf8) // Act let event = try JSONDecoder().decode(TimelineEvent.self, from: json) // Assert #expect(event.toolName == nil) #expect(event.hasKnownClass) } @Test("knownClasses 与服务器 TimelineClass 全集一致(src/types.ts:423)") func knownClassesMirrorServerUnion() { #expect(TimelineEvent.knownClasses == ["tool", "waiting", "done", "stuck", "user"]) } @Test("decodeList:未知 class 条目被丢弃,合法条目保留(消费方丢弃语义)") func decodeListDropsUnknownClassEntries() { // Arrange — 1 合法 + 1 未知 class + 1 缺 label + 1 形状完全错误 let json = Data(""" [{"at":1,"class":"tool","label":"ran Bash"}, {"at":2,"class":"alien","label":"???"}, {"at":3,"class":"done"}, 42] """.utf8) // Act let events = TimelineEvent.decodeList(from: json) // Assert #expect(events == [TimelineEvent(at: 1, class: "tool", toolName: nil, label: "ran Bash")]) } @Test("decodeList:5 个已知 class 全部保留") func decodeListKeepsAllKnownClasses() { // Arrange let json = Data(""" [{"at":1,"class":"tool","label":"a"},{"at":2,"class":"waiting","label":"b"}, {"at":3,"class":"done","label":"c"},{"at":4,"class":"stuck","label":"d"}, {"at":5,"class":"user","label":"e"}] """.utf8) // Act let events = TimelineEvent.decodeList(from: json) // Assert #expect(events.count == 5) #expect(events.allSatisfy { $0.hasKnownClass }) } @Test("decodeList:坏 JSON / 顶层非数组 → 空数组,永不 throw", arguments: ["not json", "{\"at\":1}", "42", ""]) func decodeListNeverThrowsOnMalformedInput(text: String) { #expect(TimelineEvent.decodeList(from: Data(text.utf8)) == []) }