mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-16 15:43:57 -06:00
test(qa): consolidate gateway child fixtures (#118508)
This commit is contained in:
committed by
GitHub
parent
f153cf3a92
commit
f6341f2fba
@@ -1,7 +1,7 @@
|
||||
import { spawn } from "node:child_process";
|
||||
// Qa Lab tests cover gateway child plugin behavior.
|
||||
import { EventEmitter, once } from "node:events";
|
||||
import { lstat, mkdir, mkdtemp, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import { lstat, mkdir, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { Writable } from "node:stream";
|
||||
@@ -35,16 +35,12 @@ vi.mock("./node-exec.js", () => ({
|
||||
resolveQaNodeExecPath: resolveQaNodeExecPathMock,
|
||||
}));
|
||||
|
||||
const cleanups: Array<() => Promise<void>> = [];
|
||||
const tempDirs = createTempDirHarness();
|
||||
|
||||
afterEach(async () => {
|
||||
fetchWithSsrFGuardMock.mockReset();
|
||||
resolveQaNodeExecPathMock.mockReset();
|
||||
qaTempPathState.preferredTmpDir = process.env.TMPDIR || "/tmp";
|
||||
while (cleanups.length > 0) {
|
||||
await cleanups.pop()?.();
|
||||
}
|
||||
await tempDirs.cleanup();
|
||||
});
|
||||
|
||||
@@ -118,6 +114,17 @@ async function expectPathMissing(filePath: string): Promise<void> {
|
||||
throw new Error(`expected ${filePath} to be missing`);
|
||||
}
|
||||
|
||||
async function writeJsonFixture(filePath: string, value: unknown, space?: number) {
|
||||
await mkdir(path.dirname(filePath), { recursive: true });
|
||||
await writeFile(filePath, JSON.stringify(value, null, space), "utf8");
|
||||
}
|
||||
|
||||
async function writeTempProviderConfig(value: unknown) {
|
||||
const configPath = path.join(await tempDirs.makeTempDir("qa-provider-config-"), "openclaw.json");
|
||||
await writeJsonFixture(configPath, value);
|
||||
return configPath;
|
||||
}
|
||||
|
||||
describe("runQaGatewayCliCommand", () => {
|
||||
it("runs CLI commands with the Gateway fixture environment", async () => {
|
||||
const output = await testing.runQaGatewayCliCommand({
|
||||
@@ -396,10 +403,7 @@ describe("Gateway child fixture helpers", () => {
|
||||
|
||||
describe("buildQaRuntimeEnv", () => {
|
||||
it("cleans up temp QA gateway roots when node path resolution fails before startup", async () => {
|
||||
const tempParent = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-node-exec-fail-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempParent, { recursive: true, force: true });
|
||||
});
|
||||
const tempParent = await tempDirs.makeTempDir("qa-gateway-node-exec-fail-");
|
||||
qaTempPathState.preferredTmpDir = tempParent;
|
||||
resolveQaNodeExecPathMock.mockRejectedValueOnce(new Error("node missing"));
|
||||
|
||||
@@ -418,16 +422,8 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("reports command spawn errors instead of leaking unhandled child errors", async () => {
|
||||
const preferredTempParent = await mkdtemp(
|
||||
path.join(os.tmpdir(), "qa-gateway-default-spawn-fail-"),
|
||||
);
|
||||
const commandTempParent = await mkdtemp(
|
||||
path.join(os.tmpdir(), "qa-gateway-command-spawn-fail-"),
|
||||
);
|
||||
cleanups.push(async () => {
|
||||
await rm(preferredTempParent, { recursive: true, force: true });
|
||||
await rm(commandTempParent, { recursive: true, force: true });
|
||||
});
|
||||
const preferredTempParent = await tempDirs.makeTempDir("qa-gateway-default-spawn-fail-");
|
||||
const commandTempParent = await tempDirs.makeTempDir("qa-gateway-command-spawn-fail-");
|
||||
qaTempPathState.preferredTmpDir = preferredTempParent;
|
||||
const missingExecutable = path.join(commandTempParent, "missing-openclaw-node");
|
||||
|
||||
@@ -505,10 +501,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("preserves Codex CLI auth home for live frontier runs while sandboxing OpenClaw home", async () => {
|
||||
const hostHome = await mkdtemp(path.join(os.tmpdir(), "qa-host-home-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(hostHome, { recursive: true, force: true });
|
||||
});
|
||||
const hostHome = await tempDirs.makeTempDir("qa-host-home-");
|
||||
const codexHome = path.join(hostHome, ".codex");
|
||||
await mkdir(codexHome);
|
||||
|
||||
@@ -525,10 +518,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("forwards host HOME for live Claude CLI runs while keeping OpenClaw home sandboxed", async () => {
|
||||
const hostHome = await mkdtemp(path.join(os.tmpdir(), "qa-host-home-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(hostHome, { recursive: true, force: true });
|
||||
});
|
||||
const hostHome = await tempDirs.makeTempDir("qa-host-home-");
|
||||
|
||||
const env = buildQaRuntimeEnv({
|
||||
...createParams({
|
||||
@@ -544,10 +534,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("can forward host HOME for browser-backed QA runs while keeping OpenClaw home sandboxed", async () => {
|
||||
const hostHome = await mkdtemp(path.join(os.tmpdir(), "qa-host-home-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(hostHome, { recursive: true, force: true });
|
||||
});
|
||||
const hostHome = await tempDirs.makeTempDir("qa-host-home-");
|
||||
|
||||
const env = buildQaRuntimeEnv({
|
||||
...createParams({
|
||||
@@ -563,10 +550,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("preserves the live Anthropic key for live Claude CLI runs without writing it into config", async () => {
|
||||
const hostHome = await mkdtemp(path.join(os.tmpdir(), "qa-host-home-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(hostHome, { recursive: true, force: true });
|
||||
});
|
||||
const hostHome = await tempDirs.makeTempDir("qa-host-home-");
|
||||
|
||||
const env = buildQaRuntimeEnv({
|
||||
...createParams({
|
||||
@@ -585,10 +569,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("removes preserved Anthropic keys for live Claude CLI subscription runs", async () => {
|
||||
const hostHome = await mkdtemp(path.join(os.tmpdir(), "qa-host-home-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(hostHome, { recursive: true, force: true });
|
||||
});
|
||||
const hostHome = await tempDirs.makeTempDir("qa-host-home-");
|
||||
|
||||
const env = buildQaRuntimeEnv({
|
||||
...createParams({
|
||||
@@ -666,10 +647,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("re-scrubs blocked credentials in the spawned gateway child env", async () => {
|
||||
const tempParent = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-env-scrub-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempParent, { recursive: true, force: true });
|
||||
});
|
||||
const tempParent = await tempDirs.makeTempDir("qa-gateway-env-scrub-");
|
||||
qaTempPathState.preferredTmpDir = tempParent;
|
||||
const observedEnvPath = path.join(tempParent, "observed-env.json");
|
||||
const captureScript = [
|
||||
@@ -719,10 +697,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("requires an Anthropic key for live Claude CLI API-key mode", async () => {
|
||||
const hostHome = await mkdtemp(path.join(os.tmpdir(), "qa-host-home-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(hostHome, { recursive: true, force: true });
|
||||
});
|
||||
const hostHome = await tempDirs.makeTempDir("qa-host-home-");
|
||||
|
||||
expect(() =>
|
||||
buildQaRuntimeEnv({
|
||||
@@ -852,10 +827,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages a live Anthropic setup-token profile for isolated QA workers", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-setup-token-state-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-setup-token-state-");
|
||||
const token = `sk-ant-oat01-${"c".repeat(80)}`;
|
||||
|
||||
const cfg = await testing.stageQaLiveAnthropicSetupToken({
|
||||
@@ -879,10 +851,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages live env API-key profiles for isolated QA workers", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-api-key-state-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-live-api-key-state-");
|
||||
|
||||
const cfg = await testing.stageQaLiveApiKeyProfiles({
|
||||
cfg: {},
|
||||
@@ -910,10 +879,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages the OpenAI API-key fallback for live OpenAI QA workers", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-api-key-state-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-live-codex-api-key-state-");
|
||||
|
||||
const cfg = await testing.stageQaLiveApiKeyProfiles({
|
||||
cfg: {},
|
||||
@@ -948,10 +914,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages direct live OpenAI API-key aliases for isolated QA workers", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-direct-key-state-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-live-codex-direct-key-state-");
|
||||
|
||||
const cfg = await testing.stageQaLiveApiKeyProfiles({
|
||||
cfg: {},
|
||||
@@ -1059,10 +1022,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages configured OpenAI API keys for live QA runs", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-config-key-state-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-live-codex-config-key-state-");
|
||||
const cfg = await testing.stageQaLiveApiKeyProfiles({
|
||||
cfg: {
|
||||
models: {
|
||||
@@ -1104,10 +1064,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages configured OpenAI env secret refs for default OpenAI live QA runs", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-config-ref-state-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-live-codex-config-ref-state-");
|
||||
const env = {
|
||||
OPENCLAW_LIVE_CODEX_API_KEY: "qa-configured-env-ref-not-a-real-key",
|
||||
};
|
||||
@@ -1151,10 +1108,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages configured OpenAI env markers for live QA runs", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-live-codex-config-marker-state-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-live-codex-config-marker-state-");
|
||||
const cfg = await testing.stageQaLiveApiKeyProfiles({
|
||||
cfg: {
|
||||
models: {
|
||||
@@ -1219,10 +1173,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages placeholder mock auth profiles per agent dir so mock-openai runs can resolve credentials", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-mock-auth-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-mock-auth-");
|
||||
|
||||
const cfg = await testing.stageQaMockAuthProfiles({
|
||||
cfg: {},
|
||||
@@ -1256,10 +1207,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("stages mock profiles only for the requested agents and providers when callers override the defaults", async () => {
|
||||
const stateDir = await mkdtemp(path.join(os.tmpdir(), "qa-mock-auth-override-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(stateDir, { recursive: true, force: true });
|
||||
});
|
||||
const stateDir = await tempDirs.makeTempDir("qa-mock-auth-override-");
|
||||
|
||||
const cfg = await testing.stageQaMockAuthProfiles({
|
||||
cfg: {},
|
||||
@@ -1564,12 +1512,8 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("preserves only sanitized gateway debug artifacts", async () => {
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-preserve-src-"));
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-preserve-repo-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
const tempRoot = await tempDirs.makeTempDir("qa-gateway-preserve-src-");
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-gateway-preserve-repo-");
|
||||
|
||||
const stdoutLogPath = path.join(tempRoot, "gateway.stdout.log");
|
||||
const stderrLogPath = path.join(tempRoot, "gateway.stderr.log");
|
||||
@@ -1662,12 +1606,8 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("rejects preserved gateway artifacts that traverse symlinks", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-guard-repo-"));
|
||||
const outsideRoot = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-guard-outside-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
await rm(outsideRoot, { recursive: true, force: true });
|
||||
});
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-gateway-guard-repo-");
|
||||
const outsideRoot = await tempDirs.makeTempDir("qa-gateway-guard-outside-");
|
||||
await mkdir(path.join(repoRoot, ".artifacts"), { recursive: true });
|
||||
await symlink(outsideRoot, path.join(repoRoot, ".artifacts", "qa-e2e"), "dir");
|
||||
|
||||
@@ -1680,12 +1620,8 @@ describe("buildQaRuntimeEnv", () => {
|
||||
});
|
||||
|
||||
it("cleans startup temp roots when they are not preserved", async () => {
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-cleanup-src-"));
|
||||
const stagedRoot = await mkdtemp(path.join(os.tmpdir(), "qa-gateway-cleanup-stage-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
await rm(stagedRoot, { recursive: true, force: true });
|
||||
});
|
||||
const tempRoot = await tempDirs.makeTempDir("qa-gateway-cleanup-src-");
|
||||
const stagedRoot = await tempDirs.makeTempDir("qa-gateway-cleanup-stage-");
|
||||
|
||||
await writeFile(path.join(tempRoot, "openclaw.json"), "{}", "utf8");
|
||||
await writeFile(path.join(stagedRoot, "marker.txt"), "x", "utf8");
|
||||
@@ -1702,10 +1638,7 @@ describe("buildQaRuntimeEnv", () => {
|
||||
|
||||
describe("resolveQaControlUiRoot", () => {
|
||||
it("returns the built control ui root when repo assets exist", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-control-ui-root-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-control-ui-root-");
|
||||
const controlUiRoot = path.join(repoRoot, "dist", "control-ui");
|
||||
await mkdir(controlUiRoot, { recursive: true });
|
||||
await writeFile(path.join(controlUiRoot, "index.html"), "<html></html>", "utf8");
|
||||
@@ -1714,10 +1647,7 @@ describe("resolveQaControlUiRoot", () => {
|
||||
});
|
||||
|
||||
it("returns undefined when control ui is disabled or not built", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-control-ui-root-missing-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-control-ui-root-missing-");
|
||||
|
||||
expect(resolveQaControlUiRoot({ repoRoot })).toBeUndefined();
|
||||
expect(resolveQaControlUiRoot({ repoRoot, controlUiEnabled: false })).toBeUndefined();
|
||||
@@ -1726,28 +1656,16 @@ describe("resolveQaControlUiRoot", () => {
|
||||
|
||||
describe("qa bundled plugin dir", () => {
|
||||
it("prefers a built bundled plugin when present", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-root-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "qa-channel"), {
|
||||
recursive: true,
|
||||
});
|
||||
await writeFile(
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-bundled-root-");
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "dist", "extensions", "qa-channel", "package.json"),
|
||||
"{}",
|
||||
"utf8",
|
||||
{},
|
||||
);
|
||||
await mkdir(path.join(repoRoot, "dist-runtime", "extensions", "qa-channel"), {
|
||||
recursive: true,
|
||||
});
|
||||
await writeFile(
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "dist-runtime", "extensions", "qa-channel", "package.json"),
|
||||
"{}",
|
||||
"utf8",
|
||||
{},
|
||||
);
|
||||
await mkdir(path.join(repoRoot, "extensions", "qa-channel"), { recursive: true });
|
||||
await writeFile(path.join(repoRoot, "extensions", "qa-channel", "package.json"), "{}", "utf8");
|
||||
await writeJsonFixture(path.join(repoRoot, "extensions", "qa-channel", "package.json"), {});
|
||||
|
||||
expect(
|
||||
testing.resolveQaBundledPluginSourceDir({
|
||||
@@ -1758,12 +1676,8 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("falls back to the source bundled plugin when no built copy exists", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-source-root-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await mkdir(path.join(repoRoot, "extensions", "qa-channel"), { recursive: true });
|
||||
await writeFile(path.join(repoRoot, "extensions", "qa-channel", "package.json"), "{}", "utf8");
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-bundled-source-root-");
|
||||
await writeJsonFixture(path.join(repoRoot, "extensions", "qa-channel", "package.json"), {});
|
||||
|
||||
expect(
|
||||
testing.resolveQaBundledPluginSourceDir({
|
||||
@@ -1774,22 +1688,14 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("resolves bundled plugins by manifest id when the directory name differs", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-manifest-id-root-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "kimi-coding"), {
|
||||
recursive: true,
|
||||
});
|
||||
await writeFile(
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-bundled-manifest-id-root-");
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "dist", "extensions", "kimi-coding", "openclaw.plugin.json"),
|
||||
JSON.stringify({ id: "kimi", providers: ["kimi"] }),
|
||||
"utf8",
|
||||
{ id: "kimi", providers: ["kimi"] },
|
||||
);
|
||||
await writeFile(
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "dist", "extensions", "kimi-coding", "package.json"),
|
||||
"{}",
|
||||
"utf8",
|
||||
{},
|
||||
);
|
||||
|
||||
expect(
|
||||
@@ -1801,27 +1707,19 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("uses a source bundled plugin when the built copy is missing CLI metadata", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-cli-metadata-root-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "memory-core"), { recursive: true });
|
||||
await writeFile(
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-bundled-cli-metadata-root-");
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "dist", "extensions", "memory-core", "package.json"),
|
||||
"{}",
|
||||
"utf8",
|
||||
{},
|
||||
);
|
||||
await writeFile(
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "dist", "extensions", "memory-core", "openclaw.plugin.json"),
|
||||
JSON.stringify({ id: "memory-core", kind: "memory" }),
|
||||
"utf8",
|
||||
{ id: "memory-core", kind: "memory" },
|
||||
);
|
||||
await mkdir(path.join(repoRoot, "extensions", "memory-core"), { recursive: true });
|
||||
await writeFile(path.join(repoRoot, "extensions", "memory-core", "package.json"), "{}", "utf8");
|
||||
await writeFile(
|
||||
await writeJsonFixture(path.join(repoRoot, "extensions", "memory-core", "package.json"), {});
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "extensions", "memory-core", "openclaw.plugin.json"),
|
||||
JSON.stringify({ id: "memory-core", kind: "memory" }),
|
||||
"utf8",
|
||||
{ id: "memory-core", kind: "memory" },
|
||||
);
|
||||
await writeFile(
|
||||
path.join(repoRoot, "extensions", "memory-core", "cli-metadata.ts"),
|
||||
@@ -1838,10 +1736,7 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("creates a scoped bundled plugin tree for allowed plugins plus always-allowed runtime facades", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-scope-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-bundled-scope-");
|
||||
await writeFile(
|
||||
path.join(repoRoot, "package.json"),
|
||||
JSON.stringify(
|
||||
@@ -1895,10 +1790,7 @@ describe("qa bundled plugin dir", () => {
|
||||
"utf8",
|
||||
);
|
||||
await writeFile(path.join(repoRoot, "dist", "shared-chunk-abc123.js"), "export {};\n", "utf8");
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-target-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
});
|
||||
const tempRoot = await tempDirs.makeTempDir("qa-bundled-target-");
|
||||
|
||||
const { bundledPluginsDir, stagedRoot } = await testing.createQaBundledPluginsDir({
|
||||
repoRoot,
|
||||
@@ -1957,10 +1849,7 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("preserves dist-runtime-only root chunks when dist also exists", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-mixed-runtime-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-bundled-mixed-runtime-");
|
||||
await writeFile(
|
||||
path.join(repoRoot, "package.json"),
|
||||
JSON.stringify({ name: "openclaw", type: "module" }, null, 2),
|
||||
@@ -1990,10 +1879,7 @@ describe("qa bundled plugin dir", () => {
|
||||
['import { marker } from "../../runtime-chunk.js";', "export { marker };", ""].join("\n"),
|
||||
"utf8",
|
||||
);
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-mixed-target-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
});
|
||||
const tempRoot = await tempDirs.makeTempDir("qa-bundled-mixed-target-");
|
||||
|
||||
const { bundledPluginsDir } = await testing.createQaBundledPluginsDir({
|
||||
repoRoot,
|
||||
@@ -2033,19 +1919,13 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("rejects invalid bundled plugin ids before staging paths are built", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-invalid-id-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-bundled-invalid-id-");
|
||||
await writeFile(
|
||||
path.join(repoRoot, "package.json"),
|
||||
JSON.stringify({ name: "openclaw", type: "module" }, null, 2),
|
||||
"utf8",
|
||||
);
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-invalid-target-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
});
|
||||
const tempRoot = await tempDirs.makeTempDir("qa-bundled-invalid-target-");
|
||||
|
||||
await expect(
|
||||
testing.createQaBundledPluginsDir({
|
||||
@@ -2075,14 +1955,8 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("stages source-only bundled plugins into a repo-like runtime root with node_modules", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-source-stage-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
const fakeDepStoreRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-source-store-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(fakeDepStoreRoot, { recursive: true, force: true });
|
||||
});
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-bundled-source-stage-");
|
||||
const fakeDepStoreRoot = await tempDirs.makeTempDir("qa-bundled-source-store-");
|
||||
await writeFile(
|
||||
path.join(repoRoot, "package.json"),
|
||||
JSON.stringify(
|
||||
@@ -2136,10 +2010,7 @@ describe("qa bundled plugin dir", () => {
|
||||
);
|
||||
await mkdir(path.join(repoRoot, "node_modules"), { recursive: true });
|
||||
await symlink(fakeDepPackageDir, path.join(repoRoot, "node_modules", "fake-dep"), "dir");
|
||||
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "qa-bundled-source-target-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(tempRoot, { recursive: true, force: true });
|
||||
});
|
||||
const tempRoot = await tempDirs.makeTempDir("qa-bundled-source-target-");
|
||||
|
||||
const { bundledPluginsDir, stagedRoot } = await testing.createQaBundledPluginsDir({
|
||||
repoRoot,
|
||||
@@ -2175,19 +2046,14 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("maps cli backend provider ids to their owning bundled plugin ids", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-plugin-owner-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "openai"), { recursive: true });
|
||||
await writeFile(
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-plugin-owner-");
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "dist", "extensions", "openai", "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
{
|
||||
id: "openai",
|
||||
providers: ["openai", "openai"],
|
||||
cliBackends: ["codex-cli"],
|
||||
}),
|
||||
"utf8",
|
||||
},
|
||||
);
|
||||
|
||||
await expect(
|
||||
@@ -2199,19 +2065,14 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("maps configured OpenAI Responses provider aliases to the OpenAI plugin", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-plugin-owner-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await mkdir(path.join(repoRoot, "dist", "extensions", "openai"), { recursive: true });
|
||||
await writeFile(
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-plugin-owner-");
|
||||
await writeJsonFixture(
|
||||
path.join(repoRoot, "dist", "extensions", "openai", "openclaw.plugin.json"),
|
||||
JSON.stringify({
|
||||
{
|
||||
id: "openai",
|
||||
providers: ["openai"],
|
||||
cliBackends: ["codex-cli"],
|
||||
}),
|
||||
"utf8",
|
||||
},
|
||||
);
|
||||
|
||||
await expect(
|
||||
@@ -2241,44 +2102,33 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("copies selected live provider configs from the host config", async () => {
|
||||
const configPath = path.join(
|
||||
await mkdtemp(path.join(os.tmpdir(), "qa-provider-config-")),
|
||||
"openclaw.json",
|
||||
);
|
||||
cleanups.push(async () => {
|
||||
await rm(path.dirname(configPath), { recursive: true, force: true });
|
||||
});
|
||||
await writeFile(
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
models: {
|
||||
providers: {
|
||||
"custom-openai": {
|
||||
baseUrl: "https://api.example.test/v1",
|
||||
api: "openai-responses",
|
||||
models: [
|
||||
{
|
||||
id: "model-a",
|
||||
name: "model-a",
|
||||
api: "openai-responses",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
],
|
||||
},
|
||||
ignored: {
|
||||
baseUrl: "https://ignored.example.test/v1",
|
||||
api: "openai-responses",
|
||||
models: [],
|
||||
},
|
||||
const configPath = await writeTempProviderConfig({
|
||||
models: {
|
||||
providers: {
|
||||
"custom-openai": {
|
||||
baseUrl: "https://api.example.test/v1",
|
||||
api: "openai-responses",
|
||||
models: [
|
||||
{
|
||||
id: "model-a",
|
||||
name: "model-a",
|
||||
api: "openai-responses",
|
||||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
|
||||
contextWindow: 128_000,
|
||||
maxTokens: 4096,
|
||||
},
|
||||
],
|
||||
},
|
||||
ignored: {
|
||||
baseUrl: "https://ignored.example.test/v1",
|
||||
api: "openai-responses",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const overrides = await testing.readQaLiveProviderConfigOverrides({
|
||||
providerIds: ["custom-openai"],
|
||||
@@ -2290,29 +2140,18 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("copies OpenAI auth-only live provider configs for default OpenAI runs", async () => {
|
||||
const configPath = path.join(
|
||||
await mkdtemp(path.join(os.tmpdir(), "qa-provider-config-")),
|
||||
"openclaw.json",
|
||||
);
|
||||
cleanups.push(async () => {
|
||||
await rm(path.dirname(configPath), { recursive: true, force: true });
|
||||
});
|
||||
await writeFile(
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
apiKey: {
|
||||
source: "env",
|
||||
id: "OPENCLAW_LIVE_CODEX_API_KEY",
|
||||
},
|
||||
const configPath = await writeTempProviderConfig({
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
apiKey: {
|
||||
source: "env",
|
||||
id: "OPENCLAW_LIVE_CODEX_API_KEY",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const overrides = await testing.readQaLiveProviderConfigOverrides({
|
||||
providerIds: ["openai"],
|
||||
@@ -2328,28 +2167,17 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("omits empty base URLs without dropping provider configs that inherit auth", async () => {
|
||||
const configPath = path.join(
|
||||
await mkdtemp(path.join(os.tmpdir(), "qa-provider-config-")),
|
||||
"openclaw.json",
|
||||
);
|
||||
cleanups.push(async () => {
|
||||
await rm(path.dirname(configPath), { recursive: true, force: true });
|
||||
});
|
||||
await writeFile(
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "",
|
||||
api: "openai-responses",
|
||||
models: [],
|
||||
},
|
||||
const configPath = await writeTempProviderConfig({
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "",
|
||||
api: "openai-responses",
|
||||
models: [],
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const overrides = await testing.readQaLiveProviderConfigOverrides({
|
||||
providerIds: ["openai"],
|
||||
@@ -2361,31 +2189,20 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("does not copy OpenAI provider configs for custom OpenAI-compatible runs", async () => {
|
||||
const configPath = path.join(
|
||||
await mkdtemp(path.join(os.tmpdir(), "qa-provider-config-")),
|
||||
"openclaw.json",
|
||||
);
|
||||
cleanups.push(async () => {
|
||||
await rm(path.dirname(configPath), { recursive: true, force: true });
|
||||
});
|
||||
await writeFile(
|
||||
configPath,
|
||||
JSON.stringify({
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://proxy.example.test/v1",
|
||||
models: [],
|
||||
apiKey: {
|
||||
source: "env",
|
||||
id: "OPENCLAW_LIVE_CODEX_API_KEY",
|
||||
},
|
||||
const configPath = await writeTempProviderConfig({
|
||||
models: {
|
||||
providers: {
|
||||
openai: {
|
||||
baseUrl: "https://proxy.example.test/v1",
|
||||
models: [],
|
||||
apiKey: {
|
||||
source: "env",
|
||||
id: "OPENCLAW_LIVE_CODEX_API_KEY",
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
"utf8",
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
const overrides = await testing.readQaLiveProviderConfigOverrides({
|
||||
providerIds: ["openai"],
|
||||
@@ -2396,29 +2213,16 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("raises the QA runtime host version to the highest allowed plugin floor", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-runtime-version-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await writeFile(
|
||||
path.join(repoRoot, "package.json"),
|
||||
JSON.stringify({ version: "2026.4.7-1" }),
|
||||
"utf8",
|
||||
);
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-runtime-version-");
|
||||
await writeJsonFixture(path.join(repoRoot, "package.json"), { version: "2026.4.7-1" });
|
||||
const bundledRoot = path.join(repoRoot, "extensions");
|
||||
await mkdir(path.join(bundledRoot, "qa-channel"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(bundledRoot, "qa-channel", "package.json"),
|
||||
JSON.stringify({ openclaw: { install: { minHostVersion: ">=2026.4.8" } } }),
|
||||
"utf8",
|
||||
);
|
||||
await writeJsonFixture(path.join(bundledRoot, "qa-channel", "package.json"), {
|
||||
openclaw: { install: { minHostVersion: ">=2026.4.8" } },
|
||||
});
|
||||
|
||||
await mkdir(path.join(bundledRoot, "memory-core"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(bundledRoot, "memory-core", "package.json"),
|
||||
JSON.stringify({ openclaw: { install: { minHostVersion: ">=2026.4.7" } } }),
|
||||
"utf8",
|
||||
);
|
||||
await writeJsonFixture(path.join(bundledRoot, "memory-core", "package.json"), {
|
||||
openclaw: { install: { minHostVersion: ">=2026.4.7" } },
|
||||
});
|
||||
|
||||
await expect(
|
||||
testing.resolveQaRuntimeHostVersion({
|
||||
@@ -2429,28 +2233,15 @@ describe("qa bundled plugin dir", () => {
|
||||
});
|
||||
|
||||
it("includes always-allowed runtime facade plugins when raising the QA runtime host version", async () => {
|
||||
const repoRoot = await mkdtemp(path.join(os.tmpdir(), "qa-runtime-version-runtime-facade-"));
|
||||
cleanups.push(async () => {
|
||||
await rm(repoRoot, { recursive: true, force: true });
|
||||
});
|
||||
await writeFile(
|
||||
path.join(repoRoot, "package.json"),
|
||||
JSON.stringify({ version: "2026.4.7-1" }),
|
||||
"utf8",
|
||||
);
|
||||
const repoRoot = await tempDirs.makeTempDir("qa-runtime-version-runtime-facade-");
|
||||
await writeJsonFixture(path.join(repoRoot, "package.json"), { version: "2026.4.7-1" });
|
||||
const bundledRoot = path.join(repoRoot, "extensions");
|
||||
await mkdir(path.join(bundledRoot, "qa-channel"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(bundledRoot, "qa-channel", "package.json"),
|
||||
JSON.stringify({ openclaw: { install: { minHostVersion: ">=2026.4.8" } } }),
|
||||
"utf8",
|
||||
);
|
||||
await mkdir(path.join(bundledRoot, "image-generation-core"), { recursive: true });
|
||||
await writeFile(
|
||||
path.join(bundledRoot, "image-generation-core", "package.json"),
|
||||
JSON.stringify({ openclaw: { install: { minHostVersion: ">=2026.4.9" } } }),
|
||||
"utf8",
|
||||
);
|
||||
await writeJsonFixture(path.join(bundledRoot, "qa-channel", "package.json"), {
|
||||
openclaw: { install: { minHostVersion: ">=2026.4.8" } },
|
||||
});
|
||||
await writeJsonFixture(path.join(bundledRoot, "image-generation-core", "package.json"), {
|
||||
openclaw: { install: { minHostVersion: ">=2026.4.9" } },
|
||||
});
|
||||
|
||||
await expect(
|
||||
testing.resolveQaRuntimeHostVersion({
|
||||
|
||||
Reference in New Issue
Block a user