| 1 | import XCTest |
| 2 | @testable import LChatCore |
| 3 | |
| 4 | // 🤖 Phase 5 (docs/native-apps.md): the raw material of an app-host |
| 5 | // projection query — AppChatProjection.unprojected decodes exactly the app |
| 6 | // events native has no built-in projection for, and nothing else. The |
| 7 | // query's other half (window.lchatNative.projectEvent) lives in shell.js and |
| 8 | // answers with the app's own chatMessage/groupFeedMessage line. |
| 9 | final class ProjectionQueryTests: XCTestCase { |
| 10 | private let key = try! GroupKey(rawKey: Data(repeating: 3, count: 32)) |
| 11 | |
| 12 | private func blob(_ json: String) -> String { |
| 13 | try! key.encryptData(Data(json.utf8)) |
| 14 | } |
| 15 | |
| 16 | func testNamespacedEventDecodes() { |
| 17 | let json = #"{"type":"myhabits.checkin","habit":"run","name":"Ana","uid":"u-ana","ts":1700000000000}"# |
| 18 | let ev = AppChatProjection.unprojected(blob(json), key: key) |
| 19 | XCTAssertEqual(ev?.name, "Ana") |
| 20 | XCTAssertEqual(ev?.uid, "u-ana") |
| 21 | XCTAssertEqual(ev?.ts, 1_700_000_000_000) |
| 22 | // The decrypted JSON rides verbatim — the host's projectEvent parses it. |
| 23 | XCTAssertEqual(ev?.eventJSON, json) |
| 24 | } |
| 25 | |
| 26 | func testEnvelopeFieldsAreOptional() { |
| 27 | let ev = AppChatProjection.unprojected(blob(#"{"type":"vault.add"}"#), key: key) |
| 28 | XCTAssertEqual(ev?.name, "") |
| 29 | XCTAssertNil(ev?.uid) |
| 30 | XCTAssertNil(ev?.ts) |
| 31 | } |
| 32 | |
| 33 | func testChatInternalsStaySilent() { |
| 34 | // chat.* internals (typing beats, checkpoints) never query. |
| 35 | XCTAssertNil(AppChatProjection.unprojected(blob(#"{"type":"chat.typing","name":"Ana"}"#), key: key)) |
| 36 | } |
| 37 | |
| 38 | func testUnnamespacedAndMalformedStaySilent() { |
| 39 | XCTAssertNil(AppChatProjection.unprojected(blob(#"{"type":"chat","text":"hi"}"#), key: key)) |
| 40 | XCTAssertNil(AppChatProjection.unprojected(blob(#"{"type":".oops"}"#), key: key)) |
| 41 | XCTAssertNil(AppChatProjection.unprojected(blob(#"{"text":"no type"}"#), key: key)) |
| 42 | XCTAssertNil(AppChatProjection.unprojected("not-a-blob", key: key)) |
| 43 | } |
| 44 | } |
| 45 | |