import { describe, expect, it } from "vitest"; import type { RoomMapping } from "./config.js"; import { classifyEvent, parseMxc, redactionTarget, type MatrixEvent } from "./events.js"; const mapping: RoomMapping = { roomId: "!room:example.org", workspaceId: "11111111-1111-4111-8111-111111111111", groupId: "22222222-2222-4222-8222-222222222222", }; const BOT = "@archive:example.org"; function message(content: Record, extra: Partial = {}): MatrixEvent { return { type: "m.room.message", event_id: "$ev1", sender: "@alice:example.org", content, ...extra }; } const image = { msgtype: "m.image", body: "cat.png", url: "mxc://example.org/abc123" }; describe("classifyEvent", () => { it("accepts an unencrypted m.image with an mxc URI", () => { const result = classifyEvent(message(image), mapping, "unencrypted", BOT); expect(result.kind).toBe("attachment"); if (result.kind !== "attachment") return; expect(result.candidate).toMatchObject({ roomId: mapping.roomId, eventId: "$ev1", msgtype: "m.image", mxcUri: image.url }); expect(result.displayName).toBe("cat.png"); }); it("prefers content.filename over body (caption) for the display name", () => { const result = classifyEvent(message({ ...image, filename: "real.png", body: "a caption" }), mapping, "unencrypted", BOT); expect(result.kind === "attachment" && result.displayName).toBe("real.png"); }); it.each(["m.file", "m.audio", "m.video"])("accepts %s", (msgtype) => { expect(classifyEvent(message({ ...image, msgtype }), mapping, "unencrypted", BOT).kind).toBe("attachment"); }); it.each([ ["own-event", message(image, { sender: BOT }), "unencrypted"], ["room-encrypted", message(image), "encrypted"], ["room-unknown", message(image), "unknown"], ["encrypted-event", { ...message(image), type: "m.room.encrypted" }, "unencrypted"], ["encrypted-file", message({ msgtype: "m.file", body: "x", file: { url: "mxc://example.org/enc" } }), "unencrypted"], ["edit", message({ ...image, "m.relates_to": { rel_type: "m.replace", event_id: "$old" } }), "unencrypted"], ["edit", message({ ...image, "m.new_content": image }), "unencrypted"], ["redacted", message(image, { unsigned: { redacted_because: {} } }), "unencrypted"], ["not-an-attachment", message({ msgtype: "m.text", body: "hi" }), "unencrypted"], ["not-a-message", { ...message(image), type: "m.sticker" }, "unencrypted"], ["ineligible", message({ ...image, url: "https://evil.example/x.png" }), "unencrypted"], ["ineligible", message({ ...image, url: "mxc://example.org/a/b" }), "unencrypted"], ["ineligible", message({ msgtype: "m.image", body: "x" }), "unencrypted"], ] as const)("skips %s", (reason, event, encryption) => { const result = classifyEvent(event, mapping, encryption, BOT); expect(result).toMatchObject({ kind: "skip", reason }); }); it("classifies redactions regardless of room encryption (v11 content.redacts and legacy top-level)", () => { const v11: MatrixEvent = { type: "m.room.redaction", event_id: "$r", sender: "@mod:x", content: { redacts: "$ev1" } }; const legacy: MatrixEvent = { type: "m.room.redaction", event_id: "$r", sender: "@mod:x", content: {}, redacts: "$ev2" }; expect(classifyEvent(v11, mapping, "encrypted", BOT)).toMatchObject({ kind: "redaction", redacts: "$ev1" }); expect(redactionTarget(legacy)).toBe("$ev2"); expect(classifyEvent({ ...v11, content: {} }, mapping, "unencrypted", BOT).kind).toBe("skip"); }); }); describe("parseMxc", () => { it("splits server and media id", () => { expect(parseMxc("mxc://example.org/abc")).toEqual({ serverName: "example.org", mediaId: "abc" }); }); it("rejects anything else", () => { expect(() => parseMxc("https://example.org/abc")).toThrow(); expect(() => parseMxc("mxc://example.org/../x")).toThrow(); }); });