mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-02 08:10:22 +00:00
test: share cli and channel setup fixtures
This commit is contained in:
@@ -1,20 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { registerBrowserManageCommands } from "./browser-cli-manage.js";
|
||||
import { createBrowserProgram } from "./browser-cli-test-helpers.js";
|
||||
import type { CliRuntimeCapture } from "./test-runtime-capture.js";
|
||||
|
||||
const runtimeState = vi.hoisted(() => ({ capture: null as CliRuntimeCapture | null }));
|
||||
|
||||
function getRuntimeCapture(): CliRuntimeCapture {
|
||||
if (!runtimeState.capture) {
|
||||
throw new Error("runtime capture not initialized");
|
||||
}
|
||||
return runtimeState.capture;
|
||||
}
|
||||
|
||||
function getRuntime() {
|
||||
return getRuntimeCapture().defaultRuntime;
|
||||
}
|
||||
import {
|
||||
createBrowserProgram,
|
||||
getBrowserCliRuntime,
|
||||
getBrowserCliRuntimeCapture,
|
||||
} from "./browser-cli-test-helpers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
@@ -32,19 +22,15 @@ vi.mock("./browser-cli-shared.js", () => ({
|
||||
callBrowserRequest: mocks.callBrowserRequest,
|
||||
}));
|
||||
|
||||
vi.mock("./cli-utils.js", () => ({
|
||||
runCommandWithRuntime: async (
|
||||
_runtime: unknown,
|
||||
action: () => Promise<void>,
|
||||
onError: (err: unknown) => void,
|
||||
) => await action().catch(onError),
|
||||
vi.mock("./cli-utils.js", async () => ({
|
||||
...(await (await import("./browser-cli-test-helpers.js")).createBrowserCliUtilsMockModule()),
|
||||
}));
|
||||
|
||||
vi.mock("../runtime.js", async () => {
|
||||
const { createCliRuntimeCapture } = await import("./test-runtime-capture.js");
|
||||
runtimeState.capture ??= createCliRuntimeCapture();
|
||||
return { defaultRuntime: runtimeState.capture.defaultRuntime };
|
||||
});
|
||||
vi.mock(
|
||||
"../runtime.js",
|
||||
async () =>
|
||||
await (await import("./browser-cli-test-helpers.js")).createBrowserCliRuntimeMockModule(),
|
||||
);
|
||||
|
||||
function createProgram() {
|
||||
const { program, browser, parentOpts } = createBrowserProgram();
|
||||
@@ -55,7 +41,7 @@ function createProgram() {
|
||||
describe("browser manage output", () => {
|
||||
beforeEach(() => {
|
||||
mocks.callBrowserRequest.mockClear();
|
||||
getRuntimeCapture().resetRuntimeCapture();
|
||||
getBrowserCliRuntimeCapture().resetRuntimeCapture();
|
||||
});
|
||||
|
||||
it("shows chrome-mcp transport for existing-session status without fake CDP fields", async () => {
|
||||
@@ -88,7 +74,7 @@ describe("browser manage output", () => {
|
||||
from: "user",
|
||||
});
|
||||
|
||||
const output = getRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
expect(output).toContain("transport: chrome-mcp");
|
||||
expect(output).not.toContain("cdpPort:");
|
||||
expect(output).not.toContain("cdpUrl:");
|
||||
@@ -124,7 +110,7 @@ describe("browser manage output", () => {
|
||||
from: "user",
|
||||
});
|
||||
|
||||
const output = getRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
expect(output).toContain(
|
||||
"userDataDir: /Users/test/Library/Application Support/BraveSoftware/Brave-Browser",
|
||||
);
|
||||
@@ -155,7 +141,7 @@ describe("browser manage output", () => {
|
||||
const program = createProgram();
|
||||
await program.parseAsync(["browser", "profiles"], { from: "user" });
|
||||
|
||||
const output = getRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
expect(output).toContain("chrome-live: running (2 tabs) [existing-session]");
|
||||
expect(output).toContain("transport: chrome-mcp");
|
||||
expect(output).not.toContain("port: 0");
|
||||
@@ -183,7 +169,7 @@ describe("browser manage output", () => {
|
||||
{ from: "user" },
|
||||
);
|
||||
|
||||
const output = getRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
expect(output).toContain('Created profile "chrome-live"');
|
||||
expect(output).toContain("transport: chrome-mcp");
|
||||
expect(output).not.toContain("port: 0");
|
||||
@@ -220,7 +206,7 @@ describe("browser manage output", () => {
|
||||
from: "user",
|
||||
});
|
||||
|
||||
const output = getRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
const output = getBrowserCliRuntime().log.mock.calls.at(-1)?.[0] as string;
|
||||
expect(output).toContain("cdpUrl: https://example.com/chrome?token=supers…7890");
|
||||
expect(output).not.toContain("alice");
|
||||
expect(output).not.toContain("supersecretpasswordvalue1234");
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { registerBrowserManageCommands } from "./browser-cli-manage.js";
|
||||
import { createBrowserProgram } from "./browser-cli-test-helpers.js";
|
||||
import type { CliRuntimeCapture } from "./test-runtime-capture.js";
|
||||
|
||||
const runtimeState = vi.hoisted(() => ({ capture: null as CliRuntimeCapture | null }));
|
||||
|
||||
function getRuntimeCapture(): CliRuntimeCapture {
|
||||
if (!runtimeState.capture) {
|
||||
throw new Error("runtime capture not initialized");
|
||||
}
|
||||
return runtimeState.capture;
|
||||
}
|
||||
import { createBrowserProgram, getBrowserCliRuntimeCapture } from "./browser-cli-test-helpers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => {
|
||||
return {
|
||||
@@ -36,19 +26,15 @@ vi.mock("./browser-cli-shared.js", () => ({
|
||||
callBrowserRequest: mocks.callBrowserRequest,
|
||||
}));
|
||||
|
||||
vi.mock("./cli-utils.js", () => ({
|
||||
runCommandWithRuntime: async (
|
||||
_runtime: unknown,
|
||||
action: () => Promise<void>,
|
||||
onError: (err: unknown) => void,
|
||||
) => await action().catch(onError),
|
||||
vi.mock("./cli-utils.js", async () => ({
|
||||
...(await (await import("./browser-cli-test-helpers.js")).createBrowserCliUtilsMockModule()),
|
||||
}));
|
||||
|
||||
vi.mock("../runtime.js", async () => {
|
||||
const { createCliRuntimeCapture } = await import("./test-runtime-capture.js");
|
||||
runtimeState.capture ??= createCliRuntimeCapture();
|
||||
return { defaultRuntime: runtimeState.capture.defaultRuntime };
|
||||
});
|
||||
vi.mock(
|
||||
"../runtime.js",
|
||||
async () =>
|
||||
await (await import("./browser-cli-test-helpers.js")).createBrowserCliRuntimeMockModule(),
|
||||
);
|
||||
|
||||
describe("browser manage start timeout option", () => {
|
||||
function createProgram() {
|
||||
@@ -60,7 +46,7 @@ describe("browser manage start timeout option", () => {
|
||||
|
||||
beforeEach(() => {
|
||||
mocks.callBrowserRequest.mockClear();
|
||||
getRuntimeCapture().resetRuntimeCapture();
|
||||
getBrowserCliRuntimeCapture().resetRuntimeCapture();
|
||||
});
|
||||
|
||||
it("uses parent --timeout for browser start instead of hardcoded 15s", async () => {
|
||||
|
||||
@@ -1,20 +1,10 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { registerBrowserStateCommands } from "./browser-cli-state.js";
|
||||
import { createBrowserProgram as createBrowserProgramShared } from "./browser-cli-test-helpers.js";
|
||||
import type { CliRuntimeCapture } from "./test-runtime-capture.js";
|
||||
|
||||
const runtimeState = vi.hoisted(() => ({ capture: null as CliRuntimeCapture | null }));
|
||||
|
||||
function getRuntimeCapture(): CliRuntimeCapture {
|
||||
if (!runtimeState.capture) {
|
||||
throw new Error("runtime capture not initialized");
|
||||
}
|
||||
return runtimeState.capture;
|
||||
}
|
||||
|
||||
function getRuntime() {
|
||||
return getRuntimeCapture().defaultRuntime;
|
||||
}
|
||||
import {
|
||||
createBrowserProgram as createBrowserProgramShared,
|
||||
getBrowserCliRuntime,
|
||||
getBrowserCliRuntimeCapture,
|
||||
} from "./browser-cli-test-helpers.js";
|
||||
|
||||
const mocks = vi.hoisted(() => ({
|
||||
callBrowserRequest: vi.fn(async (..._args: unknown[]) => ({ ok: true })),
|
||||
@@ -29,11 +19,11 @@ vi.mock("./browser-cli-resize.js", () => ({
|
||||
runBrowserResizeWithOutput: mocks.runBrowserResizeWithOutput,
|
||||
}));
|
||||
|
||||
vi.mock("../runtime.js", async () => {
|
||||
const { createCliRuntimeCapture } = await import("./test-runtime-capture.js");
|
||||
runtimeState.capture ??= createCliRuntimeCapture();
|
||||
return { defaultRuntime: runtimeState.capture.defaultRuntime };
|
||||
});
|
||||
vi.mock(
|
||||
"../runtime.js",
|
||||
async () =>
|
||||
await (await import("./browser-cli-test-helpers.js")).createBrowserCliRuntimeMockModule(),
|
||||
);
|
||||
|
||||
describe("browser state option collisions", () => {
|
||||
const createStateProgram = ({ withGatewayUrl = false } = {}) => {
|
||||
@@ -64,8 +54,8 @@ describe("browser state option collisions", () => {
|
||||
beforeEach(() => {
|
||||
mocks.callBrowserRequest.mockClear();
|
||||
mocks.runBrowserResizeWithOutput.mockClear();
|
||||
getRuntimeCapture().resetRuntimeCapture();
|
||||
getRuntime().exit.mockImplementation(() => {});
|
||||
getBrowserCliRuntimeCapture().resetRuntimeCapture();
|
||||
getBrowserCliRuntime().exit.mockImplementation(() => {});
|
||||
});
|
||||
|
||||
it("forwards parent-captured --target-id on `browser cookies set`", async () => {
|
||||
@@ -145,37 +135,39 @@ describe("browser state option collisions", () => {
|
||||
await runBrowserCommand(["set", "offline", "maybe"]);
|
||||
|
||||
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
|
||||
expect(getRuntime().error).toHaveBeenCalledWith(expect.stringContaining("Expected on|off"));
|
||||
expect(getRuntime().exit).toHaveBeenCalledWith(1);
|
||||
expect(getBrowserCliRuntime().error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Expected on|off"),
|
||||
);
|
||||
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("errors when set media receives an invalid value", async () => {
|
||||
await runBrowserCommand(["set", "media", "sepia"]);
|
||||
|
||||
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
|
||||
expect(getRuntime().error).toHaveBeenCalledWith(
|
||||
expect(getBrowserCliRuntime().error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Expected dark|light|none"),
|
||||
);
|
||||
expect(getRuntime().exit).toHaveBeenCalledWith(1);
|
||||
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("errors when headers JSON is missing", async () => {
|
||||
await runBrowserCommand(["set", "headers"]);
|
||||
|
||||
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
|
||||
expect(getRuntime().error).toHaveBeenCalledWith(
|
||||
expect(getBrowserCliRuntime().error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Missing headers JSON"),
|
||||
);
|
||||
expect(getRuntime().exit).toHaveBeenCalledWith(1);
|
||||
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
|
||||
it("errors when headers JSON is not an object", async () => {
|
||||
await runBrowserCommand(["set", "headers", "--json", "[]"]);
|
||||
|
||||
expect(mocks.callBrowserRequest).not.toHaveBeenCalled();
|
||||
expect(getRuntime().error).toHaveBeenCalledWith(
|
||||
expect(getBrowserCliRuntime().error).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Headers JSON must be a JSON object"),
|
||||
);
|
||||
expect(getRuntime().exit).toHaveBeenCalledWith(1);
|
||||
expect(getBrowserCliRuntime().exit).toHaveBeenCalledWith(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { Command } from "commander";
|
||||
import type { BrowserParentOpts } from "./browser-cli-shared.js";
|
||||
import { createCliRuntimeCapture } from "./test-runtime-capture.js";
|
||||
import type { CliRuntimeCapture } from "./test-runtime-capture.js";
|
||||
|
||||
export function createBrowserProgram(params?: { withGatewayUrl?: boolean }): {
|
||||
program: Command;
|
||||
@@ -17,3 +19,37 @@ export function createBrowserProgram(params?: { withGatewayUrl?: boolean }): {
|
||||
const parentOpts = (cmd: Command) => cmd.parent?.opts?.() as BrowserParentOpts;
|
||||
return { program, browser, parentOpts };
|
||||
}
|
||||
|
||||
const browserCliRuntimeState = { capture: null as CliRuntimeCapture | null };
|
||||
|
||||
export function getBrowserCliRuntimeCapture(): CliRuntimeCapture {
|
||||
if (!browserCliRuntimeState.capture) {
|
||||
throw new Error("runtime capture not initialized");
|
||||
}
|
||||
return browserCliRuntimeState.capture;
|
||||
}
|
||||
|
||||
export function getBrowserCliRuntime() {
|
||||
return getBrowserCliRuntimeCapture().defaultRuntime;
|
||||
}
|
||||
|
||||
export async function mockBrowserCliDefaultRuntime() {
|
||||
browserCliRuntimeState.capture ??= createCliRuntimeCapture();
|
||||
return { defaultRuntime: browserCliRuntimeState.capture.defaultRuntime };
|
||||
}
|
||||
|
||||
export async function runCommandWithRuntimeMock(
|
||||
_runtime: unknown,
|
||||
action: () => Promise<void>,
|
||||
onError: (err: unknown) => void,
|
||||
) {
|
||||
return await action().catch(onError);
|
||||
}
|
||||
|
||||
export async function createBrowserCliUtilsMockModule() {
|
||||
return { runCommandWithRuntime: runCommandWithRuntimeMock };
|
||||
}
|
||||
|
||||
export async function createBrowserCliRuntimeMockModule() {
|
||||
return await mockBrowserCliDefaultRuntime();
|
||||
}
|
||||
|
||||
@@ -45,6 +45,78 @@ vi.mock("@clack/prompts", () => ({
|
||||
|
||||
const { registerSecretsCli } = await import("./secrets-cli.js");
|
||||
|
||||
function createManualSecretsPlan() {
|
||||
return {
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: "2026-02-26T00:00:00.000Z",
|
||||
generatedBy: "manual",
|
||||
targets: [],
|
||||
};
|
||||
}
|
||||
|
||||
function createConfigureInteractiveResult(options?: {
|
||||
targets?: unknown[];
|
||||
changed?: boolean;
|
||||
resolvabilityComplete?: boolean;
|
||||
}) {
|
||||
return {
|
||||
plan: {
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: "2026-02-26T00:00:00.000Z",
|
||||
generatedBy: "openclaw secrets configure",
|
||||
targets: options?.targets ?? [],
|
||||
},
|
||||
preflight: {
|
||||
mode: "dry-run" as const,
|
||||
changed: options?.changed ?? false,
|
||||
changedFiles: options?.changed ? ["/tmp/openclaw.json"] : [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: options?.resolvabilityComplete ?? true,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function createSecretsApplyResult(options?: {
|
||||
mode?: "dry-run" | "write";
|
||||
changed?: boolean;
|
||||
resolvabilityComplete?: boolean;
|
||||
}) {
|
||||
return {
|
||||
mode: options?.mode ?? "dry-run",
|
||||
changed: options?.changed ?? false,
|
||||
changedFiles: options?.changed ? ["/tmp/openclaw.json"] : [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: options?.resolvabilityComplete ?? true,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
};
|
||||
}
|
||||
|
||||
async function withPlanFile(run: (planPath: string) => Promise<void>) {
|
||||
const planPath = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-secrets-cli-test-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
|
||||
);
|
||||
await fs.writeFile(planPath, `${JSON.stringify(createManualSecretsPlan())}\n`, "utf8");
|
||||
try {
|
||||
await run(planPath);
|
||||
} finally {
|
||||
await fs.rm(planPath, { force: true });
|
||||
}
|
||||
}
|
||||
|
||||
describe("secrets CLI", () => {
|
||||
const createProgram = () => {
|
||||
const program = new Command();
|
||||
@@ -142,12 +214,9 @@ describe("secrets CLI", () => {
|
||||
});
|
||||
|
||||
it("runs secrets configure then apply when confirmed", async () => {
|
||||
runSecretsConfigureInteractive.mockResolvedValue({
|
||||
plan: {
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: "2026-02-26T00:00:00.000Z",
|
||||
generatedBy: "openclaw secrets configure",
|
||||
runSecretsConfigureInteractive.mockResolvedValue(
|
||||
createConfigureInteractiveResult({
|
||||
changed: true,
|
||||
targets: [
|
||||
{
|
||||
type: "skills.entries.apiKey",
|
||||
@@ -160,35 +229,10 @@ describe("secrets CLI", () => {
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
preflight: {
|
||||
mode: "dry-run",
|
||||
changed: true,
|
||||
changedFiles: ["/tmp/openclaw.json"],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: true,
|
||||
},
|
||||
refsChecked: 1,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
},
|
||||
});
|
||||
}),
|
||||
);
|
||||
confirm.mockResolvedValue(true);
|
||||
runSecretsApply.mockResolvedValue({
|
||||
mode: "write",
|
||||
changed: true,
|
||||
changedFiles: ["/tmp/openclaw.json"],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: true,
|
||||
},
|
||||
refsChecked: 1,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
});
|
||||
runSecretsApply.mockResolvedValue(createSecretsApplyResult({ mode: "write", changed: true }));
|
||||
|
||||
await createProgram().parseAsync(["secrets", "configure"], { from: "user" });
|
||||
expect(runSecretsConfigureInteractive).toHaveBeenCalled();
|
||||
@@ -209,28 +253,7 @@ describe("secrets CLI", () => {
|
||||
});
|
||||
|
||||
it("forwards --agent to secrets configure", async () => {
|
||||
runSecretsConfigureInteractive.mockResolvedValue({
|
||||
plan: {
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: "2026-02-26T00:00:00.000Z",
|
||||
generatedBy: "openclaw secrets configure",
|
||||
targets: [],
|
||||
},
|
||||
preflight: {
|
||||
mode: "dry-run",
|
||||
changed: false,
|
||||
changedFiles: [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: true,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
},
|
||||
});
|
||||
runSecretsConfigureInteractive.mockResolvedValue(createConfigureInteractiveResult());
|
||||
confirm.mockResolvedValue(false);
|
||||
|
||||
await createProgram().parseAsync(["secrets", "configure", "--agent", "ops"], { from: "user" });
|
||||
@@ -243,154 +266,57 @@ describe("secrets CLI", () => {
|
||||
});
|
||||
|
||||
it("forwards --allow-exec to secrets apply dry-run", async () => {
|
||||
const planPath = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-secrets-cli-test-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
|
||||
);
|
||||
await fs.writeFile(
|
||||
planPath,
|
||||
`${JSON.stringify({
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
generatedBy: "manual",
|
||||
targets: [],
|
||||
})}\n`,
|
||||
"utf8",
|
||||
);
|
||||
runSecretsApply.mockResolvedValue({
|
||||
mode: "dry-run",
|
||||
changed: false,
|
||||
changedFiles: [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: true,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
});
|
||||
await withPlanFile(async (planPath) => {
|
||||
runSecretsApply.mockResolvedValue(createSecretsApplyResult());
|
||||
|
||||
await createProgram().parseAsync(
|
||||
["secrets", "apply", "--from", planPath, "--dry-run", "--allow-exec"],
|
||||
{
|
||||
from: "user",
|
||||
},
|
||||
);
|
||||
expect(runSecretsApply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
write: false,
|
||||
allowExec: true,
|
||||
}),
|
||||
);
|
||||
await fs.rm(planPath, { force: true });
|
||||
await createProgram().parseAsync(
|
||||
["secrets", "apply", "--from", planPath, "--dry-run", "--allow-exec"],
|
||||
{
|
||||
from: "user",
|
||||
},
|
||||
);
|
||||
expect(runSecretsApply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
write: false,
|
||||
allowExec: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("forwards --allow-exec to secrets apply write mode", async () => {
|
||||
const planPath = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-secrets-cli-test-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
|
||||
);
|
||||
await fs.writeFile(
|
||||
planPath,
|
||||
`${JSON.stringify({
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
generatedBy: "manual",
|
||||
targets: [],
|
||||
})}\n`,
|
||||
"utf8",
|
||||
);
|
||||
runSecretsApply.mockResolvedValue({
|
||||
mode: "write",
|
||||
changed: false,
|
||||
changedFiles: [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: true,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
});
|
||||
await withPlanFile(async (planPath) => {
|
||||
runSecretsApply.mockResolvedValue(createSecretsApplyResult({ mode: "write" }));
|
||||
|
||||
await createProgram().parseAsync(["secrets", "apply", "--from", planPath, "--allow-exec"], {
|
||||
from: "user",
|
||||
await createProgram().parseAsync(["secrets", "apply", "--from", planPath, "--allow-exec"], {
|
||||
from: "user",
|
||||
});
|
||||
expect(runSecretsApply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
write: true,
|
||||
allowExec: true,
|
||||
}),
|
||||
);
|
||||
});
|
||||
expect(runSecretsApply).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
write: true,
|
||||
allowExec: true,
|
||||
}),
|
||||
);
|
||||
await fs.rm(planPath, { force: true });
|
||||
});
|
||||
|
||||
it("does not print skipped-exec note when apply dry-run skippedExecRefs is zero", async () => {
|
||||
const planPath = path.join(
|
||||
os.tmpdir(),
|
||||
`openclaw-secrets-cli-test-${Date.now()}-${Math.random().toString(16).slice(2)}.json`,
|
||||
);
|
||||
await fs.writeFile(
|
||||
planPath,
|
||||
`${JSON.stringify({
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: new Date().toISOString(),
|
||||
generatedBy: "manual",
|
||||
targets: [],
|
||||
})}\n`,
|
||||
"utf8",
|
||||
);
|
||||
runSecretsApply.mockResolvedValue({
|
||||
mode: "dry-run",
|
||||
changed: false,
|
||||
changedFiles: [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: false,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
});
|
||||
await withPlanFile(async (planPath) => {
|
||||
runSecretsApply.mockResolvedValue(createSecretsApplyResult({ resolvabilityComplete: false }));
|
||||
|
||||
await createProgram().parseAsync(["secrets", "apply", "--from", planPath, "--dry-run"], {
|
||||
from: "user",
|
||||
await createProgram().parseAsync(["secrets", "apply", "--from", planPath, "--dry-run"], {
|
||||
from: "user",
|
||||
});
|
||||
expect(runtimeLogs.some((line) => line.includes("Secrets apply dry-run note: skipped"))).toBe(
|
||||
false,
|
||||
);
|
||||
});
|
||||
expect(runtimeLogs.some((line) => line.includes("Secrets apply dry-run note: skipped"))).toBe(
|
||||
false,
|
||||
);
|
||||
await fs.rm(planPath, { force: true });
|
||||
});
|
||||
|
||||
it("does not print skipped-exec note when configure preflight skippedExecRefs is zero", async () => {
|
||||
runSecretsConfigureInteractive.mockResolvedValue({
|
||||
plan: {
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: "2026-02-26T00:00:00.000Z",
|
||||
generatedBy: "openclaw secrets configure",
|
||||
targets: [],
|
||||
},
|
||||
preflight: {
|
||||
mode: "dry-run",
|
||||
changed: false,
|
||||
changedFiles: [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: false,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
},
|
||||
});
|
||||
runSecretsConfigureInteractive.mockResolvedValue(
|
||||
createConfigureInteractiveResult({ resolvabilityComplete: false }),
|
||||
);
|
||||
confirm.mockResolvedValue(false);
|
||||
|
||||
await createProgram().parseAsync(["secrets", "configure"], { from: "user" });
|
||||
@@ -398,41 +324,8 @@ describe("secrets CLI", () => {
|
||||
});
|
||||
|
||||
it("forwards --allow-exec to configure preflight and apply", async () => {
|
||||
runSecretsConfigureInteractive.mockResolvedValue({
|
||||
plan: {
|
||||
version: 1,
|
||||
protocolVersion: 1,
|
||||
generatedAt: "2026-02-26T00:00:00.000Z",
|
||||
generatedBy: "openclaw secrets configure",
|
||||
targets: [],
|
||||
},
|
||||
preflight: {
|
||||
mode: "dry-run",
|
||||
changed: false,
|
||||
changedFiles: [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: true,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
},
|
||||
});
|
||||
runSecretsApply.mockResolvedValue({
|
||||
mode: "write",
|
||||
changed: false,
|
||||
changedFiles: [],
|
||||
checks: {
|
||||
resolvability: true,
|
||||
resolvabilityComplete: true,
|
||||
},
|
||||
refsChecked: 0,
|
||||
skippedExecRefs: 0,
|
||||
warningCount: 0,
|
||||
warnings: [],
|
||||
});
|
||||
runSecretsConfigureInteractive.mockResolvedValue(createConfigureInteractiveResult());
|
||||
runSecretsApply.mockResolvedValue(createSecretsApplyResult({ mode: "write" }));
|
||||
|
||||
await createProgram().parseAsync(["secrets", "configure", "--apply", "--yes", "--allow-exec"], {
|
||||
from: "user",
|
||||
|
||||
@@ -45,6 +45,22 @@ function createProgram() {
|
||||
return program;
|
||||
}
|
||||
|
||||
function primeDeepAuditConfig(sourceConfig = { gateway: { mode: "local" } }) {
|
||||
loadConfig.mockReturnValue(sourceConfig);
|
||||
resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
||||
resolvedConfig: sourceConfig,
|
||||
diagnostics: [],
|
||||
targetStatesByPath: {},
|
||||
hadUnresolvedTargets: false,
|
||||
});
|
||||
runSecurityAudit.mockResolvedValue({
|
||||
ts: 0,
|
||||
summary: { critical: 0, warn: 0, info: 0 },
|
||||
findings: [],
|
||||
});
|
||||
return sourceConfig;
|
||||
}
|
||||
|
||||
describe("security CLI", () => {
|
||||
beforeEach(() => {
|
||||
resetRuntimeCapture();
|
||||
@@ -131,27 +147,31 @@ describe("security CLI", () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it("forwards --token to deep probe auth without altering command-level resolver mode", async () => {
|
||||
const sourceConfig = { gateway: { mode: "local" } };
|
||||
loadConfig.mockReturnValue(sourceConfig);
|
||||
resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
||||
resolvedConfig: sourceConfig,
|
||||
diagnostics: [],
|
||||
targetStatesByPath: {},
|
||||
hadUnresolvedTargets: false,
|
||||
});
|
||||
runSecurityAudit.mockResolvedValue({
|
||||
ts: 0,
|
||||
summary: { critical: 0, warn: 0, info: 0 },
|
||||
findings: [],
|
||||
});
|
||||
|
||||
await createProgram().parseAsync(
|
||||
["security", "audit", "--deep", "--token", "explicit-token", "--json"],
|
||||
{
|
||||
from: "user",
|
||||
it.each([
|
||||
{
|
||||
title: "forwards --token to deep probe auth without altering command-level resolver mode",
|
||||
argv: ["--token", "explicit-token"],
|
||||
deepProbeAuth: { token: "explicit-token" },
|
||||
},
|
||||
{
|
||||
title: "forwards --password to deep probe auth without altering command-level resolver mode",
|
||||
argv: ["--password", "explicit-password"],
|
||||
deepProbeAuth: { password: "explicit-password" },
|
||||
},
|
||||
{
|
||||
title: "forwards both --token and --password to deep probe auth",
|
||||
argv: ["--token", "explicit-token", "--password", "explicit-password"],
|
||||
deepProbeAuth: {
|
||||
token: "explicit-token",
|
||||
password: "explicit-password",
|
||||
},
|
||||
);
|
||||
},
|
||||
])("$title", async ({ argv, deepProbeAuth }) => {
|
||||
primeDeepAuditConfig();
|
||||
|
||||
await createProgram().parseAsync(["security", "audit", "--deep", ...argv, "--json"], {
|
||||
from: "user",
|
||||
});
|
||||
|
||||
expect(resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
@@ -161,84 +181,7 @@ describe("security CLI", () => {
|
||||
expect(runSecurityAudit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
deep: true,
|
||||
deepProbeAuth: { token: "explicit-token" },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards --password to deep probe auth without altering command-level resolver mode", async () => {
|
||||
const sourceConfig = { gateway: { mode: "local" } };
|
||||
loadConfig.mockReturnValue(sourceConfig);
|
||||
resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
||||
resolvedConfig: sourceConfig,
|
||||
diagnostics: [],
|
||||
targetStatesByPath: {},
|
||||
hadUnresolvedTargets: false,
|
||||
});
|
||||
runSecurityAudit.mockResolvedValue({
|
||||
ts: 0,
|
||||
summary: { critical: 0, warn: 0, info: 0 },
|
||||
findings: [],
|
||||
});
|
||||
|
||||
await createProgram().parseAsync(
|
||||
["security", "audit", "--deep", "--password", "explicit-password", "--json"],
|
||||
{
|
||||
from: "user",
|
||||
},
|
||||
);
|
||||
|
||||
expect(resolveCommandSecretRefsViaGateway).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
mode: "read_only_status",
|
||||
}),
|
||||
);
|
||||
expect(runSecurityAudit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
deep: true,
|
||||
deepProbeAuth: { password: "explicit-password" },
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("forwards both --token and --password to deep probe auth", async () => {
|
||||
const sourceConfig = { gateway: { mode: "local" } };
|
||||
loadConfig.mockReturnValue(sourceConfig);
|
||||
resolveCommandSecretRefsViaGateway.mockResolvedValue({
|
||||
resolvedConfig: sourceConfig,
|
||||
diagnostics: [],
|
||||
targetStatesByPath: {},
|
||||
hadUnresolvedTargets: false,
|
||||
});
|
||||
runSecurityAudit.mockResolvedValue({
|
||||
ts: 0,
|
||||
summary: { critical: 0, warn: 0, info: 0 },
|
||||
findings: [],
|
||||
});
|
||||
|
||||
await createProgram().parseAsync(
|
||||
[
|
||||
"security",
|
||||
"audit",
|
||||
"--deep",
|
||||
"--token",
|
||||
"explicit-token",
|
||||
"--password",
|
||||
"explicit-password",
|
||||
"--json",
|
||||
],
|
||||
{
|
||||
from: "user",
|
||||
},
|
||||
);
|
||||
|
||||
expect(runSecurityAudit).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
deep: true,
|
||||
deepProbeAuth: {
|
||||
token: "explicit-token",
|
||||
password: "explicit-password",
|
||||
},
|
||||
deepProbeAuth,
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user