Files
openclaw/extensions/matrix/src/migration-snapshot.test.ts
2026-04-28 03:28:17 +01:00

171 lines
5.3 KiB
TypeScript

import fs from "node:fs";
import path from "node:path";
import { withTempHome } from "openclaw/plugin-sdk/test-env";
import { beforeEach, describe, expect, it, vi } from "vitest";
const legacyCryptoInspectorAvailability = vi.hoisted(() => ({
available: true,
}));
vi.mock("./legacy-crypto-inspector-availability.js", () => ({
isMatrixLegacyCryptoInspectorAvailable: () => legacyCryptoInspectorAvailability.available,
}));
import { detectLegacyMatrixCrypto } from "./legacy-crypto.js";
import {
hasActionableMatrixMigration,
maybeCreateMatrixMigrationSnapshot,
resolveMatrixMigrationSnapshotMarkerPath,
resolveMatrixMigrationSnapshotOutputDir,
} from "./migration-snapshot.js";
import { resolveMatrixAccountStorageRoot } from "./storage-paths.js";
const createBackupArchiveMock = vi.hoisted(() => vi.fn());
const MATRIX_CREDENTIALS = {
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok-123",
} as const;
function makeMatrixMigrationConfig() {
return {
channels: {
matrix: MATRIX_CREDENTIALS,
},
} as never;
}
function seedLegacyMatrixCrypto(home: string) {
const stateDir = path.join(home, ".openclaw");
const { rootDir } = resolveMatrixAccountStorageRoot({
stateDir,
...MATRIX_CREDENTIALS,
});
fs.mkdirSync(path.join(rootDir, "crypto"), { recursive: true });
fs.writeFileSync(
path.join(rootDir, "crypto", "bot-sdk.json"),
JSON.stringify({ deviceId: "DEVICE123" }),
"utf8",
);
}
describe("matrix migration snapshots", () => {
beforeEach(() => {
createBackupArchiveMock.mockReset();
legacyCryptoInspectorAvailability.available = true;
createBackupArchiveMock.mockImplementation(
async (params: { output?: string; includeWorkspace?: boolean }) => {
const outputDir = params.output;
if (!outputDir) {
throw new Error("expected migration snapshot output dir");
}
fs.mkdirSync(outputDir, { recursive: true });
const archivePath = path.join(outputDir, "matrix-migration-backup.tar.gz");
fs.writeFileSync(archivePath, "archive\n", "utf8");
return {
createdAt: "2026-04-05T00:00:00.000Z",
archivePath,
includeWorkspace: params.includeWorkspace ?? true,
};
},
);
});
it("creates a backup marker after writing a pre-migration snapshot", async () => {
await withTempHome(async (home) => {
fs.writeFileSync(path.join(home, ".openclaw", "openclaw.json"), "{}\n", "utf8");
fs.writeFileSync(path.join(home, ".openclaw", "state.txt"), "state\n", "utf8");
const result = await maybeCreateMatrixMigrationSnapshot({
trigger: "unit-test",
createBackupArchive: createBackupArchiveMock,
});
expect(result.created).toBe(true);
expect(result.markerPath).toBe(resolveMatrixMigrationSnapshotMarkerPath(process.env));
expect(
result.archivePath.startsWith(resolveMatrixMigrationSnapshotOutputDir(process.env)),
).toBe(true);
expect(fs.existsSync(result.archivePath)).toBe(true);
expect(createBackupArchiveMock).toHaveBeenCalledWith({
output: resolveMatrixMigrationSnapshotOutputDir(process.env),
includeWorkspace: false,
});
});
});
it("treats resolvable Matrix legacy state as actionable", async () => {
await withTempHome(async (home) => {
const stateDir = path.join(home, ".openclaw");
fs.mkdirSync(path.join(stateDir, "matrix"), { recursive: true });
fs.writeFileSync(
path.join(stateDir, "matrix", "bot-storage.json"),
'{"legacy":true}',
"utf8",
);
expect(
hasActionableMatrixMigration({
cfg: {
channels: {
matrix: {
homeserver: "https://matrix.example.org",
userId: "@bot:example.org",
accessToken: "tok-123",
},
},
} as never,
env: process.env,
}),
).toBe(true);
});
});
it("treats legacy Matrix crypto as actionable when the extension inspector is present", async () => {
await withTempHome(async (home) => {
seedLegacyMatrixCrypto(home);
const cfg = makeMatrixMigrationConfig();
const detection = detectLegacyMatrixCrypto({
cfg,
env: process.env,
});
expect(detection.inspectorAvailable).toBe(true);
expect(detection.plans).toHaveLength(1);
expect(detection.warnings).toEqual([]);
expect(
hasActionableMatrixMigration({
cfg,
env: process.env,
}),
).toBe(true);
});
});
it("keeps legacy Matrix crypto pending but not actionable when the inspector artifact is unavailable", async () => {
legacyCryptoInspectorAvailability.available = false;
await withTempHome(async (home) => {
seedLegacyMatrixCrypto(home);
const cfg = makeMatrixMigrationConfig();
const detection = detectLegacyMatrixCrypto({
cfg,
env: process.env,
});
expect(detection.inspectorAvailable).toBe(false);
expect(detection.plans).toHaveLength(1);
expect(detection.warnings).toContain(
"Legacy Matrix encrypted state was detected, but the Matrix crypto inspector is unavailable.",
);
expect(
hasActionableMatrixMigration({
cfg,
env: process.env,
}),
).toBe(false);
});
});
});