diff --git a/src/commands/channels.status.command-flow.test.ts b/src/commands/channels.status.command-flow.test.ts index 5c9bea70862..a549c1fd432 100644 --- a/src/commands/channels.status.command-flow.test.ts +++ b/src/commands/channels.status.command-flow.test.ts @@ -4,22 +4,37 @@ import { channelsStatusCommand } from "./channels/status.js"; const resolveDefaultAccountId = () => DEFAULT_ACCOUNT_ID; -const callGateway = vi.fn(); -const resolveCommandSecretRefsViaGateway = vi.fn(); -const requireValidConfigSnapshot = vi.fn(); -const listChannelPlugins = vi.fn(); -const withProgress = vi.fn(async (_opts: unknown, run: () => Promise) => await run()); +const mocks = vi.hoisted(() => ({ + callGateway: vi.fn(), + resolveCommandConfigWithSecrets: vi.fn(), + readConfigFileSnapshot: vi.fn(async () => ({ path: "/tmp/openclaw.json" })), + requireValidConfigSnapshot: vi.fn(), + listChannelPlugins: vi.fn(), + withProgress: vi.fn(async (_opts: unknown, run: () => Promise) => await run()), +})); vi.mock("../gateway/call.js", () => ({ - callGateway: (opts: unknown) => callGateway(opts), + callGateway: (opts: unknown) => mocks.callGateway(opts), })); -vi.mock("../cli/command-secret-gateway.js", () => ({ - resolveCommandSecretRefsViaGateway: (opts: unknown) => resolveCommandSecretRefsViaGateway(opts), +vi.mock("../cli/command-config-resolution.js", () => ({ + resolveCommandConfigWithSecrets: async (opts: { + runtime?: { log: (message: string) => void }; + }) => { + const result = await mocks.resolveCommandConfigWithSecrets(opts); + for (const entry of result?.diagnostics ?? []) { + opts.runtime?.log(`[secrets] ${entry}`); + } + return result; + }, })); -vi.mock("./shared.js", () => ({ - requireValidConfigSnapshot: (runtime: unknown) => requireValidConfigSnapshot(runtime), +vi.mock("../config/config.js", () => ({ + readConfigFileSnapshot: () => mocks.readConfigFileSnapshot(), +})); + +vi.mock("./channels/shared.js", () => ({ + requireValidConfigSnapshot: (runtime: unknown) => mocks.requireValidConfigSnapshot(runtime), formatChannelAccountLabel: ({ channel, accountId, @@ -31,13 +46,13 @@ vi.mock("./shared.js", () => ({ })); vi.mock("../channels/plugins/index.js", () => ({ - listChannelPlugins: () => listChannelPlugins(), + listChannelPlugins: () => mocks.listChannelPlugins(), getChannelPlugin: (channel: string) => - (listChannelPlugins() as Array<{ id: string }>).find((plugin) => plugin.id === channel), + (mocks.listChannelPlugins() as Array<{ id: string }>).find((plugin) => plugin.id === channel), })); vi.mock("../cli/progress.js", () => ({ - withProgress: (opts: unknown, run: () => Promise) => withProgress(opts, run), + withProgress: (opts: unknown, run: () => Promise) => mocks.withProgress(opts, run), })); function createTokenOnlyPlugin() { @@ -112,31 +127,31 @@ function createRuntimeCapture() { describe("channelsStatusCommand SecretRef fallback flow", () => { beforeEach(() => { - callGateway.mockReset(); - resolveCommandSecretRefsViaGateway.mockReset(); - requireValidConfigSnapshot.mockReset(); - listChannelPlugins.mockReset(); - withProgress.mockClear(); - listChannelPlugins.mockReturnValue([createTokenOnlyPlugin()]); + mocks.callGateway.mockReset(); + mocks.resolveCommandConfigWithSecrets.mockReset(); + mocks.readConfigFileSnapshot.mockClear(); + mocks.requireValidConfigSnapshot.mockReset(); + mocks.listChannelPlugins.mockReset(); + mocks.withProgress.mockClear(); + mocks.listChannelPlugins.mockReturnValue([createTokenOnlyPlugin()]); }); it("keeps read-only fallback output when SecretRefs are unresolved", async () => { - callGateway.mockRejectedValue(new Error("gateway closed")); - requireValidConfigSnapshot.mockResolvedValue({ secretResolved: false, channels: {} }); - resolveCommandSecretRefsViaGateway.mockResolvedValue({ + mocks.callGateway.mockRejectedValue(new Error("gateway closed")); + mocks.requireValidConfigSnapshot.mockResolvedValue({ secretResolved: false, channels: {} }); + mocks.resolveCommandConfigWithSecrets.mockResolvedValue({ resolvedConfig: { secretResolved: false, channels: {} }, + effectiveConfig: { secretResolved: false, channels: {} }, diagnostics: [ "channels status: channels.discord.token is unavailable in this command path; continuing with degraded read-only config.", ], - targetStatesByPath: {}, - hadUnresolvedTargets: true, }); const { runtime, logs, errors } = createRuntimeCapture(); await channelsStatusCommand({ probe: false }, runtime as never); expect(errors.some((line) => line.includes("Gateway not reachable"))).toBe(true); - expect(resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith( + expect(mocks.resolveCommandConfigWithSecrets).toHaveBeenCalledWith( expect.objectContaining({ commandName: "channels status", mode: "read_only_status", @@ -153,13 +168,12 @@ describe("channelsStatusCommand SecretRef fallback flow", () => { }); it("prefers resolved snapshots when command-local SecretRef resolution succeeds", async () => { - callGateway.mockRejectedValue(new Error("gateway closed")); - requireValidConfigSnapshot.mockResolvedValue({ secretResolved: false, channels: {} }); - resolveCommandSecretRefsViaGateway.mockResolvedValue({ + mocks.callGateway.mockRejectedValue(new Error("gateway closed")); + mocks.requireValidConfigSnapshot.mockResolvedValue({ secretResolved: false, channels: {} }); + mocks.resolveCommandConfigWithSecrets.mockResolvedValue({ resolvedConfig: { secretResolved: true, channels: {} }, + effectiveConfig: { secretResolved: true, channels: {} }, diagnostics: [], - targetStatesByPath: {}, - hadUnresolvedTargets: false, }); const { runtime, logs } = createRuntimeCapture();