diff --git a/extensions/qa-lab/src/lab-server.test.ts b/extensions/qa-lab/src/lab-server.test.ts index 3487207dde1..22989f22019 100644 --- a/extensions/qa-lab/src/lab-server.test.ts +++ b/extensions/qa-lab/src/lab-server.test.ts @@ -8,9 +8,122 @@ import { startQaLabServer } from "./lab-server.js"; vi.mock("openclaw/plugin-sdk/qa-channel", async () => await import("../../qa-channel/api.js")); +const captureMock = vi.hoisted(() => { + const sessions: Array> = []; + const events: Array> = []; + + const readMeta = (event: Record) => { + try { + return typeof event.metaJson === "string" + ? (JSON.parse(event.metaJson) as Record) + : {}; + } catch { + return {}; + } + }; + const countValues = (values: Array) => + Object.entries( + values.reduce>((acc, value) => { + if (value) { + acc[value] = (acc[value] ?? 0) + 1; + } + return acc; + }, {}), + ).map(([value, count]) => ({ value, count })); + + const store = { + upsertSession(session: Record) { + sessions.push({ ...session }); + }, + recordEvent(event: Record) { + events.push({ ...event }); + }, + listSessions(limit: number) { + return sessions.slice(0, limit).map((session) => + Object.assign({}, session, { + eventCount: events.filter((event) => event.sessionId === session.id).length, + }), + ); + }, + getSessionEvents(sessionId: string, limit: number) { + return events.filter((event) => event.sessionId === sessionId).slice(0, limit); + }, + summarizeSessionCoverage(sessionId: string) { + const selected = events.filter((event) => event.sessionId === sessionId); + const metas = selected.map(readMeta); + return { + sessionId, + totalEvents: selected.length, + unlabeledEventCount: metas.filter((meta) => !meta.provider && !meta.model).length, + providers: countValues(metas.map((meta) => meta.provider as string | undefined)), + apis: countValues(metas.map((meta) => meta.api as string | undefined)), + models: countValues(metas.map((meta) => meta.model as string | undefined)), + hosts: countValues(selected.map((event) => event.host as string | undefined)), + localPeers: countValues( + selected + .map((event) => event.host as string | undefined) + .filter((host) => host?.startsWith("127.0.0.1:")), + ), + }; + }, + queryPreset(preset: string, sessionId?: string) { + if (preset !== "double-sends") { + return []; + } + const selected = events.filter((event) => !sessionId || event.sessionId === sessionId); + const counts = selected.reduce>((acc, event) => { + const host = typeof event.host === "string" ? event.host : ""; + if (host) { + acc[host] = (acc[host] ?? 0) + 1; + } + return acc; + }, {}); + return Object.entries(counts) + .filter(([, duplicateCount]) => duplicateCount > 1) + .map(([host, duplicateCount]) => ({ host, duplicateCount })); + }, + readBlob() { + return null; + }, + deleteSessions(sessionIds: string[]) { + const ids = new Set(sessionIds); + for (let index = sessions.length - 1; index >= 0; index -= 1) { + if (ids.has(String(sessions[index]?.id))) { + sessions.splice(index, 1); + } + } + return { deleted: sessionIds.length }; + }, + purgeAll() { + sessions.splice(0); + events.splice(0); + return { deletedSessions: 0, deletedEvents: 0 }; + }, + }; + + return { + store, + reset() { + sessions.splice(0); + events.splice(0); + }, + }; +}); + +vi.mock("openclaw/plugin-sdk/proxy-capture", () => ({ + getDebugProxyCaptureStore: () => captureMock.store, + resolveDebugProxySettings: () => ({ + dbPath: process.env.OPENCLAW_DEBUG_PROXY_DB_PATH ?? "", + blobDir: process.env.OPENCLAW_DEBUG_PROXY_BLOB_DIR ?? "", + proxyUrl: process.env.OPENCLAW_DEBUG_PROXY_URL ?? "", + sessionId: "qa-lab-test", + }), +})); + const cleanups: Array<() => Promise> = []; afterEach(async () => { + captureMock.reset(); while (cleanups.length > 0) { await cleanups.pop()?.(); } @@ -583,12 +696,7 @@ describe("qa-lab server", () => { }); process.env.OPENCLAW_DEBUG_PROXY_DB_PATH = path.join(tempDir, "capture.sqlite"); process.env.OPENCLAW_DEBUG_PROXY_BLOB_DIR = path.join(tempDir, "blobs"); - const { getDebugProxyCaptureStore } = - await import("../../../src/proxy-capture/store.sqlite.js"); - const store = getDebugProxyCaptureStore( - process.env.OPENCLAW_DEBUG_PROXY_DB_PATH, - process.env.OPENCLAW_DEBUG_PROXY_BLOB_DIR, - ); + const store = captureMock.store; store.upsertSession({ id: "qa-capture-session", startedAt: Date.now(),