openclaw / test /cli-json-stdout.plugins.e2e.test.ts
SaylorTwift's picture
SaylorTwift HF Staff
Add files using upload-large-folder tool
205131a verified
Raw
History Blame Contribute Delete
10.5 kB
import fs from "node:fs/promises";
import path from "node:path";
import { withTempHome } from "openclaw/plugin-sdk/test-env";
import { describe, expect, it } from "vitest";
import { runBuiltCli } from "./cli-json-stdout.test-support.js";
describe("cli json stdout contract", () => {
it.each([
{ name: "an ownerless explicit fleet", ownerSelected: false },
{ name: "an explicit system owner", ownerSelected: true },
])("preserves plugin diagnostic classifications for $name", async ({ ownerSelected }) => {
await withTempHome(
async (tempHome) => {
const configPath = path.join(tempHome, "openclaw.json");
const config = {
agents: {
ownership: "explicit",
entries: { alpha: {}, beta: {} },
...(ownerSelected ? { defaults: { systemAgent: { agentId: "alpha" } } } : {}),
},
plugins: { enabled: false },
};
const configText = JSON.stringify(config);
await fs.writeFile(configPath, configText);
const env = {
OPENCLAW_STATE_DIR: path.join(tempHome, "isolated-state"),
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
NO_COLOR: "1",
};
const list = runBuiltCli(tempHome, ["plugins", "list", "--json"], env, {
inheritEnvironment: false,
});
expect(list.error).toBeUndefined();
expect(list.signal).toBeNull();
expect(list.status, list.stderr).toBe(0);
const inventory = JSON.parse(list.stdout);
expect(inventory.workspaceScope).toBe(ownerSelected ? "selected" : "omitted");
const workspaceDiagnostic = inventory.diagnostics.find(
(entry: { code?: string }) => entry.code === "workspace-scope-omitted",
);
if (ownerSelected) {
expect(workspaceDiagnostic).toBeUndefined();
} else {
expect(workspaceDiagnostic).toMatchObject({
level: "warn",
code: "workspace-scope-omitted",
message: expect.stringContaining("Workspace plugin discovery was skipped"),
});
}
const doctorJson = runBuiltCli(tempHome, ["plugins", "doctor", "--json"], env, {
inheritEnvironment: false,
});
const doctorText = runBuiltCli(tempHome, ["plugins", "doctor"], env, {
inheritEnvironment: false,
});
for (const result of [doctorJson, doctorText]) {
expect(result.error).toBeUndefined();
expect(result.signal).toBeNull();
expect(result.status, result.stderr).toBe(ownerSelected ? 0 : 1);
}
expect(doctorJson.stdout).not.toContain("\u001B");
expect(doctorJson.stdout).not.toContain("\u0007");
const diagnostics = JSON.parse(doctorJson.stdout);
expect(diagnostics.ok).toBe(ownerSelected);
expect(await fs.readFile(configPath, "utf8")).toBe(configText);
if (ownerSelected) {
expect(diagnostics.diagnostics).not.toContainEqual(
expect.objectContaining({ code: "workspace-scope-omitted" }),
);
expect(doctorText.stdout).not.toContain("Workspace plugin discovery was skipped");
expect(doctorText.stdout).toContain("configuration checks passed");
} else {
expect(doctorText.stdout).toContain(workspaceDiagnostic.message);
expect(doctorText.stdout).not.toContain("configuration checks passed");
expect(diagnostics.diagnostics).toContainEqual(workspaceDiagnostic);
}
},
{ prefix: "openclaw-plugin-doctor-classification-e2e-" },
);
});
it.each([
{
name: "the search query is missing",
args: ["plugins", "search", "--json"],
message: "Usage: openclaw plugins search <query>",
},
{
name: "ClawHub transport fails",
args: ["plugins", "search", "fixture", "--json"],
message: "offline fixture",
},
])("returns one canonical JSON document when plugins $name", async (testCase) => {
await withTempHome(
async (tempHome) => {
const preload = `data:text/javascript,${encodeURIComponent(
'globalThis.fetch = async () => { throw new Error("offline fixture"); };',
)}`;
const result = runBuiltCli(tempHome, testCase.args, {
NODE_OPTIONS: `--import=${preload}`,
OPENCLAW_STATE_DIR: path.join(tempHome, "isolated-state"),
OPENCLAW_CONFIG_PATH: path.join(tempHome, "missing-openclaw.json"),
CLAWHUB_CONFIG_PATH: path.join(tempHome, "missing-clawhub.json"),
CLAWHUB_TOKEN: "",
CLAWHUB_AUTH_TOKEN: "",
});
expect(result.status, result.stderr).toBe(1);
expect(result.stdout, result.stderr).not.toBe("");
expect(result.stdout).not.toContain("\u001B");
expect(result.stdout).not.toContain("\u0007");
expect(JSON.parse(result.stdout)).toEqual({
ok: false,
error: {
type: "cli_error",
message: testCase.message,
},
});
expect(result.stderr).toContain(testCase.message);
},
{ prefix: "openclaw-plugins-json-failure-e2e-" },
);
});
it("keeps plugins search JSON failures clean through dual-TTY finalization", async () => {
await withTempHome(
async (tempHome) => {
const preload = `data:text/javascript,${encodeURIComponent(
'Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true }); Object.defineProperty(process.stderr, "isTTY", { value: true, configurable: true });',
)}`;
const result = runBuiltCli(tempHome, ["plugins", "search", "--json"], {
NODE_OPTIONS: `--import=${preload}`,
});
expect(result.status, result.stderr).toBe(1);
expect(JSON.parse(result.stdout)).toEqual({
ok: false,
error: {
type: "cli_error",
message: "Usage: openclaw plugins search <query>",
},
});
expect(result.stdout).not.toContain("\u001B");
expect(result.stdout).not.toContain("\u0007");
expect(result.stderr).toContain("Usage: openclaw plugins search <query>");
expect(result.stderr).toContain("\u001B[?25h");
},
{ prefix: "openclaw-plugins-json-tty-failure-e2e-" },
);
});
it.each([
{ name: "a missing local marketplace", source: "local" },
{
name: "a missing local marketplace through forced Commander",
source: "local",
commander: true,
},
{ name: "a missing local marketplace with dual TTYs", source: "local", tty: true },
{ name: "an unavailable Git marketplace", source: "git" },
{ name: "an unavailable Git marketplace with dual TTYs", source: "git", tty: true },
{ name: "a missing local marketplace in human mode", source: "local", human: true },
{ name: "an unavailable Git marketplace in human mode", source: "git", human: true },
])("keeps $name failures on the canonical marketplace output boundary", async (testCase) => {
await withTempHome(
async (tempHome) => {
const stateDir = path.join(tempHome, "isolated-state");
const configPath = path.join(tempHome, "missing-openclaw.json");
const source =
testCase.source === "git"
? "ssh://marketplace.invalid/openclaw/unavailable.git"
: path.join(tempHome, "missing-marketplace");
const ttyPreload = Buffer.from(
'Object.defineProperty(process.stdout, "isTTY", { value: true, configurable: true }); Object.defineProperty(process.stderr, "isTTY", { value: true, configurable: true });',
).toString("base64");
await expect(fs.stat(stateDir)).rejects.toMatchObject({ code: "ENOENT" });
const result = runBuiltCli(
tempHome,
["plugins", "marketplace", "list", source, ...("human" in testCase ? [] : ["--json"])],
{
GIT_SSH_COMMAND: `${JSON.stringify(process.execPath)} -e "process.exit(1)"`,
GIT_TERMINAL_PROMPT: "0",
OPENCLAW_CONFIG_PATH: configPath,
OPENCLAW_DISABLE_BUNDLED_PLUGINS: "1",
OPENCLAW_STATE_DIR: stateDir,
...("commander" in testCase ? { OPENCLAW_DISABLE_ROUTE_FIRST: "1" } : {}),
...("tty" in testCase
? { NODE_OPTIONS: `--import=data:text/javascript;base64,${ttyPreload}` }
: {}),
},
{ inheritEnvironment: false },
);
const expectedMessage =
testCase.source === "git"
? `failed to clone marketplace source ${source}:`
: `unsupported marketplace source: ${source}`;
expect(result.status, result.stderr).toBe(1);
expect(result.stderr).toContain(expectedMessage);
if ("human" in testCase) {
if (testCase.source === "git") {
expect(result.stdout).toContain(`Cloning marketplace source ${source}...`);
}
} else {
expect(result.stdout, result.stderr).not.toContain("\u001B");
expect(result.stdout, result.stderr).not.toContain("\u0007");
expect(result.stdout).not.toContain("Cloning marketplace source");
expect(JSON.parse(result.stdout)).toEqual({
ok: false,
error: {
type: "cli_error",
message:
testCase.source === "git"
? expect.stringContaining(expectedMessage)
: expectedMessage,
},
});
if ("tty" in testCase) {
expect(result.stderr).toContain("\u001B[?25h");
}
}
if (testCase.source === "git") {
expect(result.stderr).toContain("fatal: Could not read from remote repository.");
const clonePath = /Cloning into '([^']+)'\.\.\./u.exec(result.stderr)?.[1];
expect(clonePath).toBeDefined();
await expect(fs.stat(path.dirname(clonePath as string))).rejects.toMatchObject({
code: "ENOENT",
});
}
await expect(fs.stat(stateDir)).rejects.toMatchObject({ code: "ENOENT" });
await expect(fs.stat(configPath)).rejects.toMatchObject({ code: "ENOENT" });
await expect(fs.stat(path.join(tempHome, ".claude"))).rejects.toMatchObject({
code: "ENOENT",
});
},
{ prefix: "openclaw-marketplace-json-failure-e2e-" },
);
});
});