import { describe, expect, it } from "vitest"; import { PermanentArchiveError } from "./inspector.js"; import { parseSync, resolveRedirect, supportsAuthenticatedMedia, syncFilter } from "./matrix.js"; const HS = new URL("https://matrix.example.org"); describe("supportsAuthenticatedMedia", () => { it("requires v1.11+ or the MSC3916 stable flag", () => { expect(supportsAuthenticatedMedia({ versions: ["v1.10", "v1.11"] })).toBe(true); expect(supportsAuthenticatedMedia({ versions: ["v1.12"] })).toBe(true); expect(supportsAuthenticatedMedia({ versions: ["v1.9"], unstable_features: { "org.matrix.msc3916.stable": true } })).toBe(true); expect(supportsAuthenticatedMedia({ versions: ["r0.6.1", "v1.10"] })).toBe(false); expect(supportsAuthenticatedMedia(null)).toBe(false); }); }); describe("resolveRedirect", () => { it("keeps auth only for the homeserver origin", () => { expect(resolveRedirect(HS, "/_matrix/media/x", HS, [])).toMatchObject({ sendAuth: true }); }); it("drops auth for allowlisted object-store hosts", () => { const hop = resolveRedirect(HS, "https://r2.example.com/bucket/obj?sig=1", HS, ["r2.example.com"]); expect(hop).toMatchObject({ sendAuth: false }); expect(hop.url.hostname).toBe("r2.example.com"); }); it("refuses unapproved hosts and http downgrades", () => { expect(() => resolveRedirect(HS, "https://evil.example/x", HS, ["r2.example.com"])).toThrow(PermanentArchiveError); expect(() => resolveRedirect(HS, "http://r2.example.com/x", HS, ["r2.example.com"])).toThrow(/non-https/); expect(() => resolveRedirect(HS, "https://matrix.example.org:8448/x", HS, [])).toThrow(PermanentArchiveError); }); }); describe("parseSync", () => { it("extracts joined timelines, state and invites, dropping malformed events", () => { const result = parseSync({ next_batch: "s2", rooms: { join: { "!a:x": { timeline: { events: [{ type: "m.room.message", event_id: "$1", sender: "@u:x", content: {} }, { junk: true }], limited: true, prev_batch: "p1" }, state: { events: [{ type: "m.room.encryption", event_id: "$s", sender: "@u:x", state_key: "", content: { algorithm: "m.megolm.v1.aes-sha2" } }] }, }, }, invite: { "!b:x": {} }, }, }); expect(result.nextBatch).toBe("s2"); expect(result.invited).toEqual(["!b:x"]); const room = result.joined.get("!a:x"); expect(room?.timeline).toMatchObject({ limited: true, prevBatch: "p1" }); expect(room?.timeline.events).toHaveLength(1); expect(room?.stateEvents[0]?.type).toBe("m.room.encryption"); }); it("rejects a response without next_batch", () => { expect(() => parseSync({})).toThrow(); }); }); describe("syncFilter", () => { it("restricts to mapped rooms and the needed event types", () => { const filter = syncFilter(["!a:x"]) as { room: { rooms: string[]; timeline: { types: string[] } } }; expect(filter.room.rooms).toEqual(["!a:x"]); expect(filter.room.timeline.types).toContain("m.room.redaction"); expect(filter.room.timeline.types).toContain("m.room.encryption"); }); });