perf(test): streamline qa gateway child tests

This commit is contained in:
Peter Steinberger
2026-04-20 16:13:45 +01:00
parent df05668f8b
commit 98a5f737d7
4 changed files with 88 additions and 66 deletions

View File

@@ -1,4 +1,4 @@
import { spawn } from "node:child_process";
import { EventEmitter } from "node:events";
import { lstat, mkdir, mkdtemp, readFile, readdir, rm, symlink, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
@@ -456,45 +456,41 @@ describe("buildQaRuntimeEnv", () => {
});
it("force-stops gateway children that ignore the graceful signal", async () => {
const child = spawn(
process.execPath,
[
"-e",
[
"process.on('SIGTERM', () => {});",
"process.stdout.write('ready\\n');",
"setInterval(() => {}, 1000);",
].join(""),
],
const child = Object.assign(new EventEmitter(), {
pid: 12345,
exitCode: null as number | null,
signalCode: null as string | null,
kill: vi.fn((signal?: "SIGTERM" | "SIGKILL" | number) => {
if (signal === "SIGKILL") {
child.signalCode = "SIGKILL";
queueMicrotask(() => child.emit("exit"));
}
return true;
}),
});
const processKill = vi.spyOn(process, "kill").mockImplementation((_pid, signal) => {
if (signal === "SIGKILL") {
child.signalCode = "SIGKILL";
queueMicrotask(() => child.emit("exit"));
}
return true;
});
await __testing.stopQaGatewayChildProcessTree(
child as unknown as Parameters<typeof __testing.stopQaGatewayChildProcessTree>[0],
{
detached: process.platform !== "win32",
stdio: ["ignore", "pipe", "ignore"],
gracefulTimeoutMs: 1,
forceTimeoutMs: 10,
},
);
cleanups.push(async () => {
if (child.exitCode === null && child.signalCode === null) {
try {
if (process.platform === "win32") {
child.kill("SIGKILL");
} else if (child.pid) {
process.kill(-child.pid, "SIGKILL");
}
} catch {
// The child already exited.
}
}
});
await new Promise<void>((resolve, reject) => {
child.once("error", reject);
child.stdout?.once("data", () => resolve());
});
await __testing.stopQaGatewayChildProcessTree(child, {
gracefulTimeoutMs: 50,
forceTimeoutMs: 1_000,
});
if (process.platform === "win32") {
expect(child.kill).toHaveBeenCalledWith("SIGTERM");
expect(child.kill).toHaveBeenCalledWith("SIGKILL");
} else {
expect(processKill).toHaveBeenCalledWith(-12345, "SIGTERM");
expect(processKill).toHaveBeenCalledWith(-12345, "SIGKILL");
}
expect(child.exitCode !== null || child.signalCode !== null).toBe(true);
});

View File

@@ -1,11 +1,9 @@
import fs from "node:fs/promises";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import {
applyAuthProfileConfig,
upsertAuthProfile,
validateAnthropicSetupToken,
} from "openclaw/plugin-sdk/provider-auth";
import { resolveQaAgentAuthDir, writeQaAuthProfiles } from "../shared/auth-store.js";
export const QA_LIVE_ANTHROPIC_SETUP_TOKEN_ENV = "OPENCLAW_QA_LIVE_ANTHROPIC_SETUP_TOKEN";
export const QA_LIVE_SETUP_TOKEN_VALUE_ENV = "OPENCLAW_LIVE_SETUP_TOKEN_VALUE";
@@ -40,16 +38,15 @@ export async function stageQaLiveAnthropicSetupToken(params: {
if (!resolved) {
return params.cfg;
}
const agentDir = path.join(params.stateDir, "agents", "main", "agent");
await fs.mkdir(agentDir, { recursive: true });
upsertAuthProfile({
profileId: resolved.profileId,
credential: {
type: "token",
provider: "anthropic",
token: resolved.token,
await writeQaAuthProfiles({
agentDir: resolveQaAgentAuthDir({ stateDir: params.stateDir, agentId: "main" }),
profiles: {
[resolved.profileId]: {
type: "token",
provider: "anthropic",
token: resolved.token,
},
},
agentDir,
});
return applyAuthProfileConfig(params.cfg, {
profileId: resolved.profileId,

View File

@@ -0,0 +1,31 @@
import fs from "node:fs/promises";
import path from "node:path";
type QaAuthProfileCredential =
| {
type: "api_key";
provider: string;
key: string;
displayName?: string;
}
| {
type: "token";
provider: string;
token: string;
};
export function resolveQaAgentAuthDir(params: { stateDir: string; agentId: string }): string {
return path.join(params.stateDir, "agents", params.agentId, "agent");
}
export async function writeQaAuthProfiles(params: {
agentDir: string;
profiles: Record<string, QaAuthProfileCredential>;
}): Promise<void> {
await fs.mkdir(params.agentDir, { recursive: true });
await fs.writeFile(
path.join(params.agentDir, "auth-profiles.json"),
`${JSON.stringify({ version: 1, profiles: params.profiles }, null, 2)}\n`,
"utf8",
);
}

View File

@@ -1,7 +1,6 @@
import fs from "node:fs/promises";
import path from "node:path";
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-runtime";
import { applyAuthProfileConfig, upsertAuthProfile } from "openclaw/plugin-sdk/provider-auth";
import { applyAuthProfileConfig } from "openclaw/plugin-sdk/provider-auth-api-key";
import { resolveQaAgentAuthDir, writeQaAuthProfiles } from "./auth-store.js";
/** Providers the mock harness stages placeholder credentials for by default. */
export const QA_MOCK_AUTH_PROVIDERS = Object.freeze(["openai", "anthropic"] as const);
@@ -42,21 +41,20 @@ export async function stageQaMockAuthProfiles(params: {
const providers = [...new Set(params.providers ?? QA_MOCK_AUTH_PROVIDERS)];
let next = params.cfg;
for (const agentId of agentIds) {
const agentDir = path.join(params.stateDir, "agents", agentId, "agent");
await fs.mkdir(agentDir, { recursive: true });
for (const provider of providers) {
const profileId = buildQaMockProfileId(provider);
upsertAuthProfile({
profileId,
credential: {
type: "api_key",
provider,
key: "qa-mock-not-a-real-key",
displayName: `QA mock ${provider} credential`,
},
agentDir,
});
}
await writeQaAuthProfiles({
agentDir: resolveQaAgentAuthDir({ stateDir: params.stateDir, agentId }),
profiles: Object.fromEntries(
providers.map((provider) => [
buildQaMockProfileId(provider),
{
type: "api_key",
provider,
key: "qa-mock-not-a-real-key",
displayName: `QA mock ${provider} credential`,
},
]),
),
});
}
for (const provider of providers) {
next = applyAuthProfileConfig(next, {