import fs from "node:fs"; import path from "node:path"; import { afterEach, describe, expect, it, vi } from "vitest"; import { withTempHome } from "../../test/helpers/temp-home.js"; import type { OpenClawConfig } from "../config/config.js"; import { isMatrixLegacyCryptoInspectorAvailable, loadMatrixLegacyCryptoInspector, } from "./matrix-plugin-helper.js"; import { MATRIX_DEFAULT_DEVICE_ID, MATRIX_DEFAULT_USER_ID, matrixHelperEnv, writeMatrixPluginFixture, writeMatrixPluginManifest, } from "./matrix.test-helpers.js"; vi.unmock("../version.js"); async function expectLoadedInspector(params: { cfg: OpenClawConfig | Record; env: NodeJS.ProcessEnv; expected: { deviceId: string; roomKeyCounts: { total: number; backedUp: number } | null; backupVersion: string | null; decryptionKeyBase64: string | null; }; }) { expect(isMatrixLegacyCryptoInspectorAvailable({ cfg: params.cfg, env: params.env })).toBe(true); const inspectLegacyStore = await loadMatrixLegacyCryptoInspector({ cfg: params.cfg, env: params.env, }); await expect( inspectLegacyStore({ cryptoRootDir: "/tmp/legacy", userId: MATRIX_DEFAULT_USER_ID, deviceId: MATRIX_DEFAULT_DEVICE_ID, }), ).resolves.toEqual(params.expected); } describe("matrix plugin helper resolution", () => { afterEach(() => { vi.restoreAllMocks(); }); it("loads the legacy crypto inspector from the bundled matrix plugin", async () => { await withTempHome( async (home) => { const bundledRoot = path.join(home, "bundled", "matrix"); writeMatrixPluginFixture( bundledRoot, [ "export async function inspectLegacyMatrixCryptoStore() {", ' return { deviceId: "BUNDLED", roomKeyCounts: { total: 7, backedUp: 6 }, backupVersion: "1", decryptionKeyBase64: "YWJjZA==" };', "}", ].join("\n"), ); const cfg = {} as const; await expectLoadedInspector({ cfg, env: process.env, expected: { deviceId: "BUNDLED", roomKeyCounts: { total: 7, backedUp: 6 }, backupVersion: "1", decryptionKeyBase64: "YWJjZA==", }, }); }, { env: matrixHelperEnv }, ); }); it("prefers configured plugin load paths over bundled matrix plugins", async () => { await withTempHome( async (home) => { const bundledRoot = path.join(home, "bundled", "matrix"); const customRoot = path.join(home, "plugins", "matrix-local"); writeMatrixPluginFixture( bundledRoot, [ "export async function inspectLegacyMatrixCryptoStore() {", ' return { deviceId: "BUNDLED", roomKeyCounts: null, backupVersion: null, decryptionKeyBase64: null };', "}", ].join("\n"), ); writeMatrixPluginFixture( customRoot, [ "export default async function inspectLegacyMatrixCryptoStore() {", ' return { deviceId: "CONFIG", roomKeyCounts: null, backupVersion: null, decryptionKeyBase64: null };', "}", ].join("\n"), ); const cfg: OpenClawConfig = { plugins: { load: { paths: [customRoot], }, }, }; await expectLoadedInspector({ cfg, env: process.env, expected: { deviceId: "CONFIG", roomKeyCounts: null, backupVersion: null, decryptionKeyBase64: null, }, }); }, { env: matrixHelperEnv }, ); }); it("keeps source-style root helper shims on the Jiti fallback path", async () => { await withTempHome( async (home) => { const customRoot = path.join(home, "plugins", "matrix-local"); writeMatrixPluginManifest(customRoot); fs.mkdirSync(path.join(customRoot, "src", "matrix"), { recursive: true }); fs.writeFileSync( path.join(customRoot, "legacy-crypto-inspector.js"), 'export { inspectLegacyMatrixCryptoStore } from "./src/matrix/legacy-crypto-inspector.js";\n', "utf8", ); fs.writeFileSync( path.join(customRoot, "src", "matrix", "legacy-crypto-inspector.ts"), [ "export async function inspectLegacyMatrixCryptoStore() {", ' return { deviceId: "SRCJS", roomKeyCounts: null, backupVersion: null, decryptionKeyBase64: null };', "}", ].join("\n"), "utf8", ); const cfg: OpenClawConfig = { plugins: { load: { paths: [customRoot], }, }, }; await expectLoadedInspector({ cfg, env: process.env, expected: { deviceId: "SRCJS", roomKeyCounts: null, backupVersion: null, decryptionKeyBase64: null, }, }); }, { env: { OPENCLAW_BUNDLED_PLUGINS_DIR: (home) => path.join(home, "empty-bundled"), }, }, ); }); it("rejects helper files that escape the plugin root", async () => { await withTempHome( async (home) => { const customRoot = path.join(home, "plugins", "matrix-local"); const outsideRoot = path.join(home, "outside"); fs.mkdirSync(customRoot, { recursive: true }); fs.mkdirSync(outsideRoot, { recursive: true }); writeMatrixPluginManifest(customRoot); const outsideHelper = path.join(outsideRoot, "legacy-crypto-inspector.js"); fs.writeFileSync( outsideHelper, 'export default async function inspectLegacyMatrixCryptoStore() { return { deviceId: "ESCAPE", roomKeyCounts: null, backupVersion: null, decryptionKeyBase64: null }; }\n', "utf8", ); try { fs.symlinkSync( outsideHelper, path.join(customRoot, "legacy-crypto-inspector.js"), process.platform === "win32" ? "file" : undefined, ); } catch { return; } const cfg: OpenClawConfig = { plugins: { load: { paths: [customRoot], }, }, }; expect(isMatrixLegacyCryptoInspectorAvailable({ cfg, env: process.env })).toBe(false); await expect( loadMatrixLegacyCryptoInspector({ cfg, env: process.env, }), ).rejects.toThrow("Matrix plugin helper path is unsafe"); }, { env: { OPENCLAW_BUNDLED_PLUGINS_DIR: (home) => path.join(home, "empty-bundled"), }, }, ); }); });