diff --git a/src/cli/daemon-cli.coverage.test.ts b/src/cli/daemon-cli.coverage.test.ts index ca6fa109b..2813d486b 100644 --- a/src/cli/daemon-cli.coverage.test.ts +++ b/src/cli/daemon-cli.coverage.test.ts @@ -123,7 +123,7 @@ describe("daemon-cli coverage", () => { expect(callGateway).toHaveBeenCalledWith(expect.objectContaining({ method: "status" })); expect(findExtraGatewayServices).toHaveBeenCalled(); expect(inspectPortUsage).toHaveBeenCalled(); - }, 20_000); + }); it("derives probe URL from service args + env (json)", async () => { resetRuntimeCapture(); @@ -162,7 +162,7 @@ describe("daemon-cli coverage", () => { expect(parsed.config?.mismatch).toBe(true); expect(parsed.rpc?.url).toBe("ws://127.0.0.1:19001"); expect(parsed.rpc?.ok).toBe(true); - }, 20_000); + }); it("passes deep scan flag for daemon status", async () => { findExtraGatewayServices.mockClear(); diff --git a/src/cli/gateway-cli.coverage.test.ts b/src/cli/gateway-cli.coverage.test.ts index c9b238dc8..4c426b0e8 100644 --- a/src/cli/gateway-cli.coverage.test.ts +++ b/src/cli/gateway-cli.coverage.test.ts @@ -143,9 +143,8 @@ describe("gateway-cli coverage", () => { expect(discoverGatewayBeacons).toHaveBeenCalledTimes(1); const out = runtimeLogs.join("\n"); - for (const text of ['"beacons"', '"wsUrl"', "ws://"]) { - expect(out).toContain(text); - } + expect(out).toContain('"beacons"'); + expect(out).toContain("ws://"); }); it("validates gateway discover timeout", async () => { diff --git a/src/commands/doctor.warns-state-directory-is-missing.test.ts b/src/commands/doctor.warns-state-directory-is-missing.test.ts index aabab0403..00453e2e1 100644 --- a/src/commands/doctor.warns-state-directory-is-missing.test.ts +++ b/src/commands/doctor.warns-state-directory-is-missing.test.ts @@ -30,7 +30,7 @@ describe("doctor command", () => { const stateNote = note.mock.calls.find((call) => call[1] === "State integrity"); expect(stateNote).toBeTruthy(); expect(String(stateNote?.[0])).toContain("CRITICAL"); - }, 30_000); + }); it("warns about opencode provider overrides", async () => { mockDoctorConfigSnapshot({ diff --git a/src/commands/onboard-non-interactive.provider-auth.test.ts b/src/commands/onboard-non-interactive.provider-auth.test.ts index 86cb58071..1bca5a57e 100644 --- a/src/commands/onboard-non-interactive.provider-auth.test.ts +++ b/src/commands/onboard-non-interactive.provider-auth.test.ts @@ -66,7 +66,7 @@ async function removeDirWithRetry(dir: string): Promise { if (!isTransient || attempt === 4) { throw error; } - await delay(25 * (attempt + 1)); + await delay(10 * (attempt + 1)); } } } @@ -189,7 +189,7 @@ describe("onboard (non-interactive): provider auth", () => { key: "sk-minimax-test", }); }); - }, 60_000); + }); it("supports MiniMax CN API endpoint auth choice", async () => { await withOnboardEnv("openclaw-onboard-minimax-cn-", async (env) => { @@ -208,7 +208,7 @@ describe("onboard (non-interactive): provider auth", () => { key: "sk-minimax-test", }); }); - }, 60_000); + }); it("stores Z.AI API key and uses global baseUrl by default", async () => { await withOnboardEnv("openclaw-onboard-zai-", async (env) => { @@ -223,7 +223,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(cfg.agents?.defaults?.model?.primary).toBe("zai/glm-5"); await expectApiKeyProfile({ profileId: "zai:default", provider: "zai", key: "zai-test-key" }); }); - }, 60_000); + }); it("supports Z.AI CN coding endpoint auth choice", async () => { await withOnboardEnv("openclaw-onboard-zai-cn-", async (env) => { @@ -236,7 +236,7 @@ describe("onboard (non-interactive): provider auth", () => { "https://open.bigmodel.cn/api/coding/paas/v4", ); }); - }, 60_000); + }); it("stores xAI API key and sets default model", async () => { await withOnboardEnv("openclaw-onboard-xai-", async (env) => { @@ -251,7 +251,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(cfg.agents?.defaults?.model?.primary).toBe("xai/grok-4"); await expectApiKeyProfile({ profileId: "xai:default", provider: "xai", key: "xai-test-key" }); }); - }, 60_000); + }); it("infers Mistral auth choice from --mistral-api-key and sets default model", async () => { await withOnboardEnv("openclaw-onboard-mistral-infer-", async (env) => { @@ -268,7 +268,7 @@ describe("onboard (non-interactive): provider auth", () => { key: "mistral-test-key", }); }); - }, 60_000); + }); it("stores Volcano Engine API key and sets default model", async () => { await withOnboardEnv("openclaw-onboard-volcengine-", async (env) => { @@ -279,7 +279,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(cfg.agents?.defaults?.model?.primary).toBe("volcengine-plan/ark-code-latest"); }); - }, 60_000); + }); it("infers BytePlus auth choice from --byteplus-api-key and sets default model", async () => { await withOnboardEnv("openclaw-onboard-byteplus-infer-", async (env) => { @@ -289,7 +289,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(cfg.agents?.defaults?.model?.primary).toBe("byteplus-plan/ark-code-latest"); }); - }, 60_000); + }); it("stores Vercel AI Gateway API key and sets default model", async () => { await withOnboardEnv("openclaw-onboard-ai-gateway-", async (env) => { @@ -309,7 +309,7 @@ describe("onboard (non-interactive): provider auth", () => { key: "gateway-test-key", }); }); - }, 60_000); + }); it("stores token auth profile", async () => { await withOnboardEnv("openclaw-onboard-token-", async ({ configPath, runtime }) => { @@ -336,7 +336,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(profile.token).toBe(cleanToken); } }); - }, 60_000); + }); it("stores OpenAI API key and sets OpenAI default model", async () => { await withOnboardEnv("openclaw-onboard-openai-", async (env) => { @@ -347,7 +347,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(cfg.agents?.defaults?.model?.primary).toBe(OPENAI_DEFAULT_MODEL); }); - }, 60_000); + }); it("rejects vLLM auth choice in non-interactive mode", async () => { await withOnboardEnv("openclaw-onboard-vllm-non-interactive-", async ({ runtime }) => { @@ -358,7 +358,7 @@ describe("onboard (non-interactive): provider auth", () => { }), ).rejects.toThrow('Auth choice "vllm" requires interactive mode.'); }); - }, 60_000); + }); it("stores LiteLLM API key and sets default model", async () => { await withOnboardEnv("openclaw-onboard-litellm-", async (env) => { @@ -376,7 +376,7 @@ describe("onboard (non-interactive): provider auth", () => { key: "litellm-test-key", }); }); - }, 60_000); + }); it.each([ { @@ -391,37 +391,31 @@ describe("onboard (non-interactive): provider auth", () => { prefix: "openclaw-onboard-cf-gateway-infer-", options: {}, }, - ])( - "$name", - async ({ prefix, options }) => { - await withOnboardEnv(prefix, async ({ configPath, runtime }) => { - await runNonInteractiveOnboardingWithDefaults(runtime, { - cloudflareAiGatewayAccountId: "cf-account-id", - cloudflareAiGatewayGatewayId: "cf-gateway-id", - cloudflareAiGatewayApiKey: "cf-gateway-test-key", - skipSkills: true, - ...options, - }); - - const cfg = await readJsonFile(configPath); - - expect(cfg.auth?.profiles?.["cloudflare-ai-gateway:default"]?.provider).toBe( - "cloudflare-ai-gateway", - ); - expect(cfg.auth?.profiles?.["cloudflare-ai-gateway:default"]?.mode).toBe("api_key"); - expect(cfg.agents?.defaults?.model?.primary).toBe( - "cloudflare-ai-gateway/claude-sonnet-4-5", - ); - await expectApiKeyProfile({ - profileId: "cloudflare-ai-gateway:default", - provider: "cloudflare-ai-gateway", - key: "cf-gateway-test-key", - metadata: { accountId: "cf-account-id", gatewayId: "cf-gateway-id" }, - }); + ])("$name", async ({ prefix, options }) => { + await withOnboardEnv(prefix, async ({ configPath, runtime }) => { + await runNonInteractiveOnboardingWithDefaults(runtime, { + cloudflareAiGatewayAccountId: "cf-account-id", + cloudflareAiGatewayGatewayId: "cf-gateway-id", + cloudflareAiGatewayApiKey: "cf-gateway-test-key", + skipSkills: true, + ...options, }); - }, - 60_000, - ); + + const cfg = await readJsonFile(configPath); + + expect(cfg.auth?.profiles?.["cloudflare-ai-gateway:default"]?.provider).toBe( + "cloudflare-ai-gateway", + ); + expect(cfg.auth?.profiles?.["cloudflare-ai-gateway:default"]?.mode).toBe("api_key"); + expect(cfg.agents?.defaults?.model?.primary).toBe("cloudflare-ai-gateway/claude-sonnet-4-5"); + await expectApiKeyProfile({ + profileId: "cloudflare-ai-gateway:default", + provider: "cloudflare-ai-gateway", + key: "cf-gateway-test-key", + metadata: { accountId: "cf-account-id", gatewayId: "cf-gateway-id" }, + }); + }); + }); it("infers Together auth choice from --together-api-key and sets default model", async () => { await withOnboardEnv("openclaw-onboard-together-infer-", async (env) => { @@ -438,7 +432,7 @@ describe("onboard (non-interactive): provider auth", () => { key: "together-test-key", }); }); - }, 60_000); + }); it("infers QIANFAN auth choice from --qianfan-api-key and sets default model", async () => { await withOnboardEnv("openclaw-onboard-qianfan-infer-", async (env) => { @@ -455,7 +449,7 @@ describe("onboard (non-interactive): provider auth", () => { key: "qianfan-test-key", }); }); - }, 60_000); + }); it("configures a custom provider from non-interactive flags", async () => { await withOnboardEnv("openclaw-onboard-custom-provider-", async ({ configPath, runtime }) => { @@ -477,7 +471,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(provider?.models?.some((model) => model.id === "foo-large")).toBe(true); expect(cfg.agents?.defaults?.model?.primary).toBe("custom-llm-example-com/foo-large"); }); - }, 60_000); + }); it("infers custom provider auth choice from custom flags", async () => { await withOnboardEnv( @@ -501,7 +495,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(cfg.agents?.defaults?.model?.primary).toBe("custom-models-custom-local/local-large"); }, ); - }, 60_000); + }); it("uses CUSTOM_API_KEY env fallback for non-interactive custom provider auth", async () => { await withOnboardEnv( @@ -512,7 +506,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(await readCustomLocalProviderApiKey(configPath)).toBe("custom-env-key"); }, ); - }, 60_000); + }); it("uses matching profile fallback for non-interactive custom provider auth", async () => { await withOnboardEnv( @@ -530,7 +524,7 @@ describe("onboard (non-interactive): provider auth", () => { expect(await readCustomLocalProviderApiKey(configPath)).toBe("custom-profile-key"); }, ); - }, 60_000); + }); it("fails custom provider auth when compatibility is invalid", async () => { await withOnboardEnv( @@ -547,7 +541,7 @@ describe("onboard (non-interactive): provider auth", () => { ).rejects.toThrow('Invalid --custom-compatibility (use "openai" or "anthropic").'); }, ); - }, 60_000); + }); it("fails custom provider auth when explicit provider id is invalid", async () => { await withOnboardEnv("openclaw-onboard-custom-provider-invalid-id-", async ({ runtime }) => { @@ -563,7 +557,7 @@ describe("onboard (non-interactive): provider auth", () => { "Invalid custom provider config: Custom provider ID must include letters, numbers, or hyphens.", ); }); - }, 60_000); + }); it("fails inferred custom auth when required flags are incomplete", async () => { await withOnboardEnv( @@ -577,5 +571,5 @@ describe("onboard (non-interactive): provider auth", () => { ).rejects.toThrow('Auth choice "custom-api-key" requires a base URL and model ID.'); }, ); - }, 60_000); + }); }); diff --git a/src/config/io.owner-display-secret.test.ts b/src/config/io.owner-display-secret.test.ts index 99f8f6b35..bbe7e0485 100644 --- a/src/config/io.owner-display-secret.test.ts +++ b/src/config/io.owner-display-secret.test.ts @@ -14,7 +14,7 @@ async function waitForPersistedSecret(configPath: string, expectedSecret: string if (parsed.commands?.ownerDisplaySecret === expectedSecret) { return; } - await new Promise((resolve) => setTimeout(resolve, 25)); + await new Promise((resolve) => setTimeout(resolve, 5)); } throw new Error("timed out waiting for ownerDisplaySecret persistence"); } diff --git a/src/security/audit.test.ts b/src/security/audit.test.ts index 699e13720..03af14cf8 100644 --- a/src/security/audit.test.ts +++ b/src/security/audit.test.ts @@ -103,6 +103,7 @@ function expectNoFinding(res: SecurityAuditReport, checkId: string): void { describe("security audit", () => { let fixtureRoot = ""; let caseId = 0; + let channelSecurityStateDir = ""; const makeTmpDir = async (label: string) => { const dir = path.join(fixtureRoot, `case-${caseId++}-${label}`); @@ -110,14 +111,23 @@ describe("security audit", () => { return dir; }; - const withStateDir = async (label: string, fn: (tmp: string) => Promise) => { - const tmp = await makeTmpDir(label); - await fs.mkdir(path.join(tmp, "credentials"), { recursive: true, mode: 0o700 }); - await withEnvAsync({ OPENCLAW_STATE_DIR: tmp }, async () => await fn(tmp)); + const withChannelSecurityStateDir = async (fn: (tmp: string) => Promise) => { + const credentialsDir = path.join(channelSecurityStateDir, "credentials"); + await fs.rm(credentialsDir, { recursive: true, force: true }); + await fs.mkdir(credentialsDir, { recursive: true, mode: 0o700 }); + await withEnvAsync( + { OPENCLAW_STATE_DIR: channelSecurityStateDir }, + async () => await fn(channelSecurityStateDir), + ); }; beforeAll(async () => { fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-security-audit-")); + channelSecurityStateDir = path.join(fixtureRoot, "channel-security"); + await fs.mkdir(path.join(channelSecurityStateDir, "credentials"), { + recursive: true, + mode: 0o700, + }); }); afterAll(async () => { @@ -1367,7 +1377,7 @@ describe("security audit", () => { }); it("flags Discord native commands without a guild user allowlist", async () => { - await withStateDir("discord", async () => { + await withChannelSecurityStateDir(async () => { const cfg: OpenClawConfig = { channels: { discord: { @@ -1404,7 +1414,7 @@ describe("security audit", () => { }); it("does not flag Discord slash commands when dm.allowFrom includes a Discord snowflake id", async () => { - await withStateDir("discord-allowfrom-snowflake", async () => { + await withChannelSecurityStateDir(async () => { const cfg: OpenClawConfig = { channels: { discord: { @@ -1441,7 +1451,7 @@ describe("security audit", () => { }); it("warns when Discord allowlists contain name-based entries", async () => { - await withStateDir("discord-name-based-allowlist", async (tmp) => { + await withChannelSecurityStateDir(async (tmp) => { await fs.writeFile( path.join(tmp, "credentials", "discord-allowFrom.json"), JSON.stringify({ version: 1, allowFrom: ["team.owner"] }), @@ -1491,7 +1501,7 @@ describe("security audit", () => { }); it("does not warn when Discord allowlists use ID-style entries only", async () => { - await withStateDir("discord-id-only-allowlist", async () => { + await withChannelSecurityStateDir(async () => { const cfg: OpenClawConfig = { channels: { discord: { @@ -1534,7 +1544,7 @@ describe("security audit", () => { }); it("flags Discord slash commands when access-group enforcement is disabled and no users allowlist exists", async () => { - await withStateDir("discord-open", async () => { + await withChannelSecurityStateDir(async () => { const cfg: OpenClawConfig = { commands: { useAccessGroups: false }, channels: { @@ -1572,7 +1582,7 @@ describe("security audit", () => { }); it("flags Slack slash commands without a channel users allowlist", async () => { - await withStateDir("slack", async () => { + await withChannelSecurityStateDir(async () => { const cfg: OpenClawConfig = { channels: { slack: { @@ -1604,7 +1614,7 @@ describe("security audit", () => { }); it("flags Slack slash commands when access-group enforcement is disabled", async () => { - await withStateDir("slack-open", async () => { + await withChannelSecurityStateDir(async () => { const cfg: OpenClawConfig = { commands: { useAccessGroups: false }, channels: { @@ -1637,7 +1647,7 @@ describe("security audit", () => { }); it("flags Telegram group commands without a sender allowlist", async () => { - await withStateDir("telegram", async () => { + await withChannelSecurityStateDir(async () => { const cfg: OpenClawConfig = { channels: { telegram: { @@ -1668,7 +1678,7 @@ describe("security audit", () => { }); it("warns when Telegram allowFrom entries are non-numeric (legacy @username configs)", async () => { - await withStateDir("telegram-invalid-allowfrom", async () => { + await withChannelSecurityStateDir(async () => { const cfg: OpenClawConfig = { channels: { telegram: {