mirror of https://github.com/openclaw/openclaw.git
test: share matrix migration fixtures
This commit is contained in:
parent
c2c136ae95
commit
d9a7dcec4b
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
);
|
||||
}
|
||||
Loading…
Reference in New Issue