mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-09 16:21:15 +00:00
195 lines
6.1 KiB
TypeScript
195 lines
6.1 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import { withTempHome } from "../../../test/helpers/temp-home.js";
|
|
import { autoPrepareLegacyMatrixCrypto, detectLegacyMatrixCrypto } from "./legacy-crypto.js";
|
|
import { resolveMatrixAccountStorageRoot } from "./storage-paths.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,
|
|
writeFile,
|
|
writeMatrixCredentials,
|
|
} from "./test-helpers.js";
|
|
|
|
function createDefaultMatrixConfig(): OpenClawConfig {
|
|
return {
|
|
channels: {
|
|
matrix: {
|
|
homeserver: MATRIX_TEST_HOMESERVER,
|
|
userId: MATRIX_DEFAULT_USER_ID,
|
|
accessToken: MATRIX_DEFAULT_ACCESS_TOKEN,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
|
|
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 };
|
|
}
|
|
|
|
function createOpsLegacyCryptoFixture(params: {
|
|
home: string;
|
|
accessToken?: string;
|
|
includeStoredCredentials?: boolean;
|
|
}) {
|
|
const stateDir = path.join(params.home, ".openclaw");
|
|
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 };
|
|
}
|
|
|
|
describe("matrix legacy encrypted-state migration", () => {
|
|
afterEach(() => {});
|
|
|
|
it("extracts a saved backup key into the new recovery-key path", async () => {
|
|
await withTempHome(async (home) => {
|
|
const { cfg, rootDir } = writeDefaultLegacyCryptoFixture(home);
|
|
|
|
const detection = detectLegacyMatrixCrypto({ cfg, env: process.env });
|
|
expect(detection.warnings).toEqual([]);
|
|
expect(detection.plans).toHaveLength(1);
|
|
|
|
const result = await autoPrepareLegacyMatrixCrypto({
|
|
cfg,
|
|
env: process.env,
|
|
deps: {
|
|
inspectLegacyStore: async () => ({
|
|
deviceId: MATRIX_DEFAULT_DEVICE_ID,
|
|
roomKeyCounts: { total: 12, backedUp: 12 },
|
|
backupVersion: "1",
|
|
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==");
|
|
});
|
|
});
|
|
|
|
it("skips migration when no legacy Matrix plans exist", async () => {
|
|
await withTempHome(async () => {
|
|
const result = await autoPrepareLegacyMatrixCrypto({
|
|
cfg: createDefaultMatrixConfig(),
|
|
env: process.env,
|
|
});
|
|
|
|
expect(result).toEqual({
|
|
migrated: false,
|
|
changes: [],
|
|
warnings: [],
|
|
});
|
|
});
|
|
});
|
|
|
|
it("warns when legacy local-only room keys cannot be recovered automatically", async () => {
|
|
await withTempHome(async (home) => {
|
|
const { cfg, rootDir } = writeDefaultLegacyCryptoFixture(home);
|
|
|
|
const result = await autoPrepareLegacyMatrixCrypto({
|
|
cfg,
|
|
env: process.env,
|
|
deps: {
|
|
inspectLegacyStore: async () => ({
|
|
deviceId: MATRIX_DEFAULT_DEVICE_ID,
|
|
roomKeyCounts: { total: 15, backedUp: 10 },
|
|
backupVersion: null,
|
|
decryptionKeyBase64: null,
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.migrated).toBe(true);
|
|
expect(result.warnings).toContain(
|
|
'Legacy Matrix encrypted state for account "default" contains 5 room key(s) that were never backed up. Backed-up keys can be restored automatically, but local-only encrypted history may remain unavailable after upgrade.',
|
|
);
|
|
expect(result.warnings).toContain(
|
|
'Legacy Matrix encrypted state for account "default" cannot be fully converted automatically because the old rust crypto store does not expose all local room keys for export.',
|
|
);
|
|
const state = JSON.parse(
|
|
fs.readFileSync(path.join(rootDir, "legacy-crypto-migration.json"), "utf8"),
|
|
) as { restoreStatus: string };
|
|
expect(state.restoreStatus).toBe("manual-action-required");
|
|
});
|
|
});
|
|
|
|
it("prefers stored credentials for named accounts when config is token-only", async () => {
|
|
await withTempHome(async (home) => {
|
|
const { rootDir } = createOpsLegacyCryptoFixture({
|
|
home,
|
|
includeStoredCredentials: true,
|
|
});
|
|
const cfg: OpenClawConfig = {
|
|
channels: {
|
|
matrix: {
|
|
accounts: {
|
|
ops: {
|
|
homeserver: MATRIX_TEST_HOMESERVER,
|
|
accessToken: MATRIX_OPS_ACCESS_TOKEN,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
};
|
|
|
|
const result = await autoPrepareLegacyMatrixCrypto({
|
|
cfg,
|
|
env: process.env,
|
|
deps: {
|
|
inspectLegacyStore: async () => ({
|
|
deviceId: MATRIX_OPS_DEVICE_ID,
|
|
roomKeyCounts: { total: 1, backedUp: 1 },
|
|
backupVersion: "1",
|
|
decryptionKeyBase64: "b3Bz",
|
|
}),
|
|
},
|
|
});
|
|
|
|
expect(result.migrated).toBe(true);
|
|
expect(fs.existsSync(path.join(rootDir, "recovery-key.json"))).toBe(true);
|
|
});
|
|
});
|
|
});
|