60 lines
3.4 KiB
TypeScript
60 lines
3.4 KiB
TypeScript
import { expect, fakeServerState, gotoWorkbench, saveScreenshot, test } from "../fixtures/test";
|
|
import { selectors, sessionTab } from "../fixtures/selectors";
|
|
|
|
test.describe("durable projector resume", () => {
|
|
test.use({ scenarioId: "projector-resume-from-checkpoint" });
|
|
|
|
test("catches up a stale AgentRun trace without read-side repair", async ({ page }, testInfo) => {
|
|
const sessionId = "ses_317f78e1-ed91-4b09-a72a-3668d5469f59";
|
|
const traceId = "trc_f2456023233a485b";
|
|
|
|
await gotoWorkbench(page, `/workbench/sessions/${sessionId}`);
|
|
|
|
const initialTurn = await page.request.get(`/v1/workbench/turns/${traceId}`);
|
|
expect(initialTurn.status()).toBe(200);
|
|
const initialTurnBody = await initialTurn.json();
|
|
expect(initialTurnBody.turn.status).toBe("running");
|
|
expect(initialTurnBody.turn.projectionStatus).toBe("projecting");
|
|
expect(initialTurnBody.turn.projectionHealth).toBe("projecting");
|
|
expect(initialTurnBody.turn.lastProjectedSeq).toBe(4);
|
|
|
|
await expect(page.locator(sessionTab(sessionId))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(`${selectors.messageCard}[data-role="agent"]`).last()).toHaveAttribute("data-status", "running");
|
|
await saveScreenshot(page, testInfo, "projector-resume-before-catchup");
|
|
|
|
await expect.poll(async () => {
|
|
const response = await page.request.get(`/v1/workbench/turns/${traceId}`);
|
|
if (!response.ok()) return "http-error";
|
|
const payload = await response.json();
|
|
return `${payload.turn?.status}/${payload.turn?.projectionHealth}/${payload.turn?.lastProjectedSeq}`;
|
|
}, { timeout: 12_000 }).toBe("completed/caught-up/7");
|
|
|
|
const trace = await page.request.get(`/v1/workbench/traces/${traceId}/events?limit=10`);
|
|
expect(trace.status()).toBe(200);
|
|
const traceBody = await trace.json();
|
|
expect(traceBody.traceStatus).toBe("completed");
|
|
expect(traceBody.projectionStatus).toBe("caught-up");
|
|
expect(traceBody.projectionHealth).toBe("caught-up");
|
|
expect(traceBody.events.map((event: { seq?: number }) => event.seq)).toEqual([1, 2, 3, 4, 5, 6, 7]);
|
|
expect(traceBody.range).toMatchObject({ afterSeq: 0, fromSeq: 1, toSeq: 7, returned: 7, total: 7 });
|
|
|
|
const emptyTracePage = await page.request.get(`/v1/workbench/traces/${traceId}/events?cursor=seq:999999&limit=5`);
|
|
expect(emptyTracePage.status()).toBe(200);
|
|
const emptyTraceBody = await emptyTracePage.json();
|
|
expect(emptyTraceBody.events).toHaveLength(0);
|
|
expect(emptyTraceBody.range).toMatchObject({ afterSeq: 999999, fromSeq: null, toSeq: null, returned: 0, total: 7 });
|
|
expect(emptyTraceBody.nextSeq).toBe(999999);
|
|
|
|
const finalCard = page.locator(`${selectors.messageCard}[data-role="agent"]`).filter({ hasText: "fake projector resume completed from durable checkpoint" });
|
|
await expect(finalCard).toHaveAttribute("data-status", "completed");
|
|
await expect(page.locator(`${selectors.traceTimeline}[data-status="completed"]`).last()).toBeVisible();
|
|
expect(await finalCard.innerText()).not.toContain("暂无可读 Trace");
|
|
await expect(page.locator(selectors.commandSend)).toHaveAttribute("data-action", "turn");
|
|
await saveScreenshot(page, testInfo, "projector-resume-after-catchup");
|
|
|
|
const state = await fakeServerState(page) as { legacyRequestLedger?: unknown[]; chatRequests?: unknown[] };
|
|
expect(state.legacyRequestLedger).toEqual([]);
|
|
expect(state.chatRequests).toEqual([]);
|
|
});
|
|
});
|