eleven / ocho Public

Clone
git clone https://amadan.net/eleven/ocho

Plain git — no account needed to clone.

Download

Download this file

1import Foundation
2import XCTest
3@testable import LChatCore
4
5// πŸ€– The shared chat event vectors (test/event-vectors/chat.json), read from
6// the SOURCE TREE β€” the same file the web suite (test/event-vectors.test.mjs)
7// runs, so there is no copied fixture to drift (the AppDefaultCommandTests
8// pattern). Strict by design: a vector or key the contract names must exist,
9// so a test asking for it FAILS rather than skipping when the JSON lags.
10enum ChatVectors {
11 static var url: URL {
12 URL(fileURLWithPath: #filePath)
13 .deletingLastPathComponent() // Tests/LChatCoreTests
14 .deletingLastPathComponent() // Tests
15 .deletingLastPathComponent() // LChatCore
16 .deletingLastPathComponent() // ios
17 .deletingLastPathComponent() // repo
18 .appending(path: "test/event-vectors/chat.json")
19 }
20
21 /// Every entry under "vectors".
22 static func all() throws -> [[String: Any]] {
23 let data = try Data(contentsOf: url)
24 let root = try XCTUnwrap(try JSONSerialization.jsonObject(with: data) as? [String: Any],
25 "chat.json is not a JSON object")
26 return try XCTUnwrap(root["vectors"] as? [[String: Any]], "chat.json has no vectors array")
27 }
28
29 /// One named vector β€” a hard failure when it's missing.
30 static func vector(_ name: String) throws -> [String: Any] {
31 try XCTUnwrap(try all().first { $0["name"] as? String == name },
32 "chat.json has no vector named \(name)")
33 }
34
35 /// A vector's decrypted_payload decoded the way the app decodes a blob
36 /// (JSONDecoder β†’ MessagePayload).
37 static func payload(_ v: [String: Any]) throws -> MessagePayload {
38 let obj = try XCTUnwrap(v["decrypted_payload"], "vector has no decrypted_payload")
39 let data = try JSONSerialization.data(withJSONObject: obj)
40 return try JSONDecoder().decode(MessagePayload.self, from: data)
41 }
42
43 /// The server-side frame metadata β€” id + created_at (seconds).
44 static func serverMessage(_ v: [String: Any]) throws -> (id: Int64, at: Int64) {
45 let meta = try XCTUnwrap(v["server_message"] as? [String: Any], "vector has no server_message")
46 let id = try XCTUnwrap((meta["id"] as? NSNumber)?.int64Value, "server_message.id")
47 let at = try XCTUnwrap((meta["created_at"] as? NSNumber)?.int64Value, "server_message.created_at")
48 return (id, at)
49 }
50
51 /// A vector's whole message_sequence, folded the way a client folds it:
52 /// ordinary payloads become feed messages, "threadconvert" payloads become
53 /// claims in the conversion map β€” keyed on the CONVERT ROW's own server id,
54 /// which is the tie-break between two people converting overlapping chains.
55 /// `skipConverts` folds the same sequence the way a client too old to know
56 /// the payload type does: it drops the event and the chain stays inline.
57 static func sequence(_ v: [String: Any], skipConverts: Bool = false) throws
58 -> (messages: [ChatFeedModel.Message], converts: [Int64: ChatFeedModel.ThreadConvert]) {
59 let steps = try XCTUnwrap(v["message_sequence"] as? [[String: Any]],
60 "vector has no message_sequence")
61 var list: [ChatFeedModel.Message] = []
62 var claims: [Int64: ChatFeedModel.ThreadConvert] = [:]
63 for step in steps {
64 let p = try payload(step)
65 if p.isThreadConvertEvent {
66 guard !skipConverts else { continue }
67 let meta = try serverMessage(step)
68 ChatFeedModel.claimConverts(&claims, root: p.root ?? 0, ids: p.ids ?? [], at: meta.id)
69 continue
70 }
71 list.append(try message(step))
72 }
73 // Server order, however the steps were delivered β€” the model sorts the
74 // same way, so an out-of-order vector folds to the same list.
75 list.sort { $0.id < $1.id }
76 return (list, claims)
77 }
78
79 /// A vector folded into the feed's Message the way ChatFeedModel.fold does
80 /// (reply = payload.replyTo; raw = the payload). `mine` is false β€” the
81 /// vectors are everyone else's messages.
82 static func message(_ v: [String: Any]) throws -> ChatFeedModel.Message {
83 let p = try payload(v)
84 let meta = try serverMessage(v)
85 return ChatFeedModel.Message(id: meta.id, name: p.name ?? "", text: p.text ?? "",
86 mine: false, at: meta.at, action: p.action ?? false,
87 edited: false, attachments: p.attachments ?? [],
88 reply: p.replyTo, raw: p)
89 }
90}
91