T-HUB/apps/archive-bot/src/config.test.ts

105 lines
4.4 KiB
TypeScript

import { describe, expect, it } from "vitest";
import { ConfigError, loadConfig, parseExportConfig, parseRoomMappings, toGroupFileMapping } from "./config.js";
const WS = "11111111-1111-4111-8111-111111111111";
const GROUP = "22222222-2222-4222-8222-222222222222";
describe("parseRoomMappings", () => {
it("accepts immutable room IDs with UUID workspace/group", () => {
expect(parseRoomMappings(JSON.stringify([{ roomId: "!abc:example.org", workspaceId: WS, groupId: GROUP }])))
.toEqual([{ roomId: "!abc:example.org", workspaceId: WS, groupId: GROUP }]);
});
it("rejects aliases, non-UUIDs, duplicates, empty and malformed input", () => {
expect(() => parseRoomMappings(JSON.stringify([{ roomId: "#archive:example.org", workspaceId: WS, groupId: GROUP }])))
.toThrow(ConfigError);
expect(() => parseRoomMappings(JSON.stringify([{ roomId: "!a:x", workspaceId: "ws", groupId: GROUP }]))).toThrow(ConfigError);
const dup = { roomId: "!a:x", workspaceId: WS, groupId: GROUP };
expect(() => parseRoomMappings(JSON.stringify([dup, dup]))).toThrow(/duplicate/);
expect(() => parseRoomMappings("[]")).toThrow(ConfigError);
expect(() => parseRoomMappings("{nope")).toThrow(ConfigError);
});
it("maps to an unencrypted-archive GroupFileMapping kept forever", () => {
const mapping = toGroupFileMapping({ roomId: "!a:x", workspaceId: WS, groupId: GROUP });
expect(mapping.archivePolicy).toBe("unencrypted-archive");
expect(mapping.retention).toEqual({ legalHold: false, mode: "keep" });
});
});
const env = {
DATABASE_URL: "postgres://bot@db/hub",
MATRIX_HOMESERVER_URL: "https://matrix.example.org",
MATRIX_USER: "archive-bot",
MATRIX_PASSWORD: "pw",
ROOM_MAPPINGS: JSON.stringify([{ roomId: "!a:x", workspaceId: WS, groupId: GROUP }]),
FILES_OCID_WEBDAV_URL: "https://files.example.org",
FILES_OCID_USER: "svc",
FILES_OCID_PASSWORD: "pw2",
ARCHIVE_BOT_HEALTH_PORT: "9100",
MATRIX_MEDIA_REDIRECT_HOSTS: " R2.example.com ,",
};
void env;
describe("loadConfig", () => {
it("parses the full env contract", () => {
const config = loadConfig(env);
expect(config.healthPort).toBe(9100);
expect(config.mediaRedirectHosts).toEqual(["r2.example.com"]);
expect(config.matrixDeviceId).toBe("HUB_ARCHIVE_BOT");
});
it("fails on missing secrets or a bad port", () => {
expect(() => loadConfig({ ...env, MATRIX_PASSWORD: "" })).toThrow(/MATRIX_PASSWORD/);
expect(() => loadConfig({ ...env, ARCHIVE_BOT_HEALTH_PORT: "70000" })).toThrow(/PORT/);
expect(() => loadConfig({ ...env, FILES_OCID_WEBDAV_URL: "ftp://x" })).toThrow(/http/);
});
});
describe("parseExportConfig (phase-B encrypted exports)", () => {
const R = "age1" + "q".repeat(58);
const base = {
ARCHIVE_EXPORT_AGE_RECIPIENTS: R,
ARCHIVE_EXPORT_R2_ACCESS_KEY: "k",
ARCHIVE_EXPORT_R2_SECRET_KEY: "s",
};
it("is null when nothing is configured", () => {
expect(parseExportConfig({})).toBeNull();
});
it("is null when explicitly disabled, even with credentials present", () => {
expect(parseExportConfig({ ...base, ARCHIVE_EXPORT_ENABLED: "false" })).toBeNull();
});
it("parses recipients, R2 credentials and defaults", () => {
const cfg = parseExportConfig(base);
expect(cfg).toMatchObject({
enabled: true,
ageRecipients: [R],
every: 500,
r2: { region: "auto", bucket: "hub-matrix-media" },
});
});
it("de-duplicates recipients and honours ARCHIVE_EXPORT_EVERY", () => {
const cfg = parseExportConfig({ ...base, ARCHIVE_EXPORT_AGE_RECIPIENTS: `${R}, ${R}`, ARCHIVE_EXPORT_EVERY: "50" });
expect(cfg?.ageRecipients).toEqual([R]);
expect(cfg?.every).toBe(50);
});
it("rejects malformed recipients and missing credentials", () => {
expect(() => parseExportConfig({ ...base, ARCHIVE_EXPORT_AGE_RECIPIENTS: "AGE-SECRET-KEY-1X" })).toThrow(ConfigError);
expect(() => parseExportConfig({ ARCHIVE_EXPORT_AGE_RECIPIENTS: R })).toThrow(/ACCESS_KEY/);
expect(() => parseExportConfig({ ...base, ARCHIVE_EXPORT_EVERY: "-1" })).toThrow(ConfigError);
expect(() => parseExportConfig({ ...base, ARCHIVE_EXPORT_R2_ENDPOINT: "http://insecure" })).toThrow(ConfigError);
});
it("wires into loadConfig", () => {
const cfg = loadConfig({ ...env, ...base });
expect(cfg.export).not.toBeNull();
expect(cfg.export?.ageRecipients).toEqual([R]);
// and stays null in the default case
expect(loadConfig(env).export).toBeNull();
});
});