mirror of
https://github.com/farcasclaudiu/openclaw.git
synced 2026-06-28 21:01:43 +03:00
test: speed up test suite and trim redundant onboarding tests
This commit is contained in:
+80
-16
@@ -5,19 +5,24 @@ import path from "node:path";
|
|||||||
|
|
||||||
const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
const pnpm = process.platform === "win32" ? "pnpm.cmd" : "pnpm";
|
||||||
|
|
||||||
const runs = [
|
const unitIsolatedFiles = [
|
||||||
{
|
"src/plugins/loader.test.ts",
|
||||||
name: "unit",
|
"src/plugins/tools.optional.test.ts",
|
||||||
args: ["vitest", "run", "--config", "vitest.unit.config.ts"],
|
"src/agents/session-tool-result-guard.tool-result-persist-hook.test.ts",
|
||||||
},
|
"src/security/fix.test.ts",
|
||||||
{
|
"src/utils.test.ts",
|
||||||
name: "extensions",
|
"src/auto-reply/tool-meta.test.ts",
|
||||||
args: ["vitest", "run", "--config", "vitest.extensions.config.ts"],
|
"src/commands/auth-choice.test.ts",
|
||||||
},
|
"src/media/store.header-ext.test.ts",
|
||||||
{
|
"src/browser/server.covers-additional-endpoint-branches.test.ts",
|
||||||
name: "gateway",
|
"src/browser/server.post-tabs-open-profile-unknown-returns-404.test.ts",
|
||||||
args: ["vitest", "run", "--config", "vitest.gateway.config.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();
|
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 isMacOS = process.platform === "darwin" || process.env.RUNNER_OS === "macOS";
|
||||||
const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows";
|
const isWindows = process.platform === "win32" || process.env.RUNNER_OS === "Windows";
|
||||||
const isWindowsCi = isCI && isWindows;
|
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 shardOverride = Number.parseInt(process.env.OPENCLAW_TEST_SHARDS ?? "", 10);
|
||||||
const shardCount = isWindowsCi
|
const shardCount = isWindowsCi
|
||||||
? Number.isFinite(shardOverride) && shardOverride > 1
|
? Number.isFinite(shardOverride) && shardOverride > 1
|
||||||
@@ -40,12 +101,12 @@ const passthroughArgs =
|
|||||||
const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10);
|
const overrideWorkers = Number.parseInt(process.env.OPENCLAW_TEST_WORKERS ?? "", 10);
|
||||||
const resolvedOverride =
|
const resolvedOverride =
|
||||||
Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
|
Number.isFinite(overrideWorkers) && overrideWorkers > 0 ? overrideWorkers : null;
|
||||||
// Keep gateway serial by default to avoid resource contention with unit/extensions.
|
// Keep gateway serial on Windows CI and CI by default; run in parallel locally
|
||||||
// Allow explicit opt-in parallel runs on non-Windows CI/local when requested.
|
// for lower wall-clock time. CI can opt in via OPENCLAW_TEST_PARALLEL_GATEWAY=1.
|
||||||
const keepGatewaySerial =
|
const keepGatewaySerial =
|
||||||
isWindowsCi ||
|
isWindowsCi ||
|
||||||
process.env.OPENCLAW_TEST_SERIAL_GATEWAY === "1" ||
|
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 parallelRuns = keepGatewaySerial ? runs.filter((entry) => entry.name !== "gateway") : runs;
|
||||||
const serialRuns = keepGatewaySerial ? runs.filter((entry) => entry.name === "gateway") : [];
|
const serialRuns = keepGatewaySerial ? runs.filter((entry) => entry.name === "gateway") : [];
|
||||||
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
|
const localWorkers = Math.max(4, Math.min(16, os.cpus().length));
|
||||||
@@ -65,6 +126,9 @@ const maxWorkersForRun = (name) => {
|
|||||||
if (isCI && isMacOS) {
|
if (isCI && isMacOS) {
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
if (name === "unit-isolated") {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
if (name === "extensions") {
|
if (name === "extensions") {
|
||||||
return defaultExtensionsWorkers;
|
return defaultExtensionsWorkers;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,11 +212,12 @@ describe("onboard (non-interactive): provider auth", () => {
|
|||||||
|
|
||||||
it("stores xAI API key and sets default model", async () => {
|
it("stores xAI API key and sets default model", async () => {
|
||||||
await withOnboardEnv("openclaw-onboard-xai-", async ({ configPath, runtime }) => {
|
await withOnboardEnv("openclaw-onboard-xai-", async ({ configPath, runtime }) => {
|
||||||
|
const rawKey = "xai-test-\r\nkey";
|
||||||
await runNonInteractive(
|
await runNonInteractive(
|
||||||
{
|
{
|
||||||
nonInteractive: true,
|
nonInteractive: true,
|
||||||
authChoice: "xai-api-key",
|
authChoice: "xai-api-key",
|
||||||
xaiApiKey: "xai-test-key",
|
xaiApiKey: rawKey,
|
||||||
skipHealth: true,
|
skipHealth: true,
|
||||||
skipChannels: true,
|
skipChannels: true,
|
||||||
skipSkills: true,
|
skipSkills: true,
|
||||||
@@ -272,7 +273,8 @@ describe("onboard (non-interactive): provider auth", () => {
|
|||||||
|
|
||||||
it("stores token auth profile", async () => {
|
it("stores token auth profile", async () => {
|
||||||
await withOnboardEnv("openclaw-onboard-token-", async ({ configPath, runtime }) => {
|
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(
|
await runNonInteractive(
|
||||||
{
|
{
|
||||||
@@ -301,7 +303,7 @@ describe("onboard (non-interactive): provider auth", () => {
|
|||||||
expect(profile?.type).toBe("token");
|
expect(profile?.type).toBe("token");
|
||||||
if (profile?.type === "token") {
|
if (profile?.type === "token") {
|
||||||
expect(profile.provider).toBe("anthropic");
|
expect(profile.provider).toBe("anthropic");
|
||||||
expect(profile.token).toBe(token);
|
expect(profile.token).toBe(cleanToken);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, 60_000);
|
}, 60_000);
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
@@ -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);
|
|
||||||
});
|
|
||||||
@@ -19,6 +19,7 @@ const ensureSystemdUserLingerInteractive = vi.hoisted(() => vi.fn(async () => {}
|
|||||||
const isSystemdUserServiceAvailable = vi.hoisted(() => vi.fn(async () => true));
|
const isSystemdUserServiceAvailable = vi.hoisted(() => vi.fn(async () => true));
|
||||||
const ensureControlUiAssetsBuilt = vi.hoisted(() => vi.fn(async () => ({ ok: true })));
|
const ensureControlUiAssetsBuilt = vi.hoisted(() => vi.fn(async () => ({ ok: true })));
|
||||||
const runTui = vi.hoisted(() => vi.fn(async () => {}));
|
const runTui = vi.hoisted(() => vi.fn(async () => {}));
|
||||||
|
const setupOnboardingShellCompletion = vi.hoisted(() => vi.fn(async () => {}));
|
||||||
|
|
||||||
vi.mock("../commands/onboard-channels.js", () => ({
|
vi.mock("../commands/onboard-channels.js", () => ({
|
||||||
setupChannels,
|
setupChannels,
|
||||||
@@ -73,6 +74,10 @@ vi.mock("../tui/tui.js", () => ({
|
|||||||
runTui,
|
runTui,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("./onboarding.completion.js", () => ({
|
||||||
|
setupOnboardingShellCompletion,
|
||||||
|
}));
|
||||||
|
|
||||||
describe("runOnboardingWizard", () => {
|
describe("runOnboardingWizard", () => {
|
||||||
it("exits when config is invalid", async () => {
|
it("exits when config is invalid", async () => {
|
||||||
readConfigFileSnapshot.mockResolvedValueOnce({
|
readConfigFileSnapshot.mockResolvedValueOnce({
|
||||||
|
|||||||
Reference in New Issue
Block a user