mirror of
https://github.com/openclaw/clawhub.git
synced 2026-07-21 19:25:25 -04:00
214 lines
6.4 KiB
TypeScript
214 lines
6.4 KiB
TypeScript
/* @vitest-environment node */
|
|
|
|
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import {
|
|
buildWorkerRun,
|
|
loadDevWorkerEnv,
|
|
parseArgs,
|
|
resolveEnabledWorkers,
|
|
resolveRunnableWorkers,
|
|
validateWorkerEnv,
|
|
WORKERS,
|
|
} from "./dev-workers";
|
|
|
|
const tempDirs: string[] = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(tempDirs.splice(0).map((dir) => rm(dir, { recursive: true, force: true })));
|
|
});
|
|
|
|
async function tempDir() {
|
|
const dir = await mkdtemp(join(tmpdir(), "clawhub-dev-workers-test-"));
|
|
tempDirs.push(dir);
|
|
return dir;
|
|
}
|
|
|
|
describe("dev-workers", () => {
|
|
it("parses worker selection and polling options", () => {
|
|
const options = parseArgs([
|
|
"--once",
|
|
"--interval-ms",
|
|
"250",
|
|
"--workers",
|
|
"security-scan,skill-card",
|
|
"--skip",
|
|
"skill-card",
|
|
"--batch-limit",
|
|
"2",
|
|
"--max-jobs",
|
|
"4",
|
|
"--max-runtime-minutes",
|
|
"3",
|
|
]);
|
|
|
|
expect(options.once).toBe(true);
|
|
expect(options.intervalMs).toBe(250);
|
|
expect(options.workers).toEqual(["security-scan", "skill-card"]);
|
|
expect(options.skip).toEqual(["skill-card"]);
|
|
expect(options.batchLimit).toBe(2);
|
|
expect(options.maxJobs).toBe(4);
|
|
expect(options.maxRuntimeMinutes).toBe(3);
|
|
});
|
|
|
|
it("defaults to a fast local polling interval", () => {
|
|
expect(parseArgs([]).intervalMs).toBe(5000);
|
|
});
|
|
|
|
it("loads .env.local without overriding shell env and aliases VITE_CONVEX_URL", async () => {
|
|
const root = await tempDir();
|
|
await writeFile(
|
|
join(root, ".env.local"),
|
|
["VITE_CONVEX_URL=https://dev.example", "SECURITY_SCAN_WORKER_TOKEN=from-file"].join("\n"),
|
|
"utf8",
|
|
);
|
|
const env: NodeJS.ProcessEnv = {
|
|
SECURITY_SCAN_WORKER_TOKEN: "from-shell",
|
|
};
|
|
|
|
const loaded = await loadDevWorkerEnv({ cwd: root, env });
|
|
|
|
expect(loaded.envFile).toBe(join(root, ".env.local"));
|
|
expect(env.CONVEX_URL).toBe("https://dev.example");
|
|
expect(env.SECURITY_SCAN_WORKER_TOKEN).toBe("from-shell");
|
|
});
|
|
|
|
it("aliases VITE_CONVEX_URL from shell env when .env.local is absent", async () => {
|
|
const root = await tempDir();
|
|
const env: NodeJS.ProcessEnv = {
|
|
VITE_CONVEX_URL: "https://dev.example",
|
|
SECURITY_SCAN_WORKER_TOKEN: "from-shell",
|
|
};
|
|
|
|
const loaded = await loadDevWorkerEnv({ cwd: root, env });
|
|
|
|
expect(loaded).toEqual({ envFile: null, loaded: ["CONVEX_URL"] });
|
|
expect(env.CONVEX_URL).toBe("https://dev.example");
|
|
expect(validateWorkerEnv(WORKERS[0]!, env)).toEqual([]);
|
|
});
|
|
|
|
it("resolves enabled workers from include and skip lists", () => {
|
|
const selected = resolveEnabledWorkers({
|
|
workers: ["security-scan", "skill-card"],
|
|
skip: ["skill-card"],
|
|
});
|
|
|
|
expect(selected.map((worker) => worker.id)).toEqual(["security-scan"]);
|
|
});
|
|
|
|
it("skips the default Skill Card worker when NVIDIA tooling is absent", async () => {
|
|
const root = await tempDir();
|
|
const selected = resolveEnabledWorkers({
|
|
workers: [],
|
|
skip: [],
|
|
});
|
|
|
|
const resolved = resolveRunnableWorkers(selected, parseArgs([]), {
|
|
cwd: root,
|
|
env: {},
|
|
});
|
|
|
|
expect(resolved.workers.map((worker) => worker.id)).toEqual([]);
|
|
expect(resolved.skipped).toEqual([
|
|
expect.objectContaining({
|
|
workerId: "security-scan",
|
|
reason: expect.stringContaining("CLAWHUB_ALLOW_LOCAL_CODEX_SCAN=1"),
|
|
}),
|
|
expect.objectContaining({
|
|
workerId: "skill-card",
|
|
reason: expect.stringContaining("CLAWHUB_ALLOW_LOCAL_CODEX_SCAN=1"),
|
|
}),
|
|
]);
|
|
});
|
|
|
|
it("keeps Codex workers with explicit local opt-in", async () => {
|
|
const root = await tempDir();
|
|
const toolDir = join(root, "nvidia-tooling");
|
|
await mkdir(join(toolDir, "AI Transparency Card Automation", "scripts"), { recursive: true });
|
|
await writeFile(
|
|
join(toolDir, "AI Transparency Card Automation", "scripts", "render_card.py"),
|
|
"print('render')\n",
|
|
"utf8",
|
|
);
|
|
const selected = resolveEnabledWorkers({
|
|
workers: [],
|
|
skip: [],
|
|
});
|
|
|
|
const resolved = resolveRunnableWorkers(selected, parseArgs(["--nvidia-tool-dir", toolDir]), {
|
|
cwd: root,
|
|
env: { CLAWHUB_ALLOW_LOCAL_CODEX_SCAN: "1" },
|
|
});
|
|
|
|
expect(resolved.workers.map((worker) => worker.id)).toEqual(["security-scan", "skill-card"]);
|
|
expect(resolved.skipped).toEqual([]);
|
|
});
|
|
|
|
it("keeps the Skill Card worker when an explicit NVIDIA tool checkout exists", async () => {
|
|
const root = await tempDir();
|
|
const toolDir = join(root, "nvidia-tooling");
|
|
await mkdir(join(toolDir, "AI Transparency Card Automation", "scripts"), { recursive: true });
|
|
await writeFile(
|
|
join(toolDir, "AI Transparency Card Automation", "scripts", "render_card.py"),
|
|
"print('render')\n",
|
|
"utf8",
|
|
);
|
|
const selected = resolveEnabledWorkers({
|
|
workers: ["skill-card"],
|
|
skip: [],
|
|
});
|
|
|
|
const resolved = resolveRunnableWorkers(
|
|
selected,
|
|
parseArgs(["--workers", "skill-card", "--nvidia-tool-dir", toolDir]),
|
|
{ cwd: root, env: { CLAWHUB_ALLOW_LOCAL_CODEX_SCAN: "1" } },
|
|
);
|
|
|
|
expect(resolved.workers.map((worker) => worker.id)).toEqual(["skill-card"]);
|
|
expect(resolved.skipped).toEqual([]);
|
|
});
|
|
|
|
it("rejects unknown worker ids", () => {
|
|
expect(() => resolveEnabledWorkers({ workers: ["bogus"], skip: [] })).toThrow(
|
|
"Unknown worker: bogus",
|
|
);
|
|
});
|
|
|
|
it("builds canonical worker script invocations", () => {
|
|
const worker = WORKERS.find((candidate) => candidate.id === "skill-card");
|
|
if (!worker) throw new Error("skill-card worker missing");
|
|
|
|
const run = buildWorkerRun(worker, {
|
|
batchLimit: 2,
|
|
maxJobs: 3,
|
|
maxRuntimeMinutes: 4,
|
|
leaseMinutes: 5,
|
|
nvidiaToolDir: null,
|
|
});
|
|
|
|
expect(run.command).toBe("bun");
|
|
expect(run.args).toEqual([
|
|
"scripts/skill-cards/run-skill-card-worker.ts",
|
|
"--batch-limit",
|
|
"2",
|
|
"--max-jobs",
|
|
"3",
|
|
"--max-runtime-minutes",
|
|
"4",
|
|
"--lease-minutes",
|
|
"5",
|
|
]);
|
|
});
|
|
|
|
it("reports missing required env for enabled workers", () => {
|
|
const worker = WORKERS.find((candidate) => candidate.id === "security-scan");
|
|
if (!worker) throw new Error("security-scan worker missing");
|
|
|
|
expect(validateWorkerEnv(worker, { CONVEX_URL: "https://dev.example" })).toEqual([
|
|
"SECURITY_SCAN_WORKER_TOKEN",
|
|
]);
|
|
});
|
|
});
|