diff --git a/src/infra/matrix-legacy-crypto.test.ts b/src/infra/matrix-legacy-crypto.test.ts index 9766d516ad9..06fb7cfdd0a 100644 --- a/src/infra/matrix-legacy-crypto.test.ts +++ b/src/infra/matrix-legacy-crypto.test.ts @@ -6,45 +6,123 @@ import { withTempHome } from "../../test/helpers/temp-home.js"; import type { OpenClawConfig } from "../config/config.js"; import { autoPrepareLegacyMatrixCrypto, detectLegacyMatrixCrypto } from "./matrix-legacy-crypto.js"; import { MATRIX_LEGACY_CRYPTO_INSPECTOR_UNAVAILABLE_MESSAGE } from "./matrix-plugin-helper.js"; +import { + MATRIX_DEFAULT_ACCESS_TOKEN, + MATRIX_DEFAULT_DEVICE_ID, + MATRIX_DEFAULT_USER_ID, + MATRIX_OPS_ACCESS_TOKEN, + MATRIX_OPS_ACCOUNT_ID, + MATRIX_OPS_DEVICE_ID, + MATRIX_OPS_USER_ID, + MATRIX_TEST_HOMESERVER, + matrixHelperEnv, + writeFile, + writeMatrixCredentials, + writeMatrixPluginFixture, +} from "./matrix.test-helpers.js"; vi.unmock("../version.js"); -function writeFile(filePath: string, value: string) { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, value, "utf8"); -} - -function writeMatrixPluginFixture(rootDir: string): void { - fs.mkdirSync(rootDir, { recursive: true }); - fs.writeFileSync( - path.join(rootDir, "openclaw.plugin.json"), - JSON.stringify({ - id: "matrix", - configSchema: { - type: "object", - additionalProperties: false, +function createDefaultMatrixConfig(): OpenClawConfig { + return { + channels: { + matrix: { + homeserver: MATRIX_TEST_HOMESERVER, + userId: MATRIX_DEFAULT_USER_ID, + accessToken: MATRIX_DEFAULT_ACCESS_TOKEN, }, - }), - "utf8", - ); - fs.writeFileSync(path.join(rootDir, "index.js"), "export default {};\n", "utf8"); - fs.writeFileSync( - path.join(rootDir, "legacy-crypto-inspector.js"), - [ - "export async function inspectLegacyMatrixCryptoStore() {", - ' return { deviceId: "FIXTURE", roomKeyCounts: { total: 1, backedUp: 1 }, backupVersion: "1", decryptionKeyBase64: null };', - "}", - ].join("\n"), - "utf8", - ); + }, + }; } -const matrixHelperEnv = { - OPENCLAW_BUNDLED_PLUGINS_DIR: (home: string) => path.join(home, "bundled"), - OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1", - OPENCLAW_VERSION: undefined, - VITEST: "true", -}; +function writeDefaultLegacyCryptoFixture(home: string) { + const stateDir = path.join(home, ".openclaw"); + const cfg = createDefaultMatrixConfig(); + const { rootDir } = resolveMatrixAccountStorageRoot({ + stateDir, + homeserver: MATRIX_TEST_HOMESERVER, + userId: MATRIX_DEFAULT_USER_ID, + accessToken: MATRIX_DEFAULT_ACCESS_TOKEN, + }); + writeFile( + path.join(rootDir, "crypto", "bot-sdk.json"), + JSON.stringify({ deviceId: MATRIX_DEFAULT_DEVICE_ID }), + ); + return { cfg, rootDir, stateDir }; +} + +function createOpsLegacyCryptoFixture(params: { + home: string; + cfg: OpenClawConfig; + accessToken?: string; + includeStoredCredentials?: boolean; +}) { + const stateDir = path.join(params.home, ".openclaw"); + writeMatrixPluginFixture(path.join(params.home, "bundled", "matrix")); + writeFile( + path.join(stateDir, "matrix", "crypto", "bot-sdk.json"), + JSON.stringify({ deviceId: MATRIX_OPS_DEVICE_ID }), + ); + if (params.includeStoredCredentials) { + writeMatrixCredentials(stateDir, { + accountId: MATRIX_OPS_ACCOUNT_ID, + accessToken: params.accessToken ?? MATRIX_OPS_ACCESS_TOKEN, + deviceId: MATRIX_OPS_DEVICE_ID, + }); + } + const { rootDir } = resolveMatrixAccountStorageRoot({ + stateDir, + homeserver: MATRIX_TEST_HOMESERVER, + userId: MATRIX_OPS_USER_ID, + accessToken: params.accessToken ?? MATRIX_OPS_ACCESS_TOKEN, + accountId: MATRIX_OPS_ACCOUNT_ID, + }); + return { rootDir, stateDir }; +} + +async function expectPreparedOpsLegacyMigration(params: { + cfg: OpenClawConfig; + env: NodeJS.ProcessEnv; + rootDir: string; + inspectLegacyStore: { + deviceId: string; + roomKeyCounts: { total: number; backedUp: number }; + backupVersion: string; + decryptionKeyBase64: string; + }; + expectAccountId?: boolean; +}) { + const detection = detectLegacyMatrixCrypto({ cfg: params.cfg, env: params.env }); + expect(detection.warnings).toEqual([]); + expect(detection.plans).toHaveLength(1); + expect(detection.plans[0]?.accountId).toBe("ops"); + + const result = await autoPrepareLegacyMatrixCrypto({ + cfg: params.cfg, + env: params.env, + deps: { + inspectLegacyStore: async () => params.inspectLegacyStore, + }, + }); + + expect(result.migrated).toBe(true); + expect(result.warnings).toEqual([]); + const recovery = JSON.parse( + fs.readFileSync(path.join(params.rootDir, "recovery-key.json"), "utf8"), + ) as { + privateKeyBase64: string; + }; + expect(recovery.privateKeyBase64).toBe(params.inspectLegacyStore.decryptionKeyBase64); + if (!params.expectAccountId) { + return; + } + const state = JSON.parse( + fs.readFileSync(path.join(params.rootDir, "legacy-crypto-migration.json"), "utf8"), + ) as { + accountId: string; + }; + expect(state.accountId).toBe("ops"); +} describe("matrix legacy encrypted-state migration", () => { afterEach(() => { @@ -55,30 +133,14 @@ describe("matrix legacy encrypted-state migration", () => { await withTempHome( async (home) => { writeMatrixPluginFixture(path.join(home, "bundled", "matrix")); - const stateDir = path.join(home, ".openclaw"); - const cfg: OpenClawConfig = { - channels: { - matrix: { - homeserver: "https://matrix.example.org", - userId: "@bot:example.org", - accessToken: "tok-123", - }, - }, - }; - const { rootDir } = resolveMatrixAccountStorageRoot({ - stateDir, - homeserver: "https://matrix.example.org", - userId: "@bot:example.org", - accessToken: "tok-123", - }); - writeFile(path.join(rootDir, "crypto", "bot-sdk.json"), '{"deviceId":"DEVICE123"}'); + const { cfg, rootDir } = writeDefaultLegacyCryptoFixture(home); const detection = detectLegacyMatrixCrypto({ cfg, env: process.env }); expect(detection.warnings).toEqual([]); expect(detection.plans).toHaveLength(1); const inspectLegacyStore = vi.fn(async () => ({ - deviceId: "DEVICE123", + deviceId: MATRIX_DEFAULT_DEVICE_ID, roomKeyCounts: { total: 12, backedUp: 12 }, backupVersion: "1", decryptionKeyBase64: "YWJjZA==", @@ -116,30 +178,14 @@ describe("matrix legacy encrypted-state migration", () => { it("warns when legacy local-only room keys cannot be recovered automatically", async () => { await withTempHome(async (home) => { - const stateDir = path.join(home, ".openclaw"); - const cfg: OpenClawConfig = { - channels: { - matrix: { - homeserver: "https://matrix.example.org", - userId: "@bot:example.org", - accessToken: "tok-123", - }, - }, - }; - const { rootDir } = resolveMatrixAccountStorageRoot({ - stateDir, - homeserver: "https://matrix.example.org", - userId: "@bot:example.org", - accessToken: "tok-123", - }); - writeFile(path.join(rootDir, "crypto", "bot-sdk.json"), '{"deviceId":"DEVICE123"}'); + const { cfg, rootDir } = writeDefaultLegacyCryptoFixture(home); const result = await autoPrepareLegacyMatrixCrypto({ cfg, env: process.env, deps: { inspectLegacyStore: async () => ({ - deviceId: "DEVICE123", + deviceId: MATRIX_DEFAULT_DEVICE_ID, roomKeyCounts: { total: 15, backedUp: 10 }, backupVersion: null, decryptionKeyBase64: null, @@ -165,30 +211,14 @@ describe("matrix legacy encrypted-state migration", () => { it("warns instead of throwing when recovery-key persistence fails", async () => { await withTempHome(async (home) => { - const stateDir = path.join(home, ".openclaw"); - const cfg: OpenClawConfig = { - channels: { - matrix: { - homeserver: "https://matrix.example.org", - userId: "@bot:example.org", - accessToken: "tok-123", - }, - }, - }; - const { rootDir } = resolveMatrixAccountStorageRoot({ - stateDir, - homeserver: "https://matrix.example.org", - userId: "@bot:example.org", - accessToken: "tok-123", - }); - writeFile(path.join(rootDir, "crypto", "bot-sdk.json"), '{"deviceId":"DEVICE123"}'); + const { cfg, rootDir } = writeDefaultLegacyCryptoFixture(home); const result = await autoPrepareLegacyMatrixCrypto({ cfg, env: process.env, deps: { inspectLegacyStore: async () => ({ - deviceId: "DEVICE123", + deviceId: MATRIX_DEFAULT_DEVICE_ID, roomKeyCounts: { total: 12, backedUp: 12 }, backupVersion: "1", decryptionKeyBase64: "YWJjZA==", @@ -214,78 +244,36 @@ describe("matrix legacy encrypted-state migration", () => { it("prepares flat legacy crypto for the only configured non-default Matrix account", async () => { await withTempHome( async (home) => { - writeMatrixPluginFixture(path.join(home, "bundled", "matrix")); - const stateDir = path.join(home, ".openclaw"); - writeFile( - path.join(stateDir, "matrix", "crypto", "bot-sdk.json"), - JSON.stringify({ deviceId: "DEVICEOPS" }), - ); - writeFile( - path.join(stateDir, "credentials", "matrix", "credentials-ops.json"), - JSON.stringify( - { - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", - accessToken: "tok-ops", - deviceId: "DEVICEOPS", - }, - null, - 2, - ), - ); - const cfg: OpenClawConfig = { channels: { matrix: { accounts: { ops: { - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", + homeserver: MATRIX_TEST_HOMESERVER, + userId: MATRIX_OPS_USER_ID, }, }, }, }, }; - const { rootDir } = resolveMatrixAccountStorageRoot({ - stateDir, - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", - accessToken: "tok-ops", - accountId: "ops", + const { rootDir } = createOpsLegacyCryptoFixture({ + home, + cfg, + includeStoredCredentials: true, }); - const detection = detectLegacyMatrixCrypto({ cfg, env: process.env }); - expect(detection.warnings).toEqual([]); - expect(detection.plans).toHaveLength(1); - expect(detection.plans[0]?.accountId).toBe("ops"); - - const result = await autoPrepareLegacyMatrixCrypto({ + await expectPreparedOpsLegacyMigration({ cfg, env: process.env, - deps: { - inspectLegacyStore: async () => ({ - deviceId: "DEVICEOPS", - roomKeyCounts: { total: 6, backedUp: 6 }, - backupVersion: "21868", - decryptionKeyBase64: "YWJjZA==", - }), + rootDir, + inspectLegacyStore: { + deviceId: MATRIX_OPS_DEVICE_ID, + roomKeyCounts: { total: 6, backedUp: 6 }, + backupVersion: "21868", + decryptionKeyBase64: "YWJjZA==", }, + expectAccountId: true, }); - - expect(result.migrated).toBe(true); - expect(result.warnings).toEqual([]); - const recovery = JSON.parse( - fs.readFileSync(path.join(rootDir, "recovery-key.json"), "utf8"), - ) as { - privateKeyBase64: string; - }; - expect(recovery.privateKeyBase64).toBe("YWJjZA=="); - const state = JSON.parse( - fs.readFileSync(path.join(rootDir, "legacy-crypto-migration.json"), "utf8"), - ) as { - accountId: string; - }; - expect(state.accountId).toBe("ops"); }, { env: matrixHelperEnv }, ); @@ -294,13 +282,6 @@ describe("matrix legacy encrypted-state migration", () => { it("uses scoped Matrix env vars when resolving flat legacy crypto migration", async () => { await withTempHome( async (home) => { - writeMatrixPluginFixture(path.join(home, "bundled", "matrix")); - const stateDir = path.join(home, ".openclaw"); - writeFile( - path.join(stateDir, "matrix", "crypto", "bot-sdk.json"), - JSON.stringify({ deviceId: "DEVICEOPS" }), - ); - const cfg: OpenClawConfig = { channels: { matrix: { @@ -310,46 +291,29 @@ describe("matrix legacy encrypted-state migration", () => { }, }, }; - const { rootDir } = resolveMatrixAccountStorageRoot({ - stateDir, - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", + const { rootDir } = createOpsLegacyCryptoFixture({ + home, + cfg, accessToken: "tok-ops-env", - accountId: "ops", }); - const detection = detectLegacyMatrixCrypto({ cfg, env: process.env }); - expect(detection.warnings).toEqual([]); - expect(detection.plans).toHaveLength(1); - expect(detection.plans[0]?.accountId).toBe("ops"); - - const result = await autoPrepareLegacyMatrixCrypto({ + await expectPreparedOpsLegacyMigration({ cfg, env: process.env, - deps: { - inspectLegacyStore: async () => ({ - deviceId: "DEVICEOPS", - roomKeyCounts: { total: 4, backedUp: 4 }, - backupVersion: "9001", - decryptionKeyBase64: "YWJjZA==", - }), + rootDir, + inspectLegacyStore: { + deviceId: MATRIX_OPS_DEVICE_ID, + roomKeyCounts: { total: 4, backedUp: 4 }, + backupVersion: "9001", + decryptionKeyBase64: "YWJjZA==", }, }); - - expect(result.migrated).toBe(true); - expect(result.warnings).toEqual([]); - const recovery = JSON.parse( - fs.readFileSync(path.join(rootDir, "recovery-key.json"), "utf8"), - ) as { - privateKeyBase64: string; - }; - expect(recovery.privateKeyBase64).toBe("YWJjZA=="); }, { env: { ...matrixHelperEnv, - MATRIX_OPS_HOMESERVER: "https://matrix.example.org", - MATRIX_OPS_USER_ID: "@ops-bot:example.org", + MATRIX_OPS_HOMESERVER: MATRIX_TEST_HOMESERVER, + MATRIX_OPS_USER_ID, MATRIX_OPS_ACCESS_TOKEN: "tok-ops-env", }, }, @@ -361,7 +325,7 @@ describe("matrix legacy encrypted-state migration", () => { const stateDir = path.join(home, ".openclaw"); writeFile( path.join(stateDir, "matrix", "crypto", "bot-sdk.json"), - JSON.stringify({ deviceId: "DEVICEOPS" }), + JSON.stringify({ deviceId: MATRIX_OPS_DEVICE_ID }), ); const cfg: OpenClawConfig = { @@ -369,12 +333,12 @@ describe("matrix legacy encrypted-state migration", () => { matrix: { accounts: { ops: { - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", - accessToken: "tok-ops", + homeserver: MATRIX_TEST_HOMESERVER, + userId: MATRIX_OPS_USER_ID, + accessToken: MATRIX_OPS_ACCESS_TOKEN, }, alerts: { - homeserver: "https://matrix.example.org", + homeserver: MATRIX_TEST_HOMESERVER, userId: "@alerts-bot:example.org", accessToken: "tok-alerts", }, @@ -422,18 +386,10 @@ describe("matrix legacy encrypted-state migration", () => { const stateDir = path.join(home, ".openclaw"); writeFile( path.join(stateDir, "matrix", "crypto", "bot-sdk.json"), - '{"deviceId":"DEVICE123"}', + JSON.stringify({ deviceId: MATRIX_DEFAULT_DEVICE_ID }), ); - const cfg: OpenClawConfig = { - channels: { - matrix: { - homeserver: "https://matrix.example.org", - userId: "@bot:example.org", - accessToken: "tok-123", - }, - }, - }; + const cfg = createDefaultMatrixConfig(); const result = await autoPrepareLegacyMatrixCrypto({ cfg, diff --git a/src/infra/matrix-migration-config.test.ts b/src/infra/matrix-migration-config.test.ts index 9ae032d5887..60f7f835cda 100644 --- a/src/infra/matrix-migration-config.test.ts +++ b/src/infra/matrix-migration-config.test.ts @@ -1,54 +1,51 @@ -import fs from "node:fs"; import path from "node:path"; import { describe, expect, it } from "vitest"; import { withTempHome } from "../../test/helpers/temp-home.js"; import type { OpenClawConfig } from "../config/config.js"; import { resolveMatrixMigrationAccountTarget } from "./matrix-migration-config.js"; +import { + MATRIX_OPS_ACCESS_TOKEN, + MATRIX_OPS_ACCOUNT_ID, + MATRIX_OPS_USER_ID, + MATRIX_TEST_HOMESERVER, + writeMatrixCredentials, +} from "./matrix.test-helpers.js"; -function writeFile(filePath: string, value: string) { - fs.mkdirSync(path.dirname(filePath), { recursive: true }); - fs.writeFileSync(filePath, value, "utf8"); +function resolveOpsTarget(cfg: OpenClawConfig, env = process.env) { + return resolveMatrixMigrationAccountTarget({ + cfg, + env, + accountId: MATRIX_OPS_ACCOUNT_ID, + }); } describe("resolveMatrixMigrationAccountTarget", () => { it("reuses stored user identity for token-only configs when the access token matches", async () => { await withTempHome(async (home) => { const stateDir = path.join(home, ".openclaw"); - writeFile( - path.join(stateDir, "credentials", "matrix", "credentials-ops.json"), - JSON.stringify( - { - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", - accessToken: "tok-ops", - deviceId: "DEVICE-OPS", - }, - null, - 2, - ), - ); + writeMatrixCredentials(stateDir, { + accountId: MATRIX_OPS_ACCOUNT_ID, + deviceId: "DEVICE-OPS", + accessToken: MATRIX_OPS_ACCESS_TOKEN, + }); const cfg: OpenClawConfig = { channels: { matrix: { accounts: { ops: { - homeserver: "https://matrix.example.org", - accessToken: "tok-ops", + homeserver: MATRIX_TEST_HOMESERVER, + accessToken: MATRIX_OPS_ACCESS_TOKEN, }, }, }, }, }; - const target = resolveMatrixMigrationAccountTarget({ - cfg, - env: process.env, - accountId: "ops", - }); + const target = resolveOpsTarget(cfg); expect(target).not.toBeNull(); - expect(target?.userId).toBe("@ops-bot:example.org"); + expect(target?.userId).toBe(MATRIX_OPS_USER_ID); expect(target?.storedDeviceId).toBe("DEVICE-OPS"); }); }); @@ -56,26 +53,19 @@ describe("resolveMatrixMigrationAccountTarget", () => { it("ignores stored device IDs from stale cached Matrix credentials", async () => { await withTempHome(async (home) => { const stateDir = path.join(home, ".openclaw"); - writeFile( - path.join(stateDir, "credentials", "matrix", "credentials-ops.json"), - JSON.stringify( - { - homeserver: "https://matrix.example.org", - userId: "@old-bot:example.org", - accessToken: "tok-old", - deviceId: "DEVICE-OLD", - }, - null, - 2, - ), - ); + writeMatrixCredentials(stateDir, { + accountId: MATRIX_OPS_ACCOUNT_ID, + userId: "@old-bot:example.org", + accessToken: "tok-old", + deviceId: "DEVICE-OLD", + }); const cfg: OpenClawConfig = { channels: { matrix: { accounts: { ops: { - homeserver: "https://matrix.example.org", + homeserver: MATRIX_TEST_HOMESERVER, userId: "@new-bot:example.org", accessToken: "tok-new", }, @@ -84,11 +74,7 @@ describe("resolveMatrixMigrationAccountTarget", () => { }, }; - const target = resolveMatrixMigrationAccountTarget({ - cfg, - env: process.env, - accountId: "ops", - }); + const target = resolveOpsTarget(cfg); expect(target).not.toBeNull(); expect(target?.userId).toBe("@new-bot:example.org"); @@ -100,26 +86,19 @@ describe("resolveMatrixMigrationAccountTarget", () => { it("does not trust stale stored creds on the same homeserver when the token changes", async () => { await withTempHome(async (home) => { const stateDir = path.join(home, ".openclaw"); - writeFile( - path.join(stateDir, "credentials", "matrix", "credentials-ops.json"), - JSON.stringify( - { - homeserver: "https://matrix.example.org", - userId: "@old-bot:example.org", - accessToken: "tok-old", - deviceId: "DEVICE-OLD", - }, - null, - 2, - ), - ); + writeMatrixCredentials(stateDir, { + accountId: MATRIX_OPS_ACCOUNT_ID, + userId: "@old-bot:example.org", + accessToken: "tok-old", + deviceId: "DEVICE-OLD", + }); const cfg: OpenClawConfig = { channels: { matrix: { accounts: { ops: { - homeserver: "https://matrix.example.org", + homeserver: MATRIX_TEST_HOMESERVER, accessToken: "tok-new", }, }, @@ -127,11 +106,7 @@ describe("resolveMatrixMigrationAccountTarget", () => { }, }; - const target = resolveMatrixMigrationAccountTarget({ - cfg, - env: process.env, - accountId: "ops", - }); + const target = resolveOpsTarget(cfg); expect(target).toBeNull(); }); @@ -140,43 +115,31 @@ describe("resolveMatrixMigrationAccountTarget", () => { it("does not inherit the base userId for non-default token-only accounts", async () => { await withTempHome(async (home) => { const stateDir = path.join(home, ".openclaw"); - writeFile( - path.join(stateDir, "credentials", "matrix", "credentials-ops.json"), - JSON.stringify( - { - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", - accessToken: "tok-ops", - deviceId: "DEVICE-OPS", - }, - null, - 2, - ), - ); + writeMatrixCredentials(stateDir, { + accountId: MATRIX_OPS_ACCOUNT_ID, + deviceId: "DEVICE-OPS", + accessToken: MATRIX_OPS_ACCESS_TOKEN, + }); const cfg: OpenClawConfig = { channels: { matrix: { - homeserver: "https://matrix.example.org", + homeserver: MATRIX_TEST_HOMESERVER, userId: "@base-bot:example.org", accounts: { ops: { - homeserver: "https://matrix.example.org", - accessToken: "tok-ops", + homeserver: MATRIX_TEST_HOMESERVER, + accessToken: MATRIX_OPS_ACCESS_TOKEN, }, }, }, }, }; - const target = resolveMatrixMigrationAccountTarget({ - cfg, - env: process.env, - accountId: "ops", - }); + const target = resolveOpsTarget(cfg); expect(target).not.toBeNull(); - expect(target?.userId).toBe("@ops-bot:example.org"); + expect(target?.userId).toBe(MATRIX_OPS_USER_ID); expect(target?.storedDeviceId).toBe("DEVICE-OPS"); }); }); @@ -186,24 +149,20 @@ describe("resolveMatrixMigrationAccountTarget", () => { const cfg: OpenClawConfig = { channels: { matrix: { - homeserver: "https://matrix.example.org", + homeserver: MATRIX_TEST_HOMESERVER, userId: "@base-bot:example.org", accessToken: "tok-base", accounts: { ops: { - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", + homeserver: MATRIX_TEST_HOMESERVER, + userId: MATRIX_OPS_USER_ID, }, }, }, }, }; - const target = resolveMatrixMigrationAccountTarget({ - cfg, - env: process.env, - accountId: "ops", - }); + const target = resolveOpsTarget(cfg); expect(target).toBeNull(); }); @@ -217,19 +176,15 @@ describe("resolveMatrixMigrationAccountTarget", () => { matrix: { accounts: { ops: { - homeserver: "https://matrix.example.org", - userId: "@ops-bot:example.org", + homeserver: MATRIX_TEST_HOMESERVER, + userId: MATRIX_OPS_USER_ID, }, }, }, }, }; - const target = resolveMatrixMigrationAccountTarget({ - cfg, - env: process.env, - accountId: "ops", - }); + const target = resolveOpsTarget(cfg); expect(target).toBeNull(); }, diff --git a/src/infra/matrix-plugin-helper.test.ts b/src/infra/matrix-plugin-helper.test.ts index 0f6bba9963d..d2f9c05bc29 100644 --- a/src/infra/matrix-plugin-helper.test.ts +++ b/src/infra/matrix-plugin-helper.test.ts @@ -7,40 +7,39 @@ 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"); -function writeMatrixPluginFixture(rootDir: string, helperBody: string): void { - fs.mkdirSync(rootDir, { recursive: true }); - fs.writeFileSync( - path.join(rootDir, "openclaw.plugin.json"), - JSON.stringify({ - id: "matrix", - configSchema: { - type: "object", - additionalProperties: false, - }, - }), - "utf8", - ); - fs.writeFileSync(path.join(rootDir, "index.js"), "export default {};\n", "utf8"); - fs.writeFileSync(path.join(rootDir, "legacy-crypto-inspector.js"), helperBody, "utf8"); -} +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, + }); -function writeMatrixPluginManifest(rootDir: string): void { - fs.mkdirSync(rootDir, { recursive: true }); - fs.writeFileSync( - path.join(rootDir, "openclaw.plugin.json"), - JSON.stringify({ - id: "matrix", - configSchema: { - type: "object", - additionalProperties: false, - }, + await expect( + inspectLegacyStore({ + cryptoRootDir: "/tmp/legacy", + userId: MATRIX_DEFAULT_USER_ID, + deviceId: MATRIX_DEFAULT_DEVICE_ID, }), - "utf8", - ); - fs.writeFileSync(path.join(rootDir, "index.js"), "export default {};\n", "utf8"); + ).resolves.toEqual(params.expected); } describe("matrix plugin helper resolution", () => { @@ -48,13 +47,6 @@ describe("matrix plugin helper resolution", () => { vi.restoreAllMocks(); }); - const helperEnv = { - OPENCLAW_BUNDLED_PLUGINS_DIR: (home: string) => path.join(home, "bundled"), - OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1", - OPENCLAW_VERSION: undefined, - VITEST: "true", - } as const; - it("loads the legacy crypto inspector from the bundled matrix plugin", async () => { await withTempHome( async (home) => { @@ -70,26 +62,18 @@ describe("matrix plugin helper resolution", () => { const cfg = {} as const; - expect(isMatrixLegacyCryptoInspectorAvailable({ cfg, env: process.env })).toBe(true); - const inspectLegacyStore = await loadMatrixLegacyCryptoInspector({ + await expectLoadedInspector({ cfg, env: process.env, - }); - - await expect( - inspectLegacyStore({ - cryptoRootDir: "/tmp/legacy", - userId: "@bot:example.org", - deviceId: "DEVICE123", - }), - ).resolves.toEqual({ - deviceId: "BUNDLED", - roomKeyCounts: { total: 7, backedUp: 6 }, - backupVersion: "1", - decryptionKeyBase64: "YWJjZA==", + expected: { + deviceId: "BUNDLED", + roomKeyCounts: { total: 7, backedUp: 6 }, + backupVersion: "1", + decryptionKeyBase64: "YWJjZA==", + }, }); }, - { env: helperEnv }, + { env: matrixHelperEnv }, ); }); @@ -123,26 +107,18 @@ describe("matrix plugin helper resolution", () => { }, }; - expect(isMatrixLegacyCryptoInspectorAvailable({ cfg, env: process.env })).toBe(true); - const inspectLegacyStore = await loadMatrixLegacyCryptoInspector({ + await expectLoadedInspector({ cfg, env: process.env, - }); - - await expect( - inspectLegacyStore({ - cryptoRootDir: "/tmp/legacy", - userId: "@bot:example.org", - deviceId: "DEVICE123", - }), - ).resolves.toEqual({ - deviceId: "CONFIG", - roomKeyCounts: null, - backupVersion: null, - decryptionKeyBase64: null, + expected: { + deviceId: "CONFIG", + roomKeyCounts: null, + backupVersion: null, + decryptionKeyBase64: null, + }, }); }, - { env: helperEnv }, + { env: matrixHelperEnv }, ); }); @@ -175,23 +151,15 @@ describe("matrix plugin helper resolution", () => { }, }; - expect(isMatrixLegacyCryptoInspectorAvailable({ cfg, env: process.env })).toBe(true); - const inspectLegacyStore = await loadMatrixLegacyCryptoInspector({ + await expectLoadedInspector({ cfg, env: process.env, - }); - - await expect( - inspectLegacyStore({ - cryptoRootDir: "/tmp/legacy", - userId: "@bot:example.org", - deviceId: "DEVICE123", - }), - ).resolves.toEqual({ - deviceId: "SRCJS", - roomKeyCounts: null, - backupVersion: null, - decryptionKeyBase64: null, + expected: { + deviceId: "SRCJS", + roomKeyCounts: null, + backupVersion: null, + decryptionKeyBase64: null, + }, }); }, { @@ -209,18 +177,7 @@ describe("matrix plugin helper resolution", () => { const outsideRoot = path.join(home, "outside"); fs.mkdirSync(customRoot, { recursive: true }); fs.mkdirSync(outsideRoot, { recursive: true }); - fs.writeFileSync( - path.join(customRoot, "openclaw.plugin.json"), - JSON.stringify({ - id: "matrix", - configSchema: { - type: "object", - additionalProperties: false, - }, - }), - "utf8", - ); - fs.writeFileSync(path.join(customRoot, "index.js"), "export default {};\n", "utf8"); + writeMatrixPluginManifest(customRoot); const outsideHelper = path.join(outsideRoot, "legacy-crypto-inspector.js"); fs.writeFileSync( outsideHelper, diff --git a/src/infra/matrix.test-helpers.ts b/src/infra/matrix.test-helpers.ts new file mode 100644 index 00000000000..b417935c58d --- /dev/null +++ b/src/infra/matrix.test-helpers.ts @@ -0,0 +1,79 @@ +import fs from "node:fs"; +import path from "node:path"; + +export const MATRIX_TEST_HOMESERVER = "https://matrix.example.org"; +export const MATRIX_DEFAULT_USER_ID = "@bot:example.org"; +export const MATRIX_DEFAULT_ACCESS_TOKEN = "tok-123"; +export const MATRIX_DEFAULT_DEVICE_ID = "DEVICE123"; +export const MATRIX_OPS_ACCOUNT_ID = "ops"; +export const MATRIX_OPS_USER_ID = "@ops-bot:example.org"; +export const MATRIX_OPS_ACCESS_TOKEN = "tok-ops"; +export const MATRIX_OPS_DEVICE_ID = "DEVICEOPS"; + +export const matrixHelperEnv = { + OPENCLAW_BUNDLED_PLUGINS_DIR: (home: string) => path.join(home, "bundled"), + OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE: "1", + OPENCLAW_VERSION: undefined, + VITEST: "true", +} as const; + +export function writeFile(filePath: string, value: string) { + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + fs.writeFileSync(filePath, value, "utf8"); +} + +export function writeMatrixPluginManifest(rootDir: string): void { + fs.mkdirSync(rootDir, { recursive: true }); + fs.writeFileSync( + path.join(rootDir, "openclaw.plugin.json"), + JSON.stringify({ + id: "matrix", + configSchema: { + type: "object", + additionalProperties: false, + }, + }), + "utf8", + ); + fs.writeFileSync(path.join(rootDir, "index.js"), "export default {};\n", "utf8"); +} + +export function writeMatrixPluginFixture(rootDir: string, helperBody?: string): void { + writeMatrixPluginManifest(rootDir); + fs.writeFileSync( + path.join(rootDir, "legacy-crypto-inspector.js"), + helperBody ?? + [ + "export async function inspectLegacyMatrixCryptoStore() {", + ' return { deviceId: "FIXTURE", roomKeyCounts: { total: 1, backedUp: 1 }, backupVersion: "1", decryptionKeyBase64: null };', + "}", + ].join("\n"), + "utf8", + ); +} + +export function writeMatrixCredentials( + stateDir: string, + params?: { + accountId?: string; + homeserver?: string; + userId?: string; + accessToken?: string; + deviceId?: string; + }, +) { + const accountId = params?.accountId ?? MATRIX_OPS_ACCOUNT_ID; + writeFile( + path.join(stateDir, "credentials", "matrix", `credentials-${accountId}.json`), + JSON.stringify( + { + homeserver: params?.homeserver ?? MATRIX_TEST_HOMESERVER, + userId: params?.userId ?? MATRIX_OPS_USER_ID, + accessToken: params?.accessToken ?? MATRIX_OPS_ACCESS_TOKEN, + deviceId: params?.deviceId ?? MATRIX_OPS_DEVICE_ID, + }, + null, + 2, + ), + ); +}