test: share matrix migration fixtures

This commit is contained in:
Peter Steinberger 2026-03-26 16:24:37 +00:00
parent c2c136ae95
commit d9a7dcec4b
4 changed files with 336 additions and 389 deletions

View File

@ -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,

View File

@ -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();
},

View File

@ -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<string, never>;
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,

View File

@ -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,
),
);
}