test: speed up test suite and trim redundant onboarding tests

This commit is contained in:
Peter Steinberger
2026-02-13 04:30:39 +00:00
parent ac41176532
commit ba7dccc49d
5 changed files with 90 additions and 203 deletions

View File

@@ -5,19 +5,24 @@ import path from "node:path";
const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
const runs = [
{
name: "unit",
args: ["vitest", "run", "--config", "vitest.unit.config.ts"],
},
{
name: "extensions",
args: ["vitest", "run", "--config", "vitest.extensions.config.ts"],
},
{
name: "gateway",
args: ["vitest", "run", "--config", "vitest.gateway.config.ts"],
},
const unitIsolatedFiles = [
"src/plugins/loader.test.ts",
"src/plugins/tools.optional.test.ts",
"src/agents/session-tool-result-guard.tool-result-persist-hook.test.ts",
"src/security/fix.test.ts",
"src/utils.test.ts",
"src/auto-reply/tool-meta.test.ts",
"src/commands/auth-choice.test.ts",
"src/media/store.header-ext.test.ts",
"src/browser/server.covers-additional-endpoint-branches.test.ts",
"src/browser/server.post-tabs-open-profile-unknown-returns-404.test.ts",
"src/browser/server.agent-contract-snapshot-endpoints.test.ts",
"src/browser/server.agent-contract-form-layout-act-commands.test.ts",
"src/browser/server.serves-status-starts-browser-requested.test.ts",
"src/browser/server.skips-default-maxchars-explicitly-set-zero.test.ts",
"src/browser/server.auth-token-gates-http.test.ts",
"src/browser/server-context.remote-tab-ops.test.ts",
"src/browser/server-context.ensure-tab-available.prefers-last-target.test.ts",
];
const children = new Set();
@@ -25,6 +30,62 @@ const isCI = process.env.CI === "true" || process.env.GITHUB_ACTIONS === "true";
const isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS";
const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows";
const isWindowsCi = isCI && isWindows;
const useVmForks =
process.env.OPENCLAW_TEST_VM_FORKS === "1" ||
(process.env.OPENCLAW_TEST_VM_FORKS !== "0" && !isWindows);
const runs = [
...(useVmForks
? [
{
name: "unit-fast",
args: [
"vitest",
"run",
"--config",
"vitest.unit.config.ts",
"--pool=vmForks",
...unitIsolatedFiles.flatMap((file) => ["--exclude", file]),
],
},
{
name: "unit-isolated",
args: [
"vitest",
"run",
"--config",
"vitest.unit.config.ts",
"--pool=forks",
...unitIsolatedFiles,
],
},
]
: [
{
name: "unit",
args: ["vitest", "run", "--config", "vitest.unit.config.ts"],
},
]),
{
name: "extensions",
args: [
"vitest",
"run",
"--config",
"vitest.extensions.config.ts",
...(useVmForks ? ["--pool=vmForks"] : []),
],
},
{
name: "gateway",
args: [
"vitest",
"run",
"--config",
"vitest.gateway.config.ts",
...(useVmForks ? ["--pool=vmForks"] : []),
],
},
];
const shardOverride = Number.parseInt(process.env.OPENCLAW_TEST_SHARDS ?? "", 10);
const shardCount = isWindowsCi
? Number.isFinite(shardOverride) && shardOverride > 1
@@ -40,12 +101,12 @@ const passthroughArgs =
const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10);
const resolvedOverride =
Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
// Keep gateway serial by default to avoid resource contention with unit/extensions.
// Allow explicit opt-in parallel runs on non-Windows CI/local when requested.
// Keep gateway serial on Windows CI and CI by default; run in parallel locally
// for lower wall-clock time. CI can opt in via OPENCLAW_TEST_PARALLEL_GATEWAY=1.
const keepGatewaySerial =
isWindowsCi ||
process.env.OPENCLAW_TEST_SERIAL_GATEWAY === "1" ||
process.env.OPENCLAW_TEST_PARALLEL_GATEWAY !== "1";
(isCI && process.env.OPENCLAW_TEST_PARALLEL_GATEWAY !== "1");
const parallelRuns = keepGatewaySerial ? runs.filter((entry) => entry.name !== "gateway") : runs;
const serialRuns = keepGatewaySerial ? runs.filter((entry) => entry.name === "gateway") : [];
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
@@ -65,6 +126,9 @@ const maxWorkersForRun = (name) => {
if (isCI && isMacOS) {
return 1;
}
if (name === "unit-isolated") {
return 1;
}
if (name === "extensions") {
return defaultExtensionsWorkers;
}

View File

@@ -212,11 +212,12 @@ describe("onboard (non-interactive): provider auth", () => {
it("stores xAI API key and sets default model", async () => {
await withOnboardEnv("openclaw-onboard-xai-", async ({ configPath, runtime }) => {
const rawKey = "xai-test-\r\nkey";
await runNonInteractive(
{
nonInteractive: true,
authChoice: "xai-api-key",
xaiApiKey: "xai-test-key",
xaiApiKey: rawKey,
skipHealth: true,
skipChannels: true,
skipSkills: true,
@@ -272,7 +273,8 @@ describe("onboard (non-interactive): provider auth", () => {
it("stores token auth profile", async () => {
await withOnboardEnv("openclaw-onboard-token-", async ({ configPath, runtime }) => {
const token = `sk-ant-oat01-${"a".repeat(80)}`;
const cleanToken = `sk-ant-oat01-${"a".repeat(80)}`;
const token = `${cleanToken.slice(0, 30)}\r${cleanToken.slice(30)}`;
await runNonInteractive(
{
@@ -301,7 +303,7 @@ describe("onboard (non-interactive): provider auth", () => {
expect(profile?.type).toBe("token");
if (profile?.type === "token") {
expect(profile.provider).toBe("anthropic");
expect(profile.token).toBe(token);
expect(profile.token).toBe(cleanToken);
}
});
}, 60_000);

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): 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 cleanToken = `sk-ant-oat01-${"a".repeat(80)}`;
const token = `${cleanToken.slice(0, 30)}\r${cleanToken.slice(30)}`;
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(cleanToken);
}
} 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-\r\nkey",
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

@@ -19,6 +19,7 @@ const ensureSystemdUserLingerInteractive = vi.hoisted(() => vi.fn(async () => {}
const isSystemdUserServiceAvailable = vi.hoisted(() => vi.fn(async () => true));
const ensureControlUiAssetsBuilt = vi.hoisted(() => vi.fn(async () => ({ ok: true })));
const runTui = vi.hoisted(() => vi.fn(async () => {}));
const setupOnboardingShellCompletion = vi.hoisted(() => vi.fn(async () => {}));
vi.mock("../commands/onboard-channels.js", () => ({
setupChannels,
@@ -73,6 +74,10 @@ vi.mock("../tui/tui.js", () => ({
runTui,
}));
vi.mock("./onboarding.completion.js", () => ({
setupOnboardingShellCompletion,
}));
describe("runOnboardingWizard", () => {
it("exits when config is invalid", async () => {
readConfigFileSnapshot.mockResolvedValueOnce({