mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
test: harden qa parity runtime staging
This commit is contained in:
@@ -9,7 +9,7 @@ describe("QA provider image generation config", () => {
|
||||
requiredPluginIds: ["qa-channel"],
|
||||
});
|
||||
|
||||
expect(patch.plugins.allow).toEqual(["memory-core", "qa-channel"]);
|
||||
expect(patch.plugins.allow).toEqual(["acpx", "memory-core", "qa-channel"]);
|
||||
expect(patch.agents.defaults.imageGenerationModel.primary).toBe("mock-openai/gpt-image-1");
|
||||
expect(patch.models?.providers["mock-openai"]?.baseUrl).toBe("http://127.0.0.1:44080/v1");
|
||||
});
|
||||
@@ -33,7 +33,7 @@ describe("QA provider image generation config", () => {
|
||||
});
|
||||
|
||||
expect(patch.plugins).toEqual({
|
||||
allow: ["memory-core", "openai", "qa-channel"],
|
||||
allow: ["acpx", "memory-core", "openai", "qa-channel"],
|
||||
entries: {
|
||||
openai: {
|
||||
enabled: true,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { QA_BASE_RUNTIME_PLUGIN_IDS } from "../qa-gateway-config.js";
|
||||
import type { QaProviderMode } from "./index.js";
|
||||
import { getQaProvider } from "./index.js";
|
||||
|
||||
@@ -45,7 +46,11 @@ export function buildQaImageGenerationConfigPatch(input: QaImageGenerationPatchI
|
||||
|
||||
return {
|
||||
plugins: {
|
||||
allow: uniqueNonEmpty(["memory-core", ...enabledPluginIds, ...input.requiredPluginIds]),
|
||||
allow: uniqueNonEmpty([
|
||||
...QA_BASE_RUNTIME_PLUGIN_IDS,
|
||||
...enabledPluginIds,
|
||||
...input.requiredPluginIds,
|
||||
]),
|
||||
...(enabledPluginIds.length > 0
|
||||
? {
|
||||
entries: Object.fromEntries(
|
||||
|
||||
@@ -58,12 +58,14 @@ describe("buildQaGatewayConfig", () => {
|
||||
expect(cfg.models?.providers?.openai?.request).toEqual({ allowPrivateNetwork: true });
|
||||
expect(cfg.models?.providers?.anthropic?.baseUrl).toBe("http://127.0.0.1:44080");
|
||||
expect(cfg.models?.providers?.anthropic?.request).toEqual({ allowPrivateNetwork: true });
|
||||
expect(cfg.plugins?.allow).toEqual(["memory-core", "qa-channel"]);
|
||||
expect(cfg.plugins?.allow).toEqual(["acpx", "memory-core", "qa-channel"]);
|
||||
expect(cfg.plugins?.entries?.acpx).toEqual({ enabled: false });
|
||||
expect(cfg.plugins?.entries?.["memory-core"]).toEqual({ enabled: true });
|
||||
expect(cfg.plugins?.entries?.["qa-channel"]).toEqual({ enabled: true });
|
||||
expect(cfg.plugins?.entries?.openai).toBeUndefined();
|
||||
expect(cfg.gateway?.reload?.deferralTimeoutMs).toBe(1_000);
|
||||
expect(cfg.tools?.profile).toBe("coding");
|
||||
expect(cfg.agents?.list?.[0]?.tools?.profile).toBe("coding");
|
||||
expect(cfg.channels?.["qa-channel"]).toMatchObject({
|
||||
enabled: true,
|
||||
baseUrl: "http://127.0.0.1:43124",
|
||||
@@ -94,7 +96,7 @@ describe("buildQaGatewayConfig", () => {
|
||||
expect(cfg.models?.providers?.anthropic?.models.map((model) => model.id)).toContain(
|
||||
"claude-opus-4-6",
|
||||
);
|
||||
expect(cfg.plugins?.allow).toEqual(["memory-core"]);
|
||||
expect(cfg.plugins?.allow).toEqual(["acpx", "memory-core"]);
|
||||
});
|
||||
|
||||
it("can wire AIMock as a separate mock provider lane", () => {
|
||||
@@ -131,7 +133,7 @@ describe("buildQaGatewayConfig", () => {
|
||||
transportConfig: {},
|
||||
});
|
||||
|
||||
expect(cfg.plugins?.allow).toEqual(["memory-core"]);
|
||||
expect(cfg.plugins?.allow).toEqual(["acpx", "memory-core"]);
|
||||
expect(cfg.plugins?.entries?.["qa-channel"]).toBeUndefined();
|
||||
expect(cfg.channels?.["qa-channel"]).toBeUndefined();
|
||||
});
|
||||
@@ -147,7 +149,7 @@ describe("buildQaGatewayConfig", () => {
|
||||
...createQaChannelTransportParams(),
|
||||
});
|
||||
|
||||
expect(cfg.plugins?.allow).toEqual(["memory-core", "active-memory", "qa-channel"]);
|
||||
expect(cfg.plugins?.allow).toEqual(["acpx", "memory-core", "active-memory", "qa-channel"]);
|
||||
expect(cfg.plugins?.entries?.["active-memory"]).toEqual({ enabled: true });
|
||||
});
|
||||
|
||||
@@ -167,7 +169,7 @@ describe("buildQaGatewayConfig", () => {
|
||||
expect(getPrimaryModel(cfg.agents?.defaults?.model)).toBe("openai/gpt-5.4");
|
||||
expect(getPrimaryModel(cfg.agents?.list?.[0]?.model)).toBe("openai/gpt-5.4");
|
||||
expect(cfg.models).toBeUndefined();
|
||||
expect(cfg.plugins?.allow).toEqual(["memory-core", "openai", "qa-channel"]);
|
||||
expect(cfg.plugins?.allow).toEqual(["acpx", "memory-core", "openai", "qa-channel"]);
|
||||
expect(cfg.plugins?.entries?.openai).toEqual({ enabled: true });
|
||||
expect(cfg.agents?.defaults?.models?.["openai/gpt-5.4"]).toEqual({
|
||||
params: { transport: "sse", openaiWsWarmup: false, fastMode: true },
|
||||
@@ -187,7 +189,13 @@ describe("buildQaGatewayConfig", () => {
|
||||
...createQaChannelTransportParams(),
|
||||
});
|
||||
|
||||
expect(cfg.plugins?.allow).toEqual(["memory-core", "anthropic", "google", "qa-channel"]);
|
||||
expect(cfg.plugins?.allow).toEqual([
|
||||
"acpx",
|
||||
"memory-core",
|
||||
"anthropic",
|
||||
"google",
|
||||
"qa-channel",
|
||||
]);
|
||||
expect(cfg.plugins?.entries?.anthropic).toEqual({ enabled: true });
|
||||
expect(cfg.plugins?.entries?.google).toEqual({ enabled: true });
|
||||
expect(cfg.plugins?.entries?.openai).toBeUndefined();
|
||||
@@ -209,7 +217,7 @@ describe("buildQaGatewayConfig", () => {
|
||||
});
|
||||
|
||||
expect(getPrimaryModel(cfg.agents?.defaults?.model)).toBe("codex-cli/test-model");
|
||||
expect(cfg.plugins?.allow).toEqual(["memory-core", "openai", "qa-channel"]);
|
||||
expect(cfg.plugins?.allow).toEqual(["acpx", "memory-core", "openai", "qa-channel"]);
|
||||
expect(cfg.plugins?.entries?.openai).toEqual({ enabled: true });
|
||||
expect(cfg.plugins?.entries?.["codex-cli"]).toBeUndefined();
|
||||
});
|
||||
@@ -249,7 +257,7 @@ describe("buildQaGatewayConfig", () => {
|
||||
|
||||
expect(cfg.models?.mode).toBe("merge");
|
||||
expect(cfg.models?.providers?.["custom-openai"]?.api).toBe("openai-responses");
|
||||
expect(cfg.plugins?.allow).toEqual(["memory-core", "openai", "qa-channel"]);
|
||||
expect(cfg.plugins?.allow).toEqual(["acpx", "memory-core", "openai", "qa-channel"]);
|
||||
});
|
||||
|
||||
it("can set a QA default thinking level for judge turns", () => {
|
||||
|
||||
@@ -20,6 +20,8 @@ export const DEFAULT_QA_CONTROL_UI_ALLOWED_ORIGINS = Object.freeze([
|
||||
"http://localhost:43124",
|
||||
]);
|
||||
|
||||
export const QA_BASE_RUNTIME_PLUGIN_IDS = Object.freeze(["acpx", "memory-core"]);
|
||||
|
||||
export function mergeQaControlUiAllowedOrigins(extraOrigins?: string[]) {
|
||||
const normalizedExtra = (extraOrigins ?? [])
|
||||
.map((origin) => origin.trim())
|
||||
@@ -96,7 +98,9 @@ export function buildQaGatewayConfig(params: {
|
||||
const transportPluginEntries = Object.fromEntries(
|
||||
transportPluginIds.map((pluginId) => [pluginId, { enabled: true }]),
|
||||
);
|
||||
const allowedPlugins = [...new Set(["memory-core", ...selectedPluginIds, ...transportPluginIds])];
|
||||
const allowedPlugins = [
|
||||
...new Set([...QA_BASE_RUNTIME_PLUGIN_IDS, ...selectedPluginIds, ...transportPluginIds]),
|
||||
];
|
||||
const resolveModelParams = (modelRef: string) =>
|
||||
provider.resolveModelParams({
|
||||
modelRef,
|
||||
@@ -114,6 +118,9 @@ export function buildQaGatewayConfig(params: {
|
||||
allow: allowedPlugins,
|
||||
entries: {
|
||||
acpx: {
|
||||
// The parity gateway stages a clean bundled-plugin tree. Keep the
|
||||
// runtime backend plugin in the allowlist so this disabled entry is
|
||||
// not mistaken for stale config when optional bundles are pruned.
|
||||
enabled: false,
|
||||
},
|
||||
"memory-core": {
|
||||
@@ -174,6 +181,9 @@ export function buildQaGatewayConfig(params: {
|
||||
subagents: {
|
||||
allowAgents: ["*"],
|
||||
},
|
||||
tools: {
|
||||
profile: "coding",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
@@ -23,6 +23,11 @@ const VISIBLE_REPLY_LEAK_PATTERNS = [
|
||||
/\bnot inventing status\b/i,
|
||||
];
|
||||
|
||||
const TOOL_BACKED_FAILURE_PATTERNS = [
|
||||
/\btool\s+[a-z0-9_.-]+\s+not found\b/i,
|
||||
/^status:\s*blocked\b/im,
|
||||
];
|
||||
|
||||
export function extractQaVisibleReplyLeakText(text: string): string | undefined {
|
||||
const trimmed = text.trim();
|
||||
if (!trimmed) {
|
||||
@@ -47,5 +52,8 @@ export function extractQaFailureReplyText(text: string): string | undefined {
|
||||
if (visibleReplyLeak) {
|
||||
return visibleReplyLeak;
|
||||
}
|
||||
if (TOOL_BACKED_FAILURE_PATTERNS.some((pattern) => pattern.test(trimmed))) {
|
||||
return trimmed;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
@@ -79,6 +79,24 @@ describe("qa suite transport helpers", () => {
|
||||
await expect(pending).rejects.toThrow("checking thread context");
|
||||
});
|
||||
|
||||
it("fails success-only waitForOutboundMessage calls when a tool-backed scenario reports missing tools", async () => {
|
||||
const state = createQaBusState();
|
||||
const pending = waitForOutboundMessage(
|
||||
state,
|
||||
(candidate) => candidate.text.includes("Status: complete"),
|
||||
5_000,
|
||||
);
|
||||
|
||||
state.addOutboundMessage({
|
||||
to: "dm:qa-operator",
|
||||
text: "Read: AGENT.md\nEvidence snippet: Tool read not found\nStatus: blocked",
|
||||
senderId: "openclaw",
|
||||
senderName: "OpenClaw QA",
|
||||
});
|
||||
|
||||
await expect(pending).rejects.toThrow("Tool read not found");
|
||||
});
|
||||
|
||||
it("fails raw scenario waitForCondition calls when a classified failure reply arrives", async () => {
|
||||
const state = createQaBusState();
|
||||
const waitForCondition = createScenarioWaitForCondition(state);
|
||||
|
||||
Reference in New Issue
Block a user