From f401b831f2bf39b149821ab86bf2dfe55091765b Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 30 Sep 2026 01:32:15 -0500 Subject: [PATCH] Stabilize route setup and attachment announcement handling Load route modules during fixture setup so cold transforms cannot leave a timed-out request running against the next test mock state. Dismiss delayed product announcements through the normal Board UI in the attachment receipt fixture. Co-Authored-By: Paperclip --- .../invite-test-resolution-route.test.ts | 19 ++++++++++++++----- .../issue-execution-policy-routes.test.ts | 13 ++++++++++--- tests/e2e/board-attachment-receipts.spec.ts | 7 +++++++ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/server/src/__tests__/invite-test-resolution-route.test.ts b/server/src/__tests__/invite-test-resolution-route.test.ts index 7ca22a99c4..f23fa5f8bf 100644 --- a/server/src/__tests__/invite-test-resolution-route.test.ts +++ b/server/src/__tests__/invite-test-resolution-route.test.ts @@ -1,6 +1,6 @@ import express from "express"; import request from "supertest"; -import { beforeEach, describe, expect, it, vi } from "vitest"; +import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; function createSelectChain(rows: unknown[]) { const query = { @@ -44,6 +44,13 @@ function createInvite(overrides: Record = {}) { }; } +function loadAppModules() { + return Promise.all([ + import("../routes/access.js"), + import("../middleware/index.js"), + ]); +} + async function createApp( db: Record, network: { @@ -51,10 +58,7 @@ async function createApp( requestHead: ReturnType; }, ) { - const [access, middleware] = await Promise.all([ - import("../routes/access.js"), - import("../middleware/index.js"), - ]); + const [access, middleware] = await loadAppModules(); const app = express(); app.use((req, _res, next) => { (req as any).actor = { type: "anon" }; @@ -75,6 +79,11 @@ async function createApp( } describe.sequential("GET /invites/:token/test-resolution", () => { + beforeAll(async () => { + // Route transformation is fixture setup, not part of the network assertions. + await loadAppModules(); + }); + beforeEach(() => { vi.clearAllMocks(); }); diff --git a/server/src/__tests__/issue-execution-policy-routes.test.ts b/server/src/__tests__/issue-execution-policy-routes.test.ts index abc52ad413..be3f8c36cb 100644 --- a/server/src/__tests__/issue-execution-policy-routes.test.ts +++ b/server/src/__tests__/issue-execution-policy-routes.test.ts @@ -173,11 +173,15 @@ type TestActor = runId: string | null; }; -async function createApp(actor?: TestActor) { - const [{ errorHandler }, { issueRoutes }] = await Promise.all([ +function loadAppModules() { + return Promise.all([ import("../middleware/index.js"), import("../routes/issues.js"), ]); +} + +async function createApp(actor?: TestActor) { + const [{ errorHandler }, { issueRoutes }] = await loadAppModules(); const app = express(); app.use(express.json()); app.use((req, _res, next) => { @@ -196,7 +200,7 @@ async function createApp(actor?: TestActor) { } describe("issue execution policy routes", () => { - beforeEach(() => { + beforeEach(async () => { vi.resetModules(); vi.doUnmock("../services/index.js"); vi.doUnmock("../routes/issues.js"); @@ -263,6 +267,9 @@ describe("issue execution policy routes", () => { }; }); mockAccessService.hasPermission.mockResolvedValue(false); + // Finish cold imports before a test configures its request-specific mocks. + // A timed-out import must not resume a request against the next test's mocks. + await loadAppModules(); }); it("reauthorizes a terminal verdict against the review policy held under the update lock", async () => { diff --git a/tests/e2e/board-attachment-receipts.spec.ts b/tests/e2e/board-attachment-receipts.spec.ts index de7fb5cbde..649758e26e 100644 --- a/tests/e2e/board-attachment-receipts.spec.ts +++ b/tests/e2e/board-attachment-receipts.spec.ts @@ -27,6 +27,13 @@ async function body( } async function setup(page: Page, request: APIRequestContext, classic: boolean) { + // Announcements can appear after navigation or reload and cover the classic + // composer's attachment button. Dismiss through the same UI as a Board user. + await page.addLocatorHandler( + page.getByRole("complementary", { name: "Paperclip announcements", exact: true }) + .getByRole("button", { name: "Dismiss announcement", exact: true }), + async (dismiss) => { await dismiss.click(); }, + ); const company = await body<{ id: string; issuePrefix: string }>( await request.post("/api/companies", { data: { name: `Board receipt browser ${randomUUID()}` },