124 lines
6.7 KiB
TypeScript
124 lines
6.7 KiB
TypeScript
import { expect, fakeServerState, gotoWorkbench, test } from "../fixtures/test";
|
|
import { selectors, sessionTab } from "../fixtures/selectors";
|
|
|
|
test("new optimistic session first turn does not submit local thr_* as upstream thread", async ({ page }) => {
|
|
await gotoWorkbench(page);
|
|
await page.locator(selectors.sessionCreate).click();
|
|
const activeTab = page.locator('.session-tab[data-active="true"]');
|
|
await expect(activeTab).toHaveAttribute("data-session-id", /^ses_/u);
|
|
const sessionId = await activeTab.getAttribute("data-session-id");
|
|
expect(sessionId).toBeTruthy();
|
|
|
|
await page.locator(selectors.commandInput).fill("web-probe D601 v0.3 smoke: reply with exactly OK.");
|
|
await expect(page.locator(selectors.commandSend)).toBeEnabled();
|
|
await page.locator(selectors.commandSend).click();
|
|
|
|
await expect.poll(async () => ((await fakeServerState(page)).chatRequests as unknown[]).length).toBe(1);
|
|
const state = await fakeServerState(page);
|
|
const request = (state.chatRequests as Record<string, unknown>[])[0];
|
|
if (!request) throw new Error("expected one chat request");
|
|
expect(request.conversationId).toBeUndefined();
|
|
expect(request.projectId).toBeUndefined();
|
|
expect(request.workspaceId).toBeUndefined();
|
|
expect(request.sessionId).toBe(sessionId);
|
|
expect(request.threadId ?? null).toBeNull();
|
|
expect((state.legacyRequestLedger as unknown[]).length).toBe(0);
|
|
});
|
|
|
|
test.describe("server authoritative session create", () => {
|
|
test.use({ scenarioId: "server-authoritative-create" });
|
|
|
|
test("new session adopts backend selected conversation before submit", async ({ page }) => {
|
|
await gotoWorkbench(page);
|
|
await page.locator(selectors.sessionCreate).click();
|
|
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_server_created/u);
|
|
await expect(page.locator('.session-tab[data-session-id="ses_server_created"]')).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator('.session-tab[data-session-id="ses_server_created"]')).toHaveAttribute("data-running", "false");
|
|
|
|
await page.locator(selectors.commandInput).fill("server authoritative create smoke");
|
|
await expect(page.locator(selectors.commandSend)).toBeEnabled();
|
|
await page.locator(selectors.commandSend).click();
|
|
|
|
await expect.poll(async () => ((await fakeServerState(page)).chatRequests as unknown[]).length).toBe(1);
|
|
const state = await fakeServerState(page);
|
|
const request = (state.chatRequests as Record<string, unknown>[])[0];
|
|
expect(request?.conversationId).toBeUndefined();
|
|
expect(request?.projectId).toBeUndefined();
|
|
expect(request?.workspaceId).toBeUndefined();
|
|
expect(request?.sessionId).toBe("ses_server_created");
|
|
expect((state.legacyRequestLedger as unknown[]).length).toBe(0);
|
|
});
|
|
});
|
|
|
|
test.describe("session create while old route is still loading", () => {
|
|
test.use({ scenarioId: "create-while-route-loading" });
|
|
|
|
test("created session owns URL, active tab, and composer while old running session refreshes", async ({ page }) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_running");
|
|
|
|
const created = page.waitForResponse((response) => response.request().method() === "POST" && new URL(response.url()).pathname === "/v1/agent/sessions");
|
|
await page.locator(selectors.sessionCreate).click();
|
|
expect((await created).status()).toBe(201);
|
|
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_created_while_running/u, { timeout: 500 });
|
|
await expect(page.locator(sessionTab("ses_created_while_running"))).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-active", "false");
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-running", "true");
|
|
await expect(page.locator(selectors.commandSend)).toHaveAttribute("data-action", "turn");
|
|
|
|
const state = await fakeServerState(page);
|
|
expect((state.legacyRequestLedger as unknown[]).length).toBe(0);
|
|
});
|
|
});
|
|
|
|
test.describe("session create read-side failure authority", () => {
|
|
test.use({ scenarioId: "created-session-read-failure" });
|
|
|
|
test("created session keeps active projection when detail/messages read fails", async ({ page }) => {
|
|
await gotoWorkbench(page, "/workbench/sessions/ses_running");
|
|
|
|
const created = page.waitForResponse((response) => response.request().method() === "POST" && new URL(response.url()).pathname === "/v1/agent/sessions");
|
|
await page.locator(selectors.sessionCreate).click();
|
|
expect((await created).status()).toBe(200);
|
|
|
|
const createdTab = page.locator(sessionTab("ses_created_read_failure"));
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_created_read_failure/u);
|
|
await expect(createdTab).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-active", "false");
|
|
await expect(page.locator(sessionTab("ses_running"))).toHaveAttribute("data-running", "true");
|
|
await expect(page.locator(".conversation-error-hint")).toContainText(/unavailable/u);
|
|
await expect(page.locator(".composer-mode")).toContainText("turn / 新请求");
|
|
await expect(page.locator(".composer-warning")).toHaveCount(0);
|
|
|
|
await page.locator(selectors.commandInput).fill("created session projection must survive read failure");
|
|
await expect(page.locator(selectors.commandSend)).toBeEnabled();
|
|
await page.waitForTimeout(800);
|
|
await expect(createdTab).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(".composer-mode")).toContainText("turn / 新请求");
|
|
|
|
const state = await fakeServerState(page);
|
|
expect((state.legacyRequestLedger as unknown[]).length).toBe(0);
|
|
});
|
|
});
|
|
|
|
test.describe("session create and delete authority", () => {
|
|
test.use({ scenarioId: "server-authoritative-create" });
|
|
|
|
test("created session becomes active and deleted session stays archived after reload", async ({ page }) => {
|
|
await gotoWorkbench(page);
|
|
await page.locator(selectors.sessionCreate).click();
|
|
await expect(page).toHaveURL(/\/workbench\/sessions\/ses_server_created/u);
|
|
await expect(page.locator('.session-tab[data-session-id="ses_server_created"]')).toHaveAttribute("data-active", "true");
|
|
await expect(page.locator(selectors.commandSend)).toBeDisabled();
|
|
|
|
await page.locator(selectors.sessionDelete).click();
|
|
await expect(page.locator('.session-tab[data-session-id="ses_server_created"]')).toHaveCount(0);
|
|
await page.reload();
|
|
await expect(page.locator('.session-tab[data-session-id="ses_server_created"]')).toHaveCount(0);
|
|
const state = await fakeServerState(page);
|
|
const deleted = (state.sessions as Array<{ sessionId?: string; status?: string }>).find((item) => item.sessionId === "ses_server_created");
|
|
expect(deleted?.status).toBe("archived");
|
|
});
|
|
});
|