fix(matrix): pass loaded cfg to verify CLI subcommands (#70992)

The verify CLI subcommands (status, backup status, backup reset, backup
restore, bootstrap, device <key>) all crashed with 'Matrix runtime
client requires a resolved runtime config' because resolveMatrixCliAccountId
loaded the runtime config to resolve the account ID but discarded it,
so the action functions were called without cfg.

Introduce resolveMatrixCliAccountContext which returns both accountId
and cfg, and use it in the 6 verify subcommand handlers. Add unit tests
in cli.test.ts that lock in the contract.

Closes #70992
This commit is contained in:
luyao618
2026-04-24 20:51:51 +08:00
committed by Gustavo Madeira Santana
parent 8226a3f8fe
commit dfbf51791b
2 changed files with 116 additions and 16 deletions

View File

@@ -211,7 +211,9 @@ describe("matrix CLI verification commands", () => {
});
const program = buildProgram();
await program.parseAsync(["matrix", "verify", "bootstrap", "--json"], { from: "user" });
await program.parseAsync(["matrix", "verify", "bootstrap", "--json"], {
from: "user",
});
expect(process.exitCode).toBe(1);
});
@@ -267,6 +269,76 @@ describe("matrix CLI verification commands", () => {
expect(process.exitCode).toBe(1);
});
it("passes loaded cfg to verify status action", async () => {
const fakeCfg = { channels: { matrix: {} } };
matrixRuntimeLoadConfigMock.mockReturnValue(fakeCfg);
mockMatrixVerificationStatus({ recoveryKeyCreatedAt: null });
const program = buildProgram();
await program.parseAsync(["matrix", "verify", "status"], { from: "user" });
expect(getMatrixVerificationStatusMock).toHaveBeenCalledWith(
expect.objectContaining({ cfg: fakeCfg }),
);
});
it("passes loaded cfg to all verify subcommands", async () => {
const fakeCfg = { channels: { matrix: {} } };
matrixRuntimeLoadConfigMock.mockReturnValue(fakeCfg);
// verify bootstrap
const program1 = buildProgram();
await program1.parseAsync(["matrix", "verify", "bootstrap"], {
from: "user",
});
expect(bootstrapMatrixVerificationMock).toHaveBeenCalledWith(
expect.objectContaining({ cfg: fakeCfg }),
);
// verify device
verifyMatrixRecoveryKeyMock.mockResolvedValue({ success: true });
const program2 = buildProgram();
await program2.parseAsync(["matrix", "verify", "device", "test-key"], {
from: "user",
});
expect(verifyMatrixRecoveryKeyMock).toHaveBeenCalledWith(
"test-key",
expect.objectContaining({ cfg: fakeCfg }),
);
// verify backup status
getMatrixRoomKeyBackupStatusMock.mockResolvedValue({});
const program3 = buildProgram();
await program3.parseAsync(["matrix", "verify", "backup", "status"], {
from: "user",
});
expect(getMatrixRoomKeyBackupStatusMock).toHaveBeenCalledWith(
expect.objectContaining({ cfg: fakeCfg }),
);
// verify backup reset
const program4 = buildProgram();
await program4.parseAsync(["matrix", "verify", "backup", "reset", "--yes"], { from: "user" });
expect(resetMatrixRoomKeyBackupMock).toHaveBeenCalledWith(
expect.objectContaining({ cfg: fakeCfg }),
);
// verify backup restore
restoreMatrixRoomKeyBackupMock.mockResolvedValue({
success: true,
imported: 0,
total: 0,
backup: {},
});
const program5 = buildProgram();
await program5.parseAsync(["matrix", "verify", "backup", "restore"], {
from: "user",
});
expect(restoreMatrixRoomKeyBackupMock).toHaveBeenCalledWith(
expect.objectContaining({ cfg: fakeCfg }),
);
});
it("lists matrix devices", async () => {
listMatrixOwnDevicesMock.mockResolvedValue([
{
@@ -332,7 +404,9 @@ describe("matrix CLI verification commands", () => {
from: "user",
});
expect(pruneMatrixStaleGatewayDevicesMock).toHaveBeenCalledWith({ accountId: "poe" });
expect(pruneMatrixStaleGatewayDevicesMock).toHaveBeenCalledWith({
accountId: "poe",
});
expect(console.log).toHaveBeenCalledWith("Deleted stale OpenClaw devices: BritdXC6iL");
expect(console.log).toHaveBeenCalledWith("Current device: A7hWrQ70ea");
expect(console.log).toHaveBeenCalledWith("Remaining devices: 1");
@@ -452,7 +526,9 @@ describe("matrix CLI verification commands", () => {
{ from: "user" },
);
expect(bootstrapMatrixVerificationMock).toHaveBeenCalledWith({ accountId: "ops" });
expect(bootstrapMatrixVerificationMock).toHaveBeenCalledWith({
accountId: "ops",
});
expect(console.log).toHaveBeenCalledWith("Matrix verification bootstrap: complete");
expect(console.log).toHaveBeenCalledWith(
`Recovery key created at: ${formatExpectedLocalTimestamp("2026-03-09T06:00:00.000Z")}`,
@@ -705,7 +781,9 @@ describe("matrix CLI verification commands", () => {
});
const program = buildProgram();
await program.parseAsync(["matrix", "verify", "bootstrap", "--json"], { from: "user" });
await program.parseAsync(["matrix", "verify", "bootstrap", "--json"], {
from: "user",
});
expect(process.exitCode).toBe(0);
});
@@ -715,7 +793,9 @@ describe("matrix CLI verification commands", () => {
mockMatrixVerificationStatus({ recoveryKeyCreatedAt: recoveryCreatedAt });
const program = buildProgram();
await program.parseAsync(["matrix", "verify", "status", "--verbose"], { from: "user" });
await program.parseAsync(["matrix", "verify", "status", "--verbose"], {
from: "user",
});
expect(console.log).toHaveBeenCalledWith(
`Recovery key created at: ${formatExpectedLocalTimestamp(recoveryCreatedAt)}`,
@@ -920,7 +1000,9 @@ describe("matrix CLI verification commands", () => {
it("requires --yes before resetting the Matrix room-key backup", async () => {
const program = buildProgram();
await program.parseAsync(["matrix", "verify", "backup", "reset"], { from: "user" });
await program.parseAsync(["matrix", "verify", "backup", "reset"], {
from: "user",
});
expect(process.exitCode).toBe(1);
expect(resetMatrixRoomKeyBackupMock).not.toHaveBeenCalled();
@@ -936,7 +1018,10 @@ describe("matrix CLI verification commands", () => {
from: "user",
});
expect(resetMatrixRoomKeyBackupMock).toHaveBeenCalledWith({ accountId: "default" });
expect(resetMatrixRoomKeyBackupMock).toHaveBeenCalledWith({
accountId: "default",
cfg: {},
});
expect(console.log).toHaveBeenCalledWith("Reset success: yes");
expect(console.log).toHaveBeenCalledWith("Previous backup version: 1");
expect(console.log).toHaveBeenCalledWith("Deleted backup version: 1");
@@ -981,6 +1066,7 @@ describe("matrix CLI verification commands", () => {
expect(getMatrixVerificationStatusMock).toHaveBeenCalledWith({
accountId: "assistant",
cfg: {},
includeRecoveryKey: false,
});
expect(console.log).toHaveBeenCalledWith("Account: assistant");

View File

@@ -96,6 +96,17 @@ function resolveMatrixCliAccountId(accountId?: string): string {
return resolveMatrixAuthContext({ cfg, accountId }).accountId;
}
function resolveMatrixCliAccountContext(accountId?: string): {
accountId: string;
cfg: CoreConfig;
} {
const cfg = getMatrixRuntime().config.loadConfig() as CoreConfig;
return {
accountId: resolveMatrixAuthContext({ cfg, accountId }).accountId,
cfg,
};
}
function formatMatrixCliCommand(command: string, accountId?: string): string {
const normalizedAccountId = normalizeAccountId(accountId);
const suffix = normalizedAccountId === "default" ? "" : ` --account ${normalizedAccountId}`;
@@ -925,13 +936,14 @@ export function registerMatrixCli(params: { program: Command }): void {
includeRecoveryKey?: boolean;
json?: boolean;
}) => {
const accountId = resolveMatrixCliAccountId(options.account);
const { accountId, cfg } = resolveMatrixCliAccountContext(options.account);
await runMatrixCliCommand({
verbose: options.verbose === true,
json: options.json === true,
run: async () =>
await getMatrixVerificationStatus({
accountId,
cfg,
includeRecoveryKey: options.includeRecoveryKey === true,
}),
onText: (status, verbose) => {
@@ -952,11 +964,11 @@ export function registerMatrixCli(params: { program: Command }): void {
.option("--verbose", "Show detailed diagnostics")
.option("--json", "Output as JSON")
.action(async (options: { account?: string; verbose?: boolean; json?: boolean }) => {
const accountId = resolveMatrixCliAccountId(options.account);
const { accountId, cfg } = resolveMatrixCliAccountContext(options.account);
await runMatrixCliCommand({
verbose: options.verbose === true,
json: options.json === true,
run: async () => await getMatrixRoomKeyBackupStatus({ accountId }),
run: async () => await getMatrixRoomKeyBackupStatus({ accountId, cfg }),
onText: (status, verbose) => {
printAccountLabel(accountId);
printBackupSummary(status);
@@ -979,7 +991,7 @@ export function registerMatrixCli(params: { program: Command }): void {
.option("--json", "Output as JSON")
.action(
async (options: { account?: string; yes?: boolean; verbose?: boolean; json?: boolean }) => {
const accountId = resolveMatrixCliAccountId(options.account);
const { accountId, cfg } = resolveMatrixCliAccountContext(options.account);
await runMatrixCliCommand({
verbose: options.verbose === true,
json: options.json === true,
@@ -987,7 +999,7 @@ export function registerMatrixCli(params: { program: Command }): void {
if (options.yes !== true) {
throw new Error("Refusing to reset Matrix room-key backup without --yes");
}
return await resetMatrixRoomKeyBackup({ accountId });
return await resetMatrixRoomKeyBackup({ accountId, cfg });
},
onText: (result, verbose) => {
printAccountLabel(accountId);
@@ -1025,13 +1037,14 @@ export function registerMatrixCli(params: { program: Command }): void {
verbose?: boolean;
json?: boolean;
}) => {
const accountId = resolveMatrixCliAccountId(options.account);
const { accountId, cfg } = resolveMatrixCliAccountContext(options.account);
await runMatrixCliCommand({
verbose: options.verbose === true,
json: options.json === true,
run: async () =>
await restoreMatrixRoomKeyBackup({
accountId,
cfg,
recoveryKey: options.recoveryKey,
}),
onText: (result, verbose) => {
@@ -1074,13 +1087,14 @@ export function registerMatrixCli(params: { program: Command }): void {
verbose?: boolean;
json?: boolean;
}) => {
const accountId = resolveMatrixCliAccountId(options.account);
const { accountId, cfg } = resolveMatrixCliAccountContext(options.account);
await runMatrixCliCommand({
verbose: options.verbose === true,
json: options.json === true,
run: async () =>
await bootstrapMatrixVerification({
accountId,
cfg,
recoveryKey: options.recoveryKey,
forceResetCrossSigning: options.forceResetCrossSigning === true,
}),
@@ -1129,11 +1143,11 @@ export function registerMatrixCli(params: { program: Command }): void {
.option("--json", "Output as JSON")
.action(
async (key: string, options: { account?: string; verbose?: boolean; json?: boolean }) => {
const accountId = resolveMatrixCliAccountId(options.account);
const { accountId, cfg } = resolveMatrixCliAccountContext(options.account);
await runMatrixCliCommand({
verbose: options.verbose === true,
json: options.json === true,
run: async () => await verifyMatrixRecoveryKey(key, { accountId }),
run: async () => await verifyMatrixRecoveryKey(key, { accountId, cfg }),
onText: (result, verbose) => {
printAccountLabel(accountId);
if (!result.success) {