From dfbf51791bd283f546630c6084cd3b66461ecb6f Mon Sep 17 00:00:00 2001 From: luyao618 Date: Fri, 24 Apr 2026 20:51:51 +0800 Subject: [PATCH] fix(matrix): pass loaded cfg to verify CLI subcommands (#70992) The verify CLI subcommands (status, backup status, backup reset, backup restore, bootstrap, device ) 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 --- extensions/matrix/src/cli.test.ts | 100 +++++++++++++++++++++++++++--- extensions/matrix/src/cli.ts | 32 +++++++--- 2 files changed, 116 insertions(+), 16 deletions(-) diff --git a/extensions/matrix/src/cli.test.ts b/extensions/matrix/src/cli.test.ts index ac9b5a3db2c..b2d30804630 100644 --- a/extensions/matrix/src/cli.test.ts +++ b/extensions/matrix/src/cli.test.ts @@ -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"); diff --git a/extensions/matrix/src/cli.ts b/extensions/matrix/src/cli.ts index 497520951d7..a5455c14d3e 100644 --- a/extensions/matrix/src/cli.ts +++ b/extensions/matrix/src/cli.ts @@ -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) {