Tests: harden flake hotspots and consolidate provider-auth suites (#11598)

* Tests: harden flake hotspots and consolidate provider-auth suites

* Tests: restore env vars by deleting missing snapshot values

* Tests: use real newline in memory summary filter case

* Tests(memory): use fake timers for qmd timeout coverage

* Changelog: add tests hardening entry for #11598
This commit is contained in:
Gustavo Madeira Santana
2026-02-07 21:32:23 -05:00
committed by GitHub
parent a30c4f45c3
commit e2dea2684f
12 changed files with 400 additions and 617 deletions

View File

@@ -1,93 +0,0 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
describe("onboard (non-interactive): Vercel AI Gateway", () => {
it("stores the API key and configures the default model", async () => {
const prev = {
home: process.env.HOME,
stateDir: process.env.OPENCLAW_STATE_DIR,
configPath: process.env.OPENCLAW_CONFIG_PATH,
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
skipCron: process.env.OPENCLAW_SKIP_CRON,
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
token: process.env.OPENCLAW_GATEWAY_TOKEN,
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
};
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-gateway-"));
process.env.HOME = tempHome;
process.env.OPENCLAW_STATE_DIR = tempHome;
process.env.OPENCLAW_CONFIG_PATH = path.join(tempHome, "openclaw.json");
vi.resetModules();
const runtime = {
log: () => {},
error: (msg: string) => {
throw new Error(msg);
},
exit: (code: number) => {
throw new Error(`exit:${code}`);
},
};
try {
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
await runNonInteractiveOnboarding(
{
nonInteractive: true,
authChoice: "ai-gateway-api-key",
aiGatewayApiKey: "gateway-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const { CONFIG_PATH } = await import("../config/config.js");
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8")) as {
auth?: {
profiles?: Record<string, { provider?: string; mode?: string }>;
};
agents?: { defaults?: { model?: { primary?: string } } };
};
expect(cfg.auth?.profiles?.["vercel-ai-gateway:default"]?.provider).toBe("vercel-ai-gateway");
expect(cfg.auth?.profiles?.["vercel-ai-gateway:default"]?.mode).toBe("api_key");
expect(cfg.agents?.defaults?.model?.primary).toBe(
"vercel-ai-gateway/anthropic/claude-opus-4.6",
);
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
const store = ensureAuthProfileStore();
const profile = store.profiles["vercel-ai-gateway:default"];
expect(profile?.type).toBe("api_key");
if (profile?.type === "api_key") {
expect(profile.provider).toBe("vercel-ai-gateway");
expect(profile.key).toBe("gateway-test-key");
}
} finally {
await fs.rm(tempHome, { recursive: true, force: true });
process.env.HOME = prev.home;
process.env.OPENCLAW_STATE_DIR = prev.stateDir;
process.env.OPENCLAW_CONFIG_PATH = prev.configPath;
process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels;
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail;
process.env.OPENCLAW_SKIP_CRON = prev.skipCron;
process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas;
process.env.OPENCLAW_GATEWAY_TOKEN = prev.token;
process.env.OPENCLAW_GATEWAY_PASSWORD = prev.password;
}
}, 60_000);
});

View File

@@ -1,191 +0,0 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
describe("onboard (non-interactive): Cloudflare AI Gateway", () => {
it("stores the API key and configures the default model", async () => {
const prev = {
home: process.env.HOME,
stateDir: process.env.OPENCLAW_STATE_DIR,
configPath: process.env.OPENCLAW_CONFIG_PATH,
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
skipCron: process.env.OPENCLAW_SKIP_CRON,
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
token: process.env.OPENCLAW_GATEWAY_TOKEN,
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
};
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-cf-gateway-"));
process.env.HOME = tempHome;
process.env.OPENCLAW_STATE_DIR = tempHome;
process.env.OPENCLAW_CONFIG_PATH = path.join(tempHome, "openclaw.json");
vi.resetModules();
const runtime = {
log: () => {},
error: (msg: string) => {
throw new Error(msg);
},
exit: (code: number) => {
throw new Error(`exit:${code}`);
},
};
try {
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
await runNonInteractiveOnboarding(
{
nonInteractive: true,
authChoice: "cloudflare-ai-gateway-api-key",
cloudflareAiGatewayAccountId: "cf-account-id",
cloudflareAiGatewayGatewayId: "cf-gateway-id",
cloudflareAiGatewayApiKey: "cf-gateway-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const { CONFIG_PATH } = await import("../config/config.js");
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8")) as {
auth?: {
profiles?: Record<string, { provider?: string; mode?: string }>;
};
agents?: { defaults?: { model?: { primary?: string } } };
};
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");
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
const store = ensureAuthProfileStore();
const profile = store.profiles["cloudflare-ai-gateway:default"];
expect(profile?.type).toBe("api_key");
if (profile?.type === "api_key") {
expect(profile.provider).toBe("cloudflare-ai-gateway");
expect(profile.key).toBe("cf-gateway-test-key");
expect(profile.metadata).toEqual({
accountId: "cf-account-id",
gatewayId: "cf-gateway-id",
});
}
} finally {
await fs.rm(tempHome, { recursive: true, force: true });
process.env.HOME = prev.home;
process.env.OPENCLAW_STATE_DIR = prev.stateDir;
process.env.OPENCLAW_CONFIG_PATH = prev.configPath;
process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels;
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail;
process.env.OPENCLAW_SKIP_CRON = prev.skipCron;
process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas;
process.env.OPENCLAW_GATEWAY_TOKEN = prev.token;
process.env.OPENCLAW_GATEWAY_PASSWORD = prev.password;
}
}, 60_000);
it("infers auth choice from API key flags", async () => {
const prev = {
home: process.env.HOME,
stateDir: process.env.OPENCLAW_STATE_DIR,
configPath: process.env.OPENCLAW_CONFIG_PATH,
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
skipCron: process.env.OPENCLAW_SKIP_CRON,
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
token: process.env.OPENCLAW_GATEWAY_TOKEN,
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
};
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-cf-gateway-infer-"));
process.env.HOME = tempHome;
process.env.OPENCLAW_STATE_DIR = tempHome;
process.env.OPENCLAW_CONFIG_PATH = path.join(tempHome, "openclaw.json");
vi.resetModules();
const runtime = {
log: () => {},
error: (msg: string) => {
throw new Error(msg);
},
exit: (code: number) => {
throw new Error(`exit:${code}`);
},
};
try {
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
await runNonInteractiveOnboarding(
{
nonInteractive: true,
cloudflareAiGatewayAccountId: "cf-account-id",
cloudflareAiGatewayGatewayId: "cf-gateway-id",
cloudflareAiGatewayApiKey: "cf-gateway-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const { CONFIG_PATH } = await import("../config/config.js");
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8")) as {
auth?: {
profiles?: Record<string, { provider?: string; mode?: string }>;
};
agents?: { defaults?: { model?: { primary?: string } } };
};
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");
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
const store = ensureAuthProfileStore();
const profile = store.profiles["cloudflare-ai-gateway:default"];
expect(profile?.type).toBe("api_key");
if (profile?.type === "api_key") {
expect(profile.provider).toBe("cloudflare-ai-gateway");
expect(profile.key).toBe("cf-gateway-test-key");
expect(profile.metadata).toEqual({
accountId: "cf-account-id",
gatewayId: "cf-gateway-id",
});
}
} finally {
await fs.rm(tempHome, { recursive: true, force: true });
process.env.HOME = prev.home;
process.env.OPENCLAW_STATE_DIR = prev.stateDir;
process.env.OPENCLAW_CONFIG_PATH = prev.configPath;
process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels;
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail;
process.env.OPENCLAW_SKIP_CRON = prev.skipCron;
process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas;
process.env.OPENCLAW_GATEWAY_TOKEN = prev.token;
process.env.OPENCLAW_GATEWAY_PASSWORD = prev.password;
}
}, 60_000);
});

View File

@@ -1,77 +0,0 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { OPENAI_DEFAULT_MODEL } from "./openai-model-default.js";
describe("onboard (non-interactive): OpenAI API key", () => {
it("stores OPENAI_API_KEY and configures the OpenAI default model", async () => {
const prev = {
home: process.env.HOME,
stateDir: process.env.OPENCLAW_STATE_DIR,
configPath: process.env.OPENCLAW_CONFIG_PATH,
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
skipCron: process.env.OPENCLAW_SKIP_CRON,
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
token: process.env.OPENCLAW_GATEWAY_TOKEN,
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
};
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-openai-"));
process.env.HOME = tempHome;
process.env.OPENCLAW_STATE_DIR = tempHome;
process.env.OPENCLAW_CONFIG_PATH = path.join(tempHome, "openclaw.json");
vi.resetModules();
const runtime = {
log: () => {},
error: (msg: string) => {
throw new Error(msg);
},
exit: (code: number) => {
throw new Error(`exit:${code}`);
},
};
try {
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
await runNonInteractiveOnboarding(
{
nonInteractive: true,
authChoice: "openai-api-key",
openaiApiKey: "sk-openai-test",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const { CONFIG_PATH } = await import("../config/config.js");
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8")) as {
agents?: { defaults?: { model?: { primary?: string } } };
};
expect(cfg.agents?.defaults?.model?.primary).toBe(OPENAI_DEFAULT_MODEL);
} finally {
await fs.rm(tempHome, { recursive: true, force: true });
process.env.HOME = prev.home;
process.env.OPENCLAW_STATE_DIR = prev.stateDir;
process.env.OPENCLAW_CONFIG_PATH = prev.configPath;
process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels;
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail;
process.env.OPENCLAW_SKIP_CRON = prev.skipCron;
process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas;
process.env.OPENCLAW_GATEWAY_TOKEN = prev.token;
process.env.OPENCLAW_GATEWAY_PASSWORD = prev.password;
}
}, 60_000);
});

View File

@@ -0,0 +1,327 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
import { OPENAI_DEFAULT_MODEL } from "./openai-model-default.js";
type RuntimeMock = {
log: () => void;
error: (msg: string) => never;
exit: (code: number) => never;
};
type EnvSnapshot = {
home: string | undefined;
stateDir: string | undefined;
configPath: string | undefined;
skipChannels: string | undefined;
skipGmail: string | undefined;
skipCron: string | undefined;
skipCanvas: string | undefined;
token: string | undefined;
password: string | undefined;
disableConfigCache: string | undefined;
};
type OnboardEnv = {
configPath: string;
runtime: RuntimeMock;
};
function captureEnv(): EnvSnapshot {
return {
home: process.env.HOME,
stateDir: process.env.OPENCLAW_STATE_DIR,
configPath: process.env.OPENCLAW_CONFIG_PATH,
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
skipCron: process.env.OPENCLAW_SKIP_CRON,
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
token: process.env.OPENCLAW_GATEWAY_TOKEN,
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
disableConfigCache: process.env.OPENCLAW_DISABLE_CONFIG_CACHE,
};
}
function restoreEnvVar(key: keyof NodeJS.ProcessEnv, value: string | undefined): void {
if (value == null) {
delete process.env[key];
return;
}
process.env[key] = value;
}
function restoreEnv(prev: EnvSnapshot): void {
restoreEnvVar("HOME", prev.home);
restoreEnvVar("OPENCLAW_STATE_DIR", prev.stateDir);
restoreEnvVar("OPENCLAW_CONFIG_PATH", prev.configPath);
restoreEnvVar("OPENCLAW_SKIP_CHANNELS", prev.skipChannels);
restoreEnvVar("OPENCLAW_SKIP_GMAIL_WATCHER", prev.skipGmail);
restoreEnvVar("OPENCLAW_SKIP_CRON", prev.skipCron);
restoreEnvVar("OPENCLAW_SKIP_CANVAS_HOST", prev.skipCanvas);
restoreEnvVar("OPENCLAW_GATEWAY_TOKEN", prev.token);
restoreEnvVar("OPENCLAW_GATEWAY_PASSWORD", prev.password);
restoreEnvVar("OPENCLAW_DISABLE_CONFIG_CACHE", prev.disableConfigCache);
}
async function withOnboardEnv(
prefix: string,
run: (ctx: OnboardEnv) => Promise<void>,
): Promise<void> {
const prev = captureEnv();
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
process.env.OPENCLAW_DISABLE_CONFIG_CACHE = "1";
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), prefix));
const configPath = path.join(tempHome, "openclaw.json");
process.env.HOME = tempHome;
process.env.OPENCLAW_STATE_DIR = tempHome;
process.env.OPENCLAW_CONFIG_PATH = configPath;
vi.resetModules();
const runtime: RuntimeMock = {
log: () => {},
error: (msg: string) => {
throw new Error(msg);
},
exit: (code: number) => {
throw new Error(`exit:${code}`);
},
};
try {
await run({ configPath, runtime });
} finally {
await fs.rm(tempHome, { recursive: true, force: true });
restoreEnv(prev);
}
}
async function runNonInteractive(
options: Record<string, unknown>,
runtime: RuntimeMock,
): Promise<void> {
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
await runNonInteractiveOnboarding(options, runtime);
}
async function readJsonFile<T>(filePath: string): Promise<T> {
return JSON.parse(await fs.readFile(filePath, "utf8")) as T;
}
async function expectApiKeyProfile(params: {
profileId: string;
provider: string;
key: string;
metadata?: Record<string, string>;
}): Promise<void> {
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
const store = ensureAuthProfileStore();
const profile = store.profiles[params.profileId];
expect(profile?.type).toBe("api_key");
if (profile?.type === "api_key") {
expect(profile.provider).toBe(params.provider);
expect(profile.key).toBe(params.key);
if (params.metadata) {
expect(profile.metadata).toEqual(params.metadata);
}
}
}
describe("onboard (non-interactive): provider auth", () => {
it("stores xAI API key and sets default model", async () => {
await withOnboardEnv("openclaw-onboard-xai-", async ({ configPath, runtime }) => {
await runNonInteractive(
{
nonInteractive: true,
authChoice: "xai-api-key",
xaiApiKey: "xai-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const cfg = await readJsonFile<{
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
agents?: { defaults?: { model?: { primary?: string } } };
}>(configPath);
expect(cfg.auth?.profiles?.["xai:default"]?.provider).toBe("xai");
expect(cfg.auth?.profiles?.["xai:default"]?.mode).toBe("api_key");
expect(cfg.agents?.defaults?.model?.primary).toBe("xai/grok-4");
await expectApiKeyProfile({ profileId: "xai:default", provider: "xai", key: "xai-test-key" });
});
}, 60_000);
it("stores Vercel AI Gateway API key and sets default model", async () => {
await withOnboardEnv("openclaw-onboard-ai-gateway-", async ({ configPath, runtime }) => {
await runNonInteractive(
{
nonInteractive: true,
authChoice: "ai-gateway-api-key",
aiGatewayApiKey: "gateway-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const cfg = await readJsonFile<{
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
agents?: { defaults?: { model?: { primary?: string } } };
}>(configPath);
expect(cfg.auth?.profiles?.["vercel-ai-gateway:default"]?.provider).toBe("vercel-ai-gateway");
expect(cfg.auth?.profiles?.["vercel-ai-gateway:default"]?.mode).toBe("api_key");
expect(cfg.agents?.defaults?.model?.primary).toBe(
"vercel-ai-gateway/anthropic/claude-opus-4.6",
);
await expectApiKeyProfile({
profileId: "vercel-ai-gateway:default",
provider: "vercel-ai-gateway",
key: "gateway-test-key",
});
});
}, 60_000);
it("stores token auth profile", async () => {
await withOnboardEnv("openclaw-onboard-token-", async ({ configPath, runtime }) => {
const token = `sk-ant-oat01-${"a".repeat(80)}`;
await runNonInteractive(
{
nonInteractive: true,
authChoice: "token",
tokenProvider: "anthropic",
token,
tokenProfileId: "anthropic:default",
skipHealth: true,
skipChannels: true,
json: true,
},
runtime,
);
const cfg = await readJsonFile<{
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
}>(configPath);
expect(cfg.auth?.profiles?.["anthropic:default"]?.provider).toBe("anthropic");
expect(cfg.auth?.profiles?.["anthropic:default"]?.mode).toBe("token");
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
const store = ensureAuthProfileStore();
const profile = store.profiles["anthropic:default"];
expect(profile?.type).toBe("token");
if (profile?.type === "token") {
expect(profile.provider).toBe("anthropic");
expect(profile.token).toBe(token);
}
});
}, 60_000);
it("stores OpenAI API key and sets OpenAI default model", async () => {
await withOnboardEnv("openclaw-onboard-openai-", async ({ configPath, runtime }) => {
await runNonInteractive(
{
nonInteractive: true,
authChoice: "openai-api-key",
openaiApiKey: "sk-openai-test",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const cfg = await readJsonFile<{
agents?: { defaults?: { model?: { primary?: string } } };
}>(configPath);
expect(cfg.agents?.defaults?.model?.primary).toBe(OPENAI_DEFAULT_MODEL);
});
}, 60_000);
it("stores Cloudflare AI Gateway API key and metadata", async () => {
await withOnboardEnv("openclaw-onboard-cf-gateway-", async ({ configPath, runtime }) => {
await runNonInteractive(
{
nonInteractive: true,
authChoice: "cloudflare-ai-gateway-api-key",
cloudflareAiGatewayAccountId: "cf-account-id",
cloudflareAiGatewayGatewayId: "cf-gateway-id",
cloudflareAiGatewayApiKey: "cf-gateway-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const cfg = await readJsonFile<{
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
agents?: { defaults?: { model?: { primary?: string } } };
}>(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" },
});
});
}, 60_000);
it("infers Cloudflare auth choice from API key flags", async () => {
await withOnboardEnv("openclaw-onboard-cf-gateway-infer-", async ({ configPath, runtime }) => {
await runNonInteractive(
{
nonInteractive: true,
cloudflareAiGatewayAccountId: "cf-account-id",
cloudflareAiGatewayGatewayId: "cf-gateway-id",
cloudflareAiGatewayApiKey: "cf-gateway-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const cfg = await readJsonFile<{
auth?: { profiles?: Record<string, { provider?: string; mode?: string }> };
agents?: { defaults?: { model?: { primary?: string } } };
}>(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" },
});
});
}, 60_000);
});

View File

@@ -1,92 +0,0 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
describe("onboard (non-interactive): token auth", () => {
it("writes token profile config and stores the token", async () => {
const prev = {
home: process.env.HOME,
stateDir: process.env.OPENCLAW_STATE_DIR,
configPath: process.env.OPENCLAW_CONFIG_PATH,
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
skipCron: process.env.OPENCLAW_SKIP_CRON,
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
token: process.env.OPENCLAW_GATEWAY_TOKEN,
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
};
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-token-"));
process.env.HOME = tempHome;
process.env.OPENCLAW_STATE_DIR = tempHome;
process.env.OPENCLAW_CONFIG_PATH = path.join(tempHome, "openclaw.json");
vi.resetModules();
const token = `sk-ant-oat01-${"a".repeat(80)}`;
const runtime = {
log: () => {},
error: (msg: string) => {
throw new Error(msg);
},
exit: (code: number) => {
throw new Error(`exit:${code}`);
},
};
try {
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
await runNonInteractiveOnboarding(
{
nonInteractive: true,
authChoice: "token",
tokenProvider: "anthropic",
token,
tokenProfileId: "anthropic:default",
skipHealth: true,
skipChannels: true,
json: true,
},
runtime,
);
const { CONFIG_PATH } = await import("../config/config.js");
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8")) as {
auth?: {
profiles?: Record<string, { provider?: string; mode?: string }>;
};
};
expect(cfg.auth?.profiles?.["anthropic:default"]?.provider).toBe("anthropic");
expect(cfg.auth?.profiles?.["anthropic:default"]?.mode).toBe("token");
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
const store = ensureAuthProfileStore();
const profile = store.profiles["anthropic:default"];
expect(profile?.type).toBe("token");
if (profile?.type === "token") {
expect(profile.provider).toBe("anthropic");
expect(profile.token).toBe(token);
}
} finally {
await fs.rm(tempHome, { recursive: true, force: true });
process.env.HOME = prev.home;
process.env.OPENCLAW_STATE_DIR = prev.stateDir;
process.env.OPENCLAW_CONFIG_PATH = prev.configPath;
process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels;
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail;
process.env.OPENCLAW_SKIP_CRON = prev.skipCron;
process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas;
process.env.OPENCLAW_GATEWAY_TOKEN = prev.token;
process.env.OPENCLAW_GATEWAY_PASSWORD = prev.password;
}
}, 60_000);
});

View File

@@ -1,91 +0,0 @@
import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { describe, expect, it, vi } from "vitest";
describe("onboard (non-interactive): xAI", () => {
it("stores the API key and configures the default model", async () => {
const prev = {
home: process.env.HOME,
stateDir: process.env.OPENCLAW_STATE_DIR,
configPath: process.env.OPENCLAW_CONFIG_PATH,
skipChannels: process.env.OPENCLAW_SKIP_CHANNELS,
skipGmail: process.env.OPENCLAW_SKIP_GMAIL_WATCHER,
skipCron: process.env.OPENCLAW_SKIP_CRON,
skipCanvas: process.env.OPENCLAW_SKIP_CANVAS_HOST,
token: process.env.OPENCLAW_GATEWAY_TOKEN,
password: process.env.OPENCLAW_GATEWAY_PASSWORD,
};
process.env.OPENCLAW_SKIP_CHANNELS = "1";
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = "1";
process.env.OPENCLAW_SKIP_CRON = "1";
process.env.OPENCLAW_SKIP_CANVAS_HOST = "1";
delete process.env.OPENCLAW_GATEWAY_TOKEN;
delete process.env.OPENCLAW_GATEWAY_PASSWORD;
const tempHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-onboard-xai-"));
process.env.HOME = tempHome;
process.env.OPENCLAW_STATE_DIR = tempHome;
process.env.OPENCLAW_CONFIG_PATH = path.join(tempHome, "openclaw.json");
vi.resetModules();
const runtime = {
log: () => {},
error: (msg: string) => {
throw new Error(msg);
},
exit: (code: number) => {
throw new Error(`exit:${code}`);
},
};
try {
const { runNonInteractiveOnboarding } = await import("./onboard-non-interactive.js");
await runNonInteractiveOnboarding(
{
nonInteractive: true,
authChoice: "xai-api-key",
xaiApiKey: "xai-test-key",
skipHealth: true,
skipChannels: true,
skipSkills: true,
json: true,
},
runtime,
);
const { CONFIG_PATH } = await import("../config/config.js");
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8")) as {
auth?: {
profiles?: Record<string, { provider?: string; mode?: string }>;
};
agents?: { defaults?: { model?: { primary?: string } } };
};
expect(cfg.auth?.profiles?.["xai:default"]?.provider).toBe("xai");
expect(cfg.auth?.profiles?.["xai:default"]?.mode).toBe("api_key");
expect(cfg.agents?.defaults?.model?.primary).toBe("xai/grok-4");
const { ensureAuthProfileStore } = await import("../agents/auth-profiles.js");
const store = ensureAuthProfileStore();
const profile = store.profiles["xai:default"];
expect(profile?.type).toBe("api_key");
if (profile?.type === "api_key") {
expect(profile.provider).toBe("xai");
expect(profile.key).toBe("xai-test-key");
}
} finally {
await fs.rm(tempHome, { recursive: true, force: true });
process.env.HOME = prev.home;
process.env.OPENCLAW_STATE_DIR = prev.stateDir;
process.env.OPENCLAW_CONFIG_PATH = prev.configPath;
process.env.OPENCLAW_SKIP_CHANNELS = prev.skipChannels;
process.env.OPENCLAW_SKIP_GMAIL_WATCHER = prev.skipGmail;
process.env.OPENCLAW_SKIP_CRON = prev.skipCron;
process.env.OPENCLAW_SKIP_CANVAS_HOST = prev.skipCanvas;
process.env.OPENCLAW_GATEWAY_TOKEN = prev.token;
process.env.OPENCLAW_GATEWAY_PASSWORD = prev.password;
}
}, 60_000);
});

View File

@@ -25,9 +25,15 @@ function createMockChild(params?: { autoClose?: boolean; closeDelayMs?: number }
};
if (params?.autoClose !== false) {
const delayMs = params?.closeDelayMs ?? 0;
setTimeout(() => {
child.emit("close", 0);
}, delayMs);
if (delayMs <= 0) {
queueMicrotask(() => {
child.emit("close", 0);
});
} else {
setTimeout(() => {
child.emit("close", 0);
}, delayMs);
}
}
return child;
}
@@ -73,6 +79,7 @@ describe("QmdMemoryManager", () => {
});
afterEach(async () => {
vi.useRealTimers();
delete process.env.OPENCLAW_STATE_DIR;
await fs.rm(tmpRoot, { recursive: true, force: true });
});
@@ -218,6 +225,7 @@ describe("QmdMemoryManager", () => {
});
it("times out qmd update during sync when configured", async () => {
vi.useFakeTimers();
cfg = {
...cfg,
memory: {
@@ -242,14 +250,17 @@ describe("QmdMemoryManager", () => {
});
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
const manager = await QmdMemoryManager.create({ cfg, agentId, resolved });
const createPromise = QmdMemoryManager.create({ cfg, agentId, resolved });
await vi.advanceTimersByTimeAsync(0);
const manager = await createPromise;
expect(manager).toBeTruthy();
if (!manager) {
throw new Error("manager missing");
}
await expect(manager.sync({ reason: "manual" })).rejects.toThrow(
"qmd update timed out after 20ms",
);
const syncPromise = manager.sync({ reason: "manual" });
const rejected = expect(syncPromise).rejects.toThrow("qmd update timed out after 20ms");
await vi.advanceTimersByTimeAsync(20);
await rejected;
await manager.close();
});
@@ -378,6 +389,7 @@ describe("QmdMemoryManager", () => {
});
it("logs and continues when qmd embed times out", async () => {
vi.useFakeTimers();
cfg = {
...cfg,
memory: {
@@ -402,12 +414,17 @@ describe("QmdMemoryManager", () => {
});
const resolved = resolveMemoryBackendConfig({ cfg, agentId });
const manager = await QmdMemoryManager.create({ cfg, agentId, resolved });
const createPromise = QmdMemoryManager.create({ cfg, agentId, resolved });
await vi.advanceTimersByTimeAsync(0);
const manager = await createPromise;
expect(manager).toBeTruthy();
if (!manager) {
throw new Error("manager missing");
}
await expect(manager.sync({ reason: "manual" })).resolves.toBeUndefined();
const syncPromise = manager.sync({ reason: "manual" });
const resolvedSync = expect(syncPromise).resolves.toBeUndefined();
await vi.advanceTimersByTimeAsync(20);
await resolvedSync;
await manager.close();
});

View File

@@ -11,6 +11,20 @@ import { runSecurityAudit } from "./audit.js";
const isWindows = process.platform === "win32";
function successfulProbeResult(url: string) {
return {
ok: true,
url,
connectLatencyMs: 1,
error: null,
close: null,
health: null,
status: null,
presence: null,
configSnapshot: null,
};
}
describe("security audit", () => {
it("includes an attack surface summary (info)", async () => {
const cfg: OpenClawConfig = {
@@ -1021,6 +1035,7 @@ describe("security audit", () => {
includeChannelSecurity: false,
deep: true,
stateDir: tmpDir,
probeGatewayFn: async (opts) => successfulProbeResult(opts.url),
});
expect(
@@ -1075,6 +1090,7 @@ description: test skill
includeChannelSecurity: false,
deep: true,
stateDir: tmpDir,
probeGatewayFn: async (opts) => successfulProbeResult(opts.url),
});
const pluginFinding = deepRes.findings.find(
@@ -1113,6 +1129,7 @@ description: test skill
includeChannelSecurity: false,
deep: true,
stateDir: tmpDir,
probeGatewayFn: async (opts) => successfulProbeResult(opts.url),
});
expect(res.findings.some((f) => f.checkId === "plugins.code_safety.entry_escape")).toBe(true);

View File

@@ -23,10 +23,12 @@ const { createWaSocket } = await import("./session.js");
describe("web login", () => {
beforeEach(() => {
vi.useFakeTimers();
vi.clearAllMocks();
});
afterEach(() => {
vi.useRealTimers();
resetLogger();
setLoggerOverride(null);
});
@@ -36,7 +38,12 @@ describe("web login", () => {
const close = vi.spyOn(sock.ws, "close");
const waiter: typeof waitForWaConnection = vi.fn().mockResolvedValue(undefined);
await loginWeb(false, waiter);
await new Promise((resolve) => setTimeout(resolve, 550));
expect(close).toHaveBeenCalled();
expect(close).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(499);
expect(close).not.toHaveBeenCalled();
await vi.advanceTimersByTimeAsync(1);
expect(close).toHaveBeenCalledTimes(1);
});
});