import pino from "pino"; import { describe, expect, it } from "vitest"; import type { RoomEncryption } from "./events.js"; import { lintEncryption, logLintReport, scanEncryption, senderIsRoomAdmin, toEncryptionHealth, type MatrixLintPort, } from "./encryption-lint.js"; import type { SyncResult, MediaDownload } from "./matrix.js"; import type { MatrixEvent } from "./events.js"; const MAPPED = "!mapped:example.org"; const logger = pino({ level: "silent" }); describe("lintEncryption", () => { it("accepts deliberately unencrypted mapped rooms", () => { const report = lintEncryption([MAPPED], new Map([[MAPPED, "unencrypted" as RoomEncryption]]), new Date(0)); expect(report.mappedUnencrypted).toEqual([MAPPED]); expect(report.issues).toEqual([]); }); it("flags encrypted/unknown mapped rooms as informational issues", () => { const report = lintEncryption([MAPPED], new Map([ [MAPPED, "encrypted"], ["!other:x", "unknown"], ]), new Date(0)); expect(report.issues.map((i) => i.kind)).toEqual(["mapped-encrypted", "unmapped-unknown"]); }); it("WARNs on unencrypted rooms outside the mapping (policy violation)", () => { const report = lintEncryption([MAPPED], new Map([ [MAPPED, "unencrypted"], ["!stray:x", "unencrypted"], ]), new Date(0)); expect(report.issues).toEqual([{ roomId: "!stray:x", kind: "unmapped-unencrypted", encrypted: "unencrypted", mapped: false, }]); expect(toEncryptionHealth(report)).toMatchObject({ checked: 2, violations: ["!stray:x"] }); }); it("produces an empty health snapshot before any scan", () => { expect(toEncryptionHealth(null)).toEqual({ checked: 0, violations: [], mappedUnencrypted: [], checkedAt: null }); }); }); describe("logLintReport", () => { it("emits WARN for violations and INFO for informational issues", () => { const lines: { level: number; roomId?: string }[] = []; const sink = pino( { level: "trace" }, { write(line: string) { const parsed = JSON.parse(line) as { level: number; roomId?: string }; lines.push({ level: parsed.level, roomId: parsed.roomId }); } }, ); logLintReport(lintEncryption([], new Map([ ["!violation:x", "unencrypted"], ["!info:x", "unknown"], ]), new Date(0)), sink); const violation = lines.find((l) => l.roomId === "!violation:x"); const informational = lines.find((l) => l.roomId === "!info:x"); expect(violation?.level).toBe(40); // pino WARN expect(informational?.level).toBe(30); // pino INFO }); }); class FakeLintMatrix implements MatrixLintPort { readonly userId = "@archive:example.org"; rooms: string[] = []; states = new Map(); async sync(): Promise { throw new Error("unused"); } async backfill(): Promise { return []; } async roomEncryption(roomId: string): Promise { return this.states.get(roomId) ?? "unknown"; } async joinRoom(): Promise {} async downloadMedia(): Promise { throw new Error("unused"); } async sendNotice(): Promise {} async roomPowerLevels(): Promise> { return {}; } async joinedRooms(): Promise { return this.rooms; } } describe("scanEncryption", () => { it("walks every joined room and produces the report", async () => { const matrix = new FakeLintMatrix(); matrix.rooms = ["!a:x", "!b:x", "!c:x"]; matrix.states.set("!a:x", "encrypted"); matrix.states.set("!b:x", "unencrypted"); matrix.states.set("!c:x", "encrypted"); const report = await scanEncryption(matrix, ["!a:x"], logger, new AbortController().signal, new Date(0)); expect(report.checked).toBe(3); expect(report.mappedUnencrypted).toEqual([]); // "!a:x" is mapped but encrypted (informational); "!b:x" is the violation. expect(report.issues).toEqual([ { roomId: "!a:x", kind: "mapped-encrypted", encrypted: "encrypted", mapped: true }, { roomId: "!b:x", kind: "unmapped-unencrypted", encrypted: "unencrypted", mapped: false }, ]); }); }); describe("senderIsRoomAdmin", () => { it("defaults to 50 and reads users_default otherwise", () => { expect(senderIsRoomAdmin({ users: { "@a:x": 50 } }, "@a:x")).toBe(true); expect(senderIsRoomAdmin({ users_default: 50 }, "@b:x")).toBe(true); expect(senderIsRoomAdmin({ users_default: 10 }, "@b:x")).toBe(false); expect(senderIsRoomAdmin({}, "@b:x")).toBe(false); }); });