| 1 | import XCTest |
| 2 | @testable import LChatCore |
| 3 | |
| 4 | // 🤖 Incoming-webhook events (issue #610, docs/webhooks.md): the hooks sidecar |
| 5 | // posts typed `webhooks.<ns>` payloads into ordinary threads. Native claims |
| 6 | // none of them — MessagePayload.isRenderableChat is the gate that keeps |
| 7 | // unclaimed typed events out of the feed and out of notifications, exactly as |
| 8 | // the web's foldEvent/isAppType gate does (test/event-vectors/webhooks.json is |
| 9 | // the twin of this fixture; keep the payloads in step). These vectors pin two |
| 10 | // things: the payload DECODES tolerantly (the unknown hook/req envelope never |
| 11 | // fails MessagePayload), and it stays non-renderable. |
| 12 | final class WebhooksVectorTests: XCTestCase { |
| 13 | func testWebhookEventsDecodeAndStayNonRenderable() throws { |
| 14 | let url = try XCTUnwrap( |
| 15 | Bundle.module.url(forResource: "Fixtures/webhooks-vector", withExtension: "json"), |
| 16 | "missing fixture webhooks-vector.json") |
| 17 | let raw = try JSONSerialization.jsonObject(with: Data(contentsOf: url)) as? [String: Any] |
| 18 | let vectors = try XCTUnwrap(raw?["vectors"] as? [[String: Any]], "fixture has no vectors") |
| 19 | XCTAssertFalse(vectors.isEmpty) |
| 20 | |
| 21 | for vector in vectors { |
| 22 | let name = vector["name"] as? String ?? "?" |
| 23 | let event = try XCTUnwrap(vector["event"] as? [String: Any], "\(name): no event") |
| 24 | let data = try JSONSerialization.data(withJSONObject: event) |
| 25 | // Tolerant decode: the hook/req envelope is unknown to native and |
| 26 | // must be ignored, never a decode failure. |
| 27 | let p = try JSONDecoder().decode(MessagePayload.self, from: data) |
| 28 | |
| 29 | XCTAssertEqual(p.type, event["type"] as? String, "\(name): type must round-trip") |
| 30 | XCTAssertEqual(p.name, event["name"] as? String, "\(name): attribution name") |
| 31 | XCTAssertEqual(p.uid, event["uid"] as? String, "\(name): attribution uid") |
| 32 | // The contract: an unclaimed typed event renders nothing — no |
| 33 | // bubble, no notification. A future consumer surface may claim a |
| 34 | // webhooks.* type deliberately; flipping this assertion then must |
| 35 | // be a conscious cross-platform change with the web's gate. |
| 36 | XCTAssertFalse(p.isRenderableChat, "\(name): webhook events must not render as chat") |
| 37 | XCTAssertFalse(p.isReactionEvent, "\(name): webhook events are not reactions") |
| 38 | } |
| 39 | } |
| 40 | } |
| 41 | |