From ed3a0df1c1e3ba485aa5d46f613188550fdb3b1e Mon Sep 17 00:00:00 2001 From: Dotta Date: Tue, 29 Sep 2026 13:04:12 -0500 Subject: [PATCH] test: await connection setup and persisted runtime identity Complete the dialog fixture context and wait for the actual setup render. Observe the post-spawn PID update before checking the starting service identity, preserving provisioning and readiness assertions. Co-Authored-By: Paperclip --- .../src/__tests__/workspace-runtime.test.ts | 8 ++-- .../IssueThreadInteractionCard.test.tsx | 38 +++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/server/src/__tests__/workspace-runtime.test.ts b/server/src/__tests__/workspace-runtime.test.ts index fd0caefa8e1..97dd8430f66 100644 --- a/server/src/__tests__/workspace-runtime.test.ts +++ b/server/src/__tests__/workspace-runtime.test.ts @@ -6636,7 +6636,7 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () => } throw new Error("Timed out waiting for runtime service process marker"); }; - const waitForPersistedStatus = async (status: string) => { + const waitForPersistedStatus = async (status: string, requireProviderRef = false) => { const deadline = Date.now() + 5_000; while (Date.now() < deadline) { const row = await db @@ -6644,7 +6644,9 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () => .from(workspaceRuntimeServices) .where(eq(workspaceRuntimeServices.executionWorkspaceId, executionWorkspaceId)) .then((rows) => rows[0] ?? null); - if (row?.status === status) return row; + // The provisional starting row is durable before spawn; the child can + // write its marker before the subsequent PID update reaches the DB. + if (row?.status === status && (!requireProviderRef || /^\d+$/.test(row.providerRef ?? ""))) return row; await new Promise((resolve) => setTimeout(resolve, 25)); } throw new Error(`Timed out waiting for persisted runtime service status ${status}`); @@ -6711,7 +6713,7 @@ describeEmbeddedPostgres("workspace runtime service control persistence", () => expect(existsSync(markerPath)).toBe(false); await waitForMarker(markerPath); - const startingRow = await waitForPersistedStatus("starting"); + const startingRow = await waitForPersistedStatus("starting", true); expect(startingRow).toMatchObject({ companyId, projectId, diff --git a/ui/src/components/IssueThreadInteractionCard.test.tsx b/ui/src/components/IssueThreadInteractionCard.test.tsx index f311a190fe9..0c7450b8842 100644 --- a/ui/src/components/IssueThreadInteractionCard.test.tsx +++ b/ui/src/components/IssueThreadInteractionCard.test.tsx @@ -62,6 +62,15 @@ const connectionIntentsApiMocks = vi.hoisted(() => ({ decline: vi.fn(), })); +const routerMocks = vi.hoisted(() => ({ + searchParams: new URLSearchParams(), + readSearchParams: vi.fn(), + setSearchParams: vi.fn(), + navigate: vi.fn(), + setBreadcrumbs: vi.fn(), + pushToast: vi.fn(), +})); + (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; async function act(callback: () => void | Promise) { @@ -79,11 +88,30 @@ async function act(callback: () => void | Promise) { } vi.mock("@/lib/router", () => ({ + useParams: () => ({}), + useSearchParams: () => { + routerMocks.readSearchParams(); + return [routerMocks.searchParams, routerMocks.setSearchParams]; + }, + useNavigate: () => routerMocks.navigate, Link: ({ to, children, className }: { to: string; children: ReactNode; className?: string }) => ( {children} ), })); +vi.mock("@/context/CompanyContext", async (importOriginal) => ({ + ...await importOriginal(), + useCompany: () => ({ selectedCompanyId: issueThreadInteractionFixtureMeta.companyId, selectedCompany: null }), +})); +vi.mock("@/context/BreadcrumbContext", async (importOriginal) => ({ + ...await importOriginal(), + useBreadcrumbs: () => ({ setBreadcrumbs: routerMocks.setBreadcrumbs }), +})); +vi.mock("@/context/ToastContext", async (importOriginal) => ({ + ...await importOriginal(), + useToast: () => ({ pushToast: routerMocks.pushToast }), +})); + vi.mock("@/api/connection-intents", () => ({ connectionIntentsApi: connectionIntentsApiMocks })); vi.mock("./task-chat/TaskChatRichInput", () => ({ @@ -128,6 +156,7 @@ afterEach(() => { root = null; container = null; localStorage.clear(); + routerMocks.readSearchParams.mockClear(); }); describe("IssueThreadInteractionCard", () => { @@ -196,6 +225,15 @@ describe("IssueThreadInteractionCard", () => { "interaction-connection-intent-default", ), ); + // setupOptions being called precedes React Query publishing its result. + // Wait for the real setup flow, not just the dialog's loading shell. + await vi.waitFor(async () => { + await act(async () => { await new Promise((resolve) => setTimeout(resolve, 0)); }); + const setupDialog = document.body.querySelector('[role="dialog"]'); + expect(setupDialog).toBeTruthy(); + expect(setupDialog?.textContent).not.toContain("Loading connection options"); + expect(routerMocks.readSearchParams).toHaveBeenCalled(); + }); const dialog = document.body.querySelector('[role="dialog"]'); expect(dialog).toBeTruthy(); expect(dialog?.textContent).toContain("Connect Notion");