Files
openclaw/extensions/qa-lab/src/suite-runtime-flow.test.ts
2026-04-25 18:05:28 +01:00

269 lines
9.3 KiB
TypeScript

import { describe, expect, it, vi } from "vitest";
const createQaScenarioRuntimeApi = vi.hoisted(() => vi.fn());
const waitForOutboundMessage = vi.hoisted(() => vi.fn());
const waitForTransportOutboundMessage = vi.hoisted(() => vi.fn());
const waitForChannelOutboundMessage = vi.hoisted(() => vi.fn());
const waitForNoOutbound = vi.hoisted(() => vi.fn());
const waitForNoTransportOutbound = vi.hoisted(() => vi.fn());
const recentOutboundSummary = vi.hoisted(() => vi.fn());
const formatConversationTranscript = vi.hoisted(() => vi.fn());
const readTransportTranscript = vi.hoisted(() => vi.fn());
const formatTransportTranscript = vi.hoisted(() => vi.fn());
const fetchJson = vi.hoisted(() => vi.fn());
const waitForGatewayHealthy = vi.hoisted(() => vi.fn());
const waitForTransportReady = vi.hoisted(() => vi.fn());
const waitForQaChannelReady = vi.hoisted(() => vi.fn());
const patchConfig = vi.hoisted(() => vi.fn());
const applyConfig = vi.hoisted(() => vi.fn());
const readConfigSnapshot = vi.hoisted(() => vi.fn());
const waitForConfigRestartSettle = vi.hoisted(() => vi.fn());
const createSession = vi.hoisted(() => vi.fn());
const readEffectiveTools = vi.hoisted(() => vi.fn());
const readSkillStatus = vi.hoisted(() => vi.fn());
const readRawQaSessionStore = vi.hoisted(() => vi.fn());
const runQaCli = vi.hoisted(() => vi.fn());
const extractMediaPathFromText = vi.hoisted(() => vi.fn());
const resolveGeneratedImagePath = vi.hoisted(() => vi.fn());
const startAgentRun = vi.hoisted(() => vi.fn());
const waitForAgentRun = vi.hoisted(() => vi.fn());
const listCronJobs = vi.hoisted(() => vi.fn());
const findManagedDreamingCronJob = vi.hoisted(() => vi.fn());
const waitForCronRunCompletion = vi.hoisted(() => vi.fn());
const readDoctorMemoryStatus = vi.hoisted(() => vi.fn());
const forceMemoryIndex = vi.hoisted(() => vi.fn());
const findSkill = vi.hoisted(() => vi.fn());
const writeWorkspaceSkill = vi.hoisted(() => vi.fn());
const callPluginToolsMcp = vi.hoisted(() => vi.fn());
const runAgentPrompt = vi.hoisted(() => vi.fn());
const ensureImageGenerationConfigured = vi.hoisted(() => vi.fn());
const handleQaAction = vi.hoisted(() => vi.fn());
const extractQaToolPayload = vi.hoisted(() => vi.fn());
const browserRequest = vi.hoisted(() => vi.fn());
const waitForBrowserReady = vi.hoisted(() => vi.fn());
const browserOpenTab = vi.hoisted(() => vi.fn());
const browserSnapshot = vi.hoisted(() => vi.fn());
const browserAct = vi.hoisted(() => vi.fn());
const webOpenPage = vi.hoisted(() => vi.fn(async () => ({ pageId: "page-1" })));
const webWait = vi.hoisted(() => vi.fn());
const webType = vi.hoisted(() => vi.fn());
const webSnapshot = vi.hoisted(() => vi.fn());
const webEvaluate = vi.hoisted(() => vi.fn());
const hasDiscoveryLabels = vi.hoisted(() => vi.fn());
const reportsDiscoveryScopeLeak = vi.hoisted(() => vi.fn());
const reportsMissingDiscoveryFiles = vi.hoisted(() => vi.fn());
const hasModelSwitchContinuityEvidence = vi.hoisted(() => vi.fn());
const qaChannelPlugin = vi.hoisted(() => ({ id: "qa-channel" }));
vi.mock("./scenario-runtime-api.js", () => ({
createQaScenarioRuntimeApi,
}));
vi.mock("./suite-runtime-transport.js", () => ({
waitForOutboundMessage,
waitForTransportOutboundMessage,
waitForChannelOutboundMessage,
waitForNoOutbound,
waitForNoTransportOutbound,
recentOutboundSummary,
formatConversationTranscript,
readTransportTranscript,
formatTransportTranscript,
}));
vi.mock("./suite-runtime-gateway.js", () => ({
fetchJson,
waitForGatewayHealthy,
waitForTransportReady,
waitForQaChannelReady,
waitForConfigRestartSettle,
patchConfig,
applyConfig,
readConfigSnapshot,
}));
vi.mock("./suite-runtime-agent.js", () => ({
createSession,
readEffectiveTools,
readSkillStatus,
readRawQaSessionStore,
runQaCli,
extractMediaPathFromText,
resolveGeneratedImagePath,
startAgentRun,
waitForAgentRun,
listCronJobs,
findManagedDreamingCronJob,
readDoctorMemoryStatus,
forceMemoryIndex,
findSkill,
writeWorkspaceSkill,
callPluginToolsMcp,
runAgentPrompt,
ensureImageGenerationConfigured,
handleQaAction,
}));
vi.mock("./browser-runtime.js", () => ({
callQaBrowserRequest: browserRequest,
waitForQaBrowserReady: waitForBrowserReady,
qaBrowserOpenTab: browserOpenTab,
qaBrowserSnapshot: browserSnapshot,
qaBrowserAct: browserAct,
}));
vi.mock("./web-runtime.js", () => ({
qaWebOpenPage: webOpenPage,
qaWebWait: webWait,
qaWebType: webType,
qaWebSnapshot: webSnapshot,
qaWebEvaluate: webEvaluate,
}));
vi.mock("./cron-run-wait.js", () => ({
waitForCronRunCompletion,
}));
vi.mock("./discovery-eval.js", () => ({
hasDiscoveryLabels,
reportsDiscoveryScopeLeak,
reportsMissingDiscoveryFiles,
}));
vi.mock("./extract-tool-payload.js", () => ({
extractQaToolPayload,
}));
vi.mock("./model-switch-eval.js", () => ({
hasModelSwitchContinuityEvidence,
}));
vi.mock("./runtime-api.js", () => ({
qaChannelPlugin,
}));
import { createQaSuiteScenarioFlowApi } from "./suite-runtime-flow.js";
import type { QaSuiteRuntimeEnv } from "./suite-runtime-types.js";
describe("qa suite runtime flow", () => {
it("wires the split suite runtime deps into the scenario runtime api", async () => {
const env = {
lab: { baseUrl: "http://127.0.0.1:4444" },
webSessionIds: new Set<string>(),
gateway: {} as QaSuiteRuntimeEnv["gateway"],
transport: {
id: "qa-channel",
label: "QA Channel",
accountId: "qa-channel",
waitReady: vi.fn(),
createGatewayConfig: vi.fn(),
buildAgentDelivery: vi.fn(),
requiredPluginIds: [],
handleAction: vi.fn(),
createReportNotes: vi.fn(),
state: {
reset: vi.fn(),
getSnapshot: vi.fn(),
addInboundMessage: vi.fn(),
addOutboundMessage: vi.fn(),
readMessage: vi.fn(),
searchMessages: vi.fn(),
waitFor: vi.fn(),
},
capabilities: {
waitForOutboundMessage: vi.fn(),
waitForCondition: vi.fn(),
getNormalizedMessageState: vi.fn(),
resetNormalizedMessageState: vi.fn(),
sendInboundMessage: vi.fn(),
injectOutboundMessage: vi.fn(),
readNormalizedMessage: vi.fn(),
executeGenericAction: vi.fn(),
waitForReady: vi.fn(),
assertNoFailureReplies: vi.fn(),
},
},
repoRoot: "/repo",
providerMode: "mock-openai",
primaryModel: "openai/gpt-5.5",
alternateModel: "openai/gpt-5.5-mini",
mock: null,
cfg: {} as QaSuiteRuntimeEnv["cfg"],
} satisfies Parameters<typeof createQaSuiteScenarioFlowApi>[0]["env"];
const scenario = {
id: "session-memory-ranking",
title: "Session memory ranking",
sourcePath: "qa/scenarios/session-memory-ranking.md",
surface: "qa-channel",
objective: "test",
successCriteria: ["test"],
execution: {
kind: "flow" as const,
config: { expected: "value" },
flow: { steps: [] },
},
};
const runScenario = vi.fn();
const splitModelRef = vi.fn();
const formatErrorMessage = vi.fn();
const liveTurnTimeoutMs = vi.fn();
const resolveQaLiveTurnTimeoutMs = vi.fn();
createQaScenarioRuntimeApi.mockReturnValue({ api: "ok" });
const result = createQaSuiteScenarioFlowApi({
env,
scenario,
runScenario,
splitModelRef,
formatErrorMessage,
liveTurnTimeoutMs,
resolveQaLiveTurnTimeoutMs,
constants: {
imageUnderstandingPngBase64: "small",
imageUnderstandingLargePngBase64: "large",
imageUnderstandingValidPngBase64: "valid",
},
});
expect(result).toEqual({ api: "ok" });
expect(createQaScenarioRuntimeApi).toHaveBeenCalledTimes(1);
const call = createQaScenarioRuntimeApi.mock.calls[0]?.[0] as {
env: typeof env;
scenario: typeof scenario;
deps: {
runScenario: typeof runScenario;
waitForQaChannelReady: typeof waitForQaChannelReady;
waitForOutboundMessage: typeof waitForOutboundMessage;
findManagedDreamingCronJob: typeof findManagedDreamingCronJob;
forceMemoryIndex: typeof forceMemoryIndex;
runAgentPrompt: typeof runAgentPrompt;
qaChannelPlugin: typeof qaChannelPlugin;
webOpenPage: (params: { url: string }) => Promise<unknown>;
};
constants: {
imageUnderstandingPngBase64: string;
imageUnderstandingLargePngBase64: string;
imageUnderstandingValidPngBase64: string;
};
};
expect(call.env).toBe(env);
expect(call.scenario).toBe(scenario);
expect(call.deps.runScenario).toBe(runScenario);
expect(call.deps.waitForQaChannelReady).toBe(waitForQaChannelReady);
expect(call.deps.waitForOutboundMessage).toBe(waitForOutboundMessage);
expect(call.deps.findManagedDreamingCronJob).toBe(findManagedDreamingCronJob);
expect(call.deps.forceMemoryIndex).toBe(forceMemoryIndex);
expect(call.deps.runAgentPrompt).toBe(runAgentPrompt);
expect(call.deps.qaChannelPlugin).toBe(qaChannelPlugin);
expect(call.constants).toEqual({
imageUnderstandingPngBase64: "small",
imageUnderstandingLargePngBase64: "large",
imageUnderstandingValidPngBase64: "valid",
});
await call.deps.webOpenPage({ url: "https://openclaw.ai" });
expect(webOpenPage).toHaveBeenCalledWith({ url: "https://openclaw.ai" });
expect(env.webSessionIds.has("page-1")).toBe(true);
});
});