test: mock channels status command seam

This commit is contained in:
Peter Steinberger
2026-04-17 07:27:17 +01:00
parent 271fc360e7
commit 4451e8479a

View File

@@ -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<unknown>) => 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<unknown>) => 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<unknown>) => withProgress(opts, run),
withProgress: (opts: unknown, run: () => Promise<unknown>) => 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();