diff --git a/src/cli/program/preaction.test.ts b/src/cli/program/preaction.test.ts index ec8ce0a848f..a83b77c05ce 100644 --- a/src/cli/program/preaction.test.ts +++ b/src/cli/program/preaction.test.ts @@ -72,49 +72,87 @@ afterEach(() => { }); describe("registerPreActionHooks", () => { - function buildProgram() { + type CommandKey = + | "status" + | "doctor" + | "completion" + | "secrets" + | "update-status" + | "config-set" + | "agents" + | "configure" + | "onboard" + | "message-send"; + + function buildProgram(keys: readonly CommandKey[]) { + const enabled = new Set(keys); + const has = (key: CommandKey) => enabled.has(key); const program = new Command().name("openclaw"); - program.command("status").action(() => {}); - program.command("doctor").action(() => {}); - program.command("completion").action(() => {}); - program.command("secrets").action(() => {}); - program - .command("update") - .command("status") - .option("--json") - .action(() => {}); - const config = program.command("config"); - config - .command("set") - .argument("") - .argument("") - .option("--json") - .action(() => {}); - program.command("agents").action(() => {}); - program.command("configure").action(() => {}); - program.command("onboard").action(() => {}); - program - .command("message") - .command("send") - .option("--json") - .action(() => {}); + if (has("status")) { + program.command("status").action(() => {}); + } + if (has("doctor")) { + program.command("doctor").action(() => {}); + } + if (has("completion")) { + program.command("completion").action(() => {}); + } + if (has("secrets")) { + program.command("secrets").action(() => {}); + } + if (has("update-status")) { + program + .command("update") + .command("status") + .option("--json") + .action(() => {}); + } + if (has("config-set")) { + const config = program.command("config"); + config + .command("set") + .argument("") + .argument("") + .option("--json") + .action(() => {}); + } + if (has("agents")) { + program.command("agents").action(() => {}); + } + if (has("configure")) { + program.command("configure").action(() => {}); + } + if (has("onboard")) { + program.command("onboard").action(() => {}); + } + if (has("message-send")) { + program + .command("message") + .command("send") + .option("--json") + .action(() => {}); + } registerPreActionHooks(program, "9.9.9-test"); return program; } async function runCommand( params: { parseArgv: string[]; processArgv?: string[] }, - program = buildProgram(), + program: Command, ) { process.argv = params.processArgv ?? [...params.parseArgv]; await program.parseAsync(params.parseArgv, { from: "user" }); } it("emits banner, resolves config, and enables verbose from --debug", async () => { - await runCommand({ - parseArgv: ["status"], - processArgv: ["node", "openclaw", "status", "--debug"], - }); + const program = buildProgram(["status"]); + await runCommand( + { + parseArgv: ["status"], + processArgv: ["node", "openclaw", "status", "--debug"], + }, + program, + ); expect(emitCliBannerMock).toHaveBeenCalledWith("9.9.9-test"); expect(setVerboseMock).toHaveBeenCalledWith(true); @@ -127,10 +165,14 @@ describe("registerPreActionHooks", () => { }); it("loads plugin registry for plugin-required commands", async () => { - await runCommand({ - parseArgv: ["message", "send"], - processArgv: ["node", "openclaw", "message", "send"], - }); + const program = buildProgram(["message-send"]); + await runCommand( + { + parseArgv: ["message", "send"], + processArgv: ["node", "openclaw", "message", "send"], + }, + program, + ); expect(setVerboseMock).toHaveBeenCalledWith(false); expect(process.env.NODE_NO_WARNINGS).toBe("1"); @@ -143,7 +185,7 @@ describe("registerPreActionHooks", () => { it("loads plugin registry for configure/onboard/agents commands", async () => { const commands = ["configure", "onboard", "agents"] as const; - const program = buildProgram(); + const program = buildProgram(["configure", "onboard", "agents"]); for (const command of commands) { vi.clearAllMocks(); await runCommand( @@ -158,7 +200,7 @@ describe("registerPreActionHooks", () => { }); it("skips config guard for doctor, completion, and secrets commands", async () => { - const program = buildProgram(); + const program = buildProgram(["doctor", "completion", "secrets"]); await runCommand( { parseArgv: ["doctor"], @@ -185,10 +227,14 @@ describe("registerPreActionHooks", () => { }); it("skips preaction work when argv indicates help/version", async () => { - await runCommand({ - parseArgv: ["status"], - processArgv: ["node", "openclaw", "--version"], - }); + const program = buildProgram(["status"]); + await runCommand( + { + parseArgv: ["status"], + processArgv: ["node", "openclaw", "--version"], + }, + program, + ); expect(emitCliBannerMock).not.toHaveBeenCalled(); expect(setVerboseMock).not.toHaveBeenCalled(); @@ -197,17 +243,21 @@ describe("registerPreActionHooks", () => { it("hides banner when OPENCLAW_HIDE_BANNER is truthy", async () => { process.env.OPENCLAW_HIDE_BANNER = "1"; - await runCommand({ - parseArgv: ["status"], - processArgv: ["node", "openclaw", "status"], - }); + const program = buildProgram(["status"]); + await runCommand( + { + parseArgv: ["status"], + processArgv: ["node", "openclaw", "status"], + }, + program, + ); expect(emitCliBannerMock).not.toHaveBeenCalled(); expect(ensureConfigReadyMock).toHaveBeenCalledTimes(1); }); it("suppresses doctor stdout for any --json output command", async () => { - const program = buildProgram(); + const program = buildProgram(["message-send", "update-status"]); await runCommand( { parseArgv: ["message", "send", "--json"], @@ -240,10 +290,14 @@ describe("registerPreActionHooks", () => { }); it("does not treat config set --json (strict-parse alias) as json output mode", async () => { - await runCommand({ - parseArgv: ["config", "set", "gateway.auth.mode", "{bad", "--json"], - processArgv: ["node", "openclaw", "config", "set", "gateway.auth.mode", "{bad", "--json"], - }); + const program = buildProgram(["config-set"]); + await runCommand( + { + parseArgv: ["config", "set", "gateway.auth.mode", "{bad", "--json"], + processArgv: ["node", "openclaw", "config", "set", "gateway.auth.mode", "{bad", "--json"], + }, + program, + ); expect(ensureConfigReadyMock).toHaveBeenCalledWith({ runtime: runtimeMock, diff --git a/test/scripts/ios-team-id.test.ts b/test/scripts/ios-team-id.test.ts index f6459d759da..7662e8f05a2 100644 --- a/test/scripts/ios-team-id.test.ts +++ b/test/scripts/ios-team-id.test.ts @@ -6,6 +6,9 @@ import path from "node:path"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; const SCRIPT = path.join(process.cwd(), "scripts", "ios-team-id.sh"); +const BASH_BIN = process.platform === "win32" ? "bash" : "/bin/bash"; +const BASE_PATH = process.env.PATH ?? "/usr/bin:/bin"; +const BASE_LANG = process.env.LANG ?? "C"; let fixtureRoot = ""; let sharedBinDir = ""; let caseId = 0; @@ -25,13 +28,13 @@ function runScript( } { const binDir = path.join(homeDir, "bin"); const env = { - ...process.env, HOME: homeDir, - PATH: `${binDir}:${sharedBinDir}:${process.env.PATH ?? ""}`, + PATH: `${binDir}:${sharedBinDir}:${BASE_PATH}`, + LANG: BASE_LANG, ...extraEnv, }; try { - const stdout = execFileSync("bash", [SCRIPT], { + const stdout = execFileSync(BASH_BIN, [SCRIPT], { env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], @@ -109,19 +112,20 @@ exit 1`, return { homeDir, binDir }; } - it("falls back to Xcode-managed provisioning profiles when preference teams are empty", async () => { + it("resolves fallback and preferred team IDs from provisioning profiles", async () => { const { homeDir } = await createHomeDir(); - await mkdir(path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles"), { - recursive: true, - }); - await writeFile( - path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles", "one.mobileprovision"), - "stub", - ); + const profilesDir = path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles"); + await mkdir(profilesDir, { recursive: true }); + await writeFile(path.join(profilesDir, "one.mobileprovision"), "stub1"); - const result = runScript(homeDir); - expect(result.ok).toBe(true); - expect(result.stdout).toBe("AAAAA11111"); + const fallbackResult = runScript(homeDir); + expect(fallbackResult.ok).toBe(true); + expect(fallbackResult.stdout).toBe("AAAAA11111"); + + await writeFile(path.join(profilesDir, "two.mobileprovision"), "stub2"); + const preferredResult = runScript(homeDir, { IOS_PREFERRED_TEAM_ID: "BBBBB22222" }); + expect(preferredResult.ok).toBe(true); + expect(preferredResult.stdout).toBe("BBBBB22222"); }); it("prints actionable guidance when Xcode account exists but no Team ID is resolvable", async () => { @@ -148,25 +152,6 @@ exit 1`, expect(result.stderr).toContain("IOS_DEVELOPMENT_TEAM"); }); - it("honors IOS_PREFERRED_TEAM_ID when multiple profile teams are available", async () => { - const { homeDir } = await createHomeDir(); - await mkdir(path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles"), { - recursive: true, - }); - await writeFile( - path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles", "one.mobileprovision"), - "stub1", - ); - await writeFile( - path.join(homeDir, "Library", "MobileDevice", "Provisioning Profiles", "two.mobileprovision"), - "stub2", - ); - - const result = runScript(homeDir, { IOS_PREFERRED_TEAM_ID: "BBBBB22222" }); - expect(result.ok).toBe(true); - expect(result.stdout).toBe("BBBBB22222"); - }); - it("matches preferred team IDs even when parser output uses CRLF line endings", async () => { const { homeDir, binDir } = await createHomeDir(); await writeExecutable(