mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
* fix(test): load compiled-subprocess declarations at collection The first load of a compiled-subprocess declaration in a Vitest invocation prepares the whole compiled worker generation (tens of seconds warm, minutes cold). 387 test files first reached a declaration through an await import() inside a test body or hook, so their first test or hook absorbed that preparation and could time out. Move those loads to collection at their owner: static subject imports, static imports in the shared helpers that owned the lazy load, and a named side-effect preload (src/test-utils/prepare-compiled-subprocesses.ts) for suites that re-import their subject per test or whose first load happens in production code they call. plugin-test-runtime keeps the host-capability fixture lazy so its other consumers do not start loading that graph. Document the rule in docs/help/testing/writing-tests.md. 335 of the 387 files now load their first declaration before collection ends; 52 remain (48 extension tests needing an SDK preload, one package test, three files over the line cap). * fix(test): retain npm install fixture reuse Preserve the existing failedSpawn reuse after merging main so the collection-time preload stays within the line-cap ratchet. Assertions and import ordering are unchanged. * fix(test): keep memory-core facade cold-import assertion meaningful The static subject import ran before beforeEach reset the loader mock, so the cold-import assertion passed vacuously. Restore the in-test subject imports after mock setup and preload the compiled-subprocess declaration during collection instead.
206 lines
7.7 KiB
TypeScript
206 lines
7.7 KiB
TypeScript
import "../src/test-utils/prepare-compiled-subprocesses.js";
|
|
import fs from "node:fs/promises";
|
|
import path from "node:path";
|
|
import { withTempHome } from "openclaw/plugin-sdk/test-env";
|
|
import { assert, describe, expect, it } from "vitest";
|
|
import { runBuiltCli } from "./cli-json-stdout.test-support.js";
|
|
|
|
describe("cli json stdout contract", () => {
|
|
it.each([
|
|
{
|
|
name: "search with a leaf JSON flag",
|
|
args: ["skills", "search", "fixture", "--json"],
|
|
message: "ClawHub /api/v1/search failed (400): offline fixture",
|
|
},
|
|
{
|
|
name: "search with a parent JSON flag",
|
|
args: ["skills", "--json", "search", "fixture"],
|
|
message: "ClawHub /api/v1/search failed (400): offline fixture",
|
|
},
|
|
{
|
|
name: "list with a leaf JSON flag",
|
|
args: ["skills", "list", "--agent", "", "--json"],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "list with a parent JSON flag",
|
|
args: ["skills", "--json", "list", "--agent", ""],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "info with a leaf JSON flag",
|
|
args: ["skills", "info", "fixture", "--agent", "", "--json"],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "info with a parent JSON flag",
|
|
args: ["skills", "--json", "info", "fixture", "--agent", ""],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "check with a leaf JSON flag",
|
|
args: ["skills", "check", "--agent", "", "--json"],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "check with a parent JSON flag",
|
|
args: ["skills", "--json", "check", "--agent", ""],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "the default report after its agent flag",
|
|
args: ["skills", "--agent", "", "--json"],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "the default report before its agent flag",
|
|
args: ["skills", "--json", "--agent", ""],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "list with a configured remote Gateway missing its URL",
|
|
args: ["skills", "list", "--json"],
|
|
message: "gateway remote mode misconfigured: gateway.remote.url missing",
|
|
remoteMissing: true,
|
|
},
|
|
...[
|
|
{ name: "the default report", args: ["skills", "--json"] },
|
|
{ name: "list", args: ["skills", "list", "--json"] },
|
|
{ name: "info", args: ["skills", "info", "fixture", "--json"] },
|
|
{ name: "check", args: ["skills", "check", "--json"] },
|
|
{ name: "curator status", args: ["skills", "curator", "status", "--json"] },
|
|
{ name: "curator pin", args: ["skills", "curator", "pin", "fixture", "--json"] },
|
|
{ name: "curator unpin", args: ["skills", "curator", "unpin", "fixture", "--json"] },
|
|
{ name: "curator restore", args: ["skills", "curator", "restore", "fixture", "--json"] },
|
|
{
|
|
name: "workshop apply",
|
|
args: ["skills", "workshop", "apply", "fixture-proposal", "--json"],
|
|
},
|
|
].map(({ name, args }) => ({
|
|
name: `${name} after an explicit environment Gateway fails`,
|
|
args,
|
|
message: "AUTOQA_SELECTED_GATEWAY_FAILURE",
|
|
explicitGateway: true,
|
|
})),
|
|
{
|
|
name: "retired curator mutation",
|
|
args: ["skills", "curator", "pin", "missing-skill", "--json"],
|
|
message:
|
|
"Skill lifecycle curation is retired. The weekly collection review manages the skill collection; pin, unpin, and restore no longer exist.",
|
|
},
|
|
{
|
|
name: "retired curator mutation with parent JSON",
|
|
args: ["skills", "curator", "--json", "pin", "missing-skill"],
|
|
message:
|
|
"Skill lifecycle curation is retired. The weekly collection review manages the skill collection; pin, unpin, and restore no longer exist.",
|
|
},
|
|
{
|
|
name: "workshop workspace validation with parent JSON",
|
|
args: ["skills", "--json", "workshop", "list", "--agent", ""],
|
|
message: "--agent must not be blank",
|
|
},
|
|
{
|
|
name: "workshop mutation",
|
|
args: ["skills", "workshop", "reject", "missing-proposal", "--json"],
|
|
message: "Skill proposal not found: missing-proposal",
|
|
},
|
|
{
|
|
name: "workshop inspection",
|
|
args: ["skills", "workshop", "inspect", "missing-proposal", "--json"],
|
|
message: "Skill proposal not found: missing-proposal",
|
|
},
|
|
])("returns one canonical JSON document when skills $name fails", async (testCase) => {
|
|
await withTempHome(
|
|
async (tempHome) => {
|
|
const configPath = path.join(tempHome, "missing-openclaw.json");
|
|
if ("remoteMissing" in testCase) {
|
|
await fs.writeFile(configPath, JSON.stringify({ gateway: { mode: "remote" } }));
|
|
}
|
|
const preload = `data:text/javascript,${encodeURIComponent(
|
|
[
|
|
'globalThis.fetch = async () => new Response("offline fixture", { status: 400 });',
|
|
...("explicitGateway" in testCase
|
|
? [
|
|
'import net from "node:net";',
|
|
'net.Socket.prototype.connect = function () { throw new Error("AUTOQA_SELECTED_GATEWAY_FAILURE"); };',
|
|
]
|
|
: []),
|
|
].join("\n"),
|
|
)}`;
|
|
const result = runBuiltCli(
|
|
tempHome,
|
|
testCase.args,
|
|
{
|
|
OPENCLAW_STATE_DIR: path.join(tempHome, "isolated-state"),
|
|
OPENCLAW_CONFIG_PATH: configPath,
|
|
OPENCLAW_GATEWAY_PORT: "1",
|
|
...("explicitGateway" in testCase
|
|
? {
|
|
OPENCLAW_GATEWAY_URL: "ws://127.0.0.1:9",
|
|
OPENCLAW_GATEWAY_TOKEN: "fixture-token",
|
|
}
|
|
: {}),
|
|
},
|
|
{ execArgv: [`--import=${preload}`] },
|
|
);
|
|
const message =
|
|
"remoteMissing" in testCase
|
|
? [
|
|
testCase.message,
|
|
`Config: ${configPath}`,
|
|
"Fix: set gateway.remote.url, or set gateway.mode=local.",
|
|
].join("\n")
|
|
: testCase.message;
|
|
|
|
expect(result.status, result.stderr).toBe(1);
|
|
expect(JSON.parse(result.stdout)).toEqual({
|
|
ok: false,
|
|
error: {
|
|
type: "cli_error",
|
|
message,
|
|
},
|
|
});
|
|
expect(result.stderr).toContain(message);
|
|
expect(result.stderr.length).toBeLessThan(2_048);
|
|
},
|
|
{ prefix: "openclaw-skills-json-failure-e2e-" },
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
{ name: "off", debug: "0", includesCause: false },
|
|
{ name: "on", debug: "1", includesCause: true },
|
|
])("keeps skills search nested causes behind debug mode ($name)", async (testCase) => {
|
|
await withTempHome(
|
|
async (tempHome) => {
|
|
// Match the selected runtime's SyntaxError for the same malformed response.
|
|
let syntaxError: unknown;
|
|
try {
|
|
JSON.parse("not-json");
|
|
} catch (error) {
|
|
syntaxError = error;
|
|
}
|
|
assert(syntaxError instanceof SyntaxError);
|
|
const preload = `data:text/javascript,${encodeURIComponent(
|
|
'globalThis.fetch = async () => new Response("not-json", { status: 200 });',
|
|
)}`;
|
|
const result = runBuiltCli(
|
|
tempHome,
|
|
["skills", "search", "fixture"],
|
|
{
|
|
OPENCLAW_DEBUG: testCase.debug,
|
|
OPENCLAW_STATE_DIR: path.join(tempHome, "isolated-state"),
|
|
OPENCLAW_CONFIG_PATH: path.join(tempHome, "missing-openclaw.json"),
|
|
},
|
|
{ execArgv: [`--import=${preload}`] },
|
|
);
|
|
|
|
expect(result.status, result.stderr).toBe(1);
|
|
expect(result.stdout).toBe("");
|
|
expect(result.stderr).toContain("ClawHub /api/v1/search returned malformed JSON");
|
|
expect(result.stderr.includes(syntaxError.message)).toBe(testCase.includesCause);
|
|
},
|
|
{ prefix: "openclaw-skills-human-failure-e2e-" },
|
|
);
|
|
});
|
|
});
|