diff --git a/src/cli/acp-cli.option-collisions.test.ts b/src/cli/acp-cli.option-collisions.test.ts index 851e521e3..3a48e7ab8 100644 --- a/src/cli/acp-cli.option-collisions.test.ts +++ b/src/cli/acp-cli.option-collisions.test.ts @@ -28,6 +28,27 @@ vi.mock("../runtime.js", () => ({ describe("acp cli option collisions", () => { let registerAcpCli: typeof import("./acp-cli.js").registerAcpCli; + async function withSecretFiles( + secrets: { token?: string; password?: string }, + run: (files: { tokenFile?: string; passwordFile?: string }) => Promise, + ): Promise { + const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-acp-cli-")); + try { + const files: { tokenFile?: string; passwordFile?: string } = {}; + if (secrets.token !== undefined) { + files.tokenFile = path.join(dir, "token.txt"); + await fs.writeFile(files.tokenFile, secrets.token, "utf8"); + } + if (secrets.password !== undefined) { + files.passwordFile = path.join(dir, "password.txt"); + await fs.writeFile(files.passwordFile, secrets.password, "utf8"); + } + return await run(files); + } finally { + await fs.rm(dir, { recursive: true, force: true }); + } + } + beforeAll(async () => { ({ registerAcpCli } = await import("./acp-cli.js")); }); @@ -57,14 +78,13 @@ describe("acp cli option collisions", () => { const program = new Command(); registerAcpCli(program); - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-acp-cli-")); - const tokenFile = path.join(dir, "token.txt"); - const passwordFile = path.join(dir, "password.txt"); - await fs.writeFile(tokenFile, "tok_file\n", "utf8"); - await fs.writeFile(passwordFile, "pw_file\n", "utf8"); - - await program.parseAsync(["acp", "--token-file", tokenFile, "--password-file", passwordFile], { - from: "user", + await withSecretFiles({ token: "tok_file\n", password: "pw_file\n" }, async (files) => { + await program.parseAsync( + ["acp", "--token-file", files.tokenFile ?? "", "--password-file", files.passwordFile ?? ""], + { + from: "user", + }, + ); }); expect(serveAcpGateway).toHaveBeenCalledWith( @@ -80,12 +100,13 @@ describe("acp cli option collisions", () => { const program = new Command(); registerAcpCli(program); - const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-acp-cli-")); - const tokenFile = path.join(dir, "token.txt"); - await fs.writeFile(tokenFile, "tok_file\n", "utf8"); - - await program.parseAsync(["acp", "--token", "tok_inline", "--token-file", tokenFile], { - from: "user", + await withSecretFiles({ token: "tok_file\n" }, async (files) => { + await program.parseAsync( + ["acp", "--token", "tok_inline", "--token-file", files.tokenFile ?? ""], + { + from: "user", + }, + ); }); expect(serveAcpGateway).not.toHaveBeenCalled(); @@ -95,6 +116,27 @@ describe("acp cli option collisions", () => { expect(defaultRuntime.exit).toHaveBeenCalledWith(1); }); + it("rejects mixed password flags and file flags", async () => { + const { registerAcpCli } = await import("./acp-cli.js"); + const program = new Command(); + registerAcpCli(program); + + await withSecretFiles({ password: "pw_file\n" }, async (files) => { + await program.parseAsync( + ["acp", "--password", "pw_inline", "--password-file", files.passwordFile ?? ""], + { + from: "user", + }, + ); + }); + + expect(serveAcpGateway).not.toHaveBeenCalled(); + expect(defaultRuntime.error).toHaveBeenCalledWith( + expect.stringMatching(/Use either --password or --password-file/), + ); + expect(defaultRuntime.exit).toHaveBeenCalledWith(1); + }); + it("warns when inline secret flags are used", async () => { const { registerAcpCli } = await import("./acp-cli.js"); const program = new Command();