diff --git a/server/src/__tests__/retained-native-session-cleanup-work.test.ts b/server/src/__tests__/retained-native-session-cleanup-work.test.ts index 1f698c7b210..f34bba49fdc 100644 --- a/server/src/__tests__/retained-native-session-cleanup-work.test.ts +++ b/server/src/__tests__/retained-native-session-cleanup-work.test.ts @@ -69,12 +69,15 @@ describe("retained native cleanup scheduled lifecycle", () => { worker = retainedNativeSessionCleanupWork(db, cleanup).register(coordinator, () => admitted); await worker.ready; } - async function flush() { + // `interval` is the real-time gap between polls. Each poll advances fake time + // by one second, which admits at most one cleanup pass (batch size 1, one + // second minimum pass interval), so a long backlog drains one entry per poll. + async function flush(interval = 50) { await vi.waitFor(async () => { await vi.advanceTimersByTimeAsync(1000); expect(idleWorkSnapshot().active).toBe(0); expect(coordinator!.nextWakeAt()).toBeNull(); - }, { timeout: 15_000 }); + }, { timeout: 15_000, interval }); } async function quiet(ms = 120_000) { const previous = db.$client.options.debug; @@ -614,7 +617,12 @@ describe("retained native cleanup scheduled lifecycle", () => { it("drains a backlog across discovery pages and becomes quiet with the completed history retained", async () => { const runIds: string[] = []; for (let index = 0; index < 103; index++) runIds.push(await candidate()); - await start(); await flush(); + // 103 serial passes at the default 50ms poll spend ~4.7s of a ~5s local run + // waiting between polls, and contended CI runners pushed it past 15s. A 5ms + // poll drains the same passes in under a second; the wake schedule is set + // before a pass releases its idle-work hold, so polling faster cannot + // observe a false quiet state. + await start(); await flush(5); expect(cleanup.mock.calls.map(([input]) => input.runId).sort()).toEqual(runIds.sort()); expect(await db.select().from(nativeRunFinalizations).where(eq(nativeRunFinalizations.companyId, companyId))).toHaveLength(103); await quiet(); diff --git a/server/src/adapters/cloud-external-adapters.test.ts b/server/src/adapters/cloud-external-adapters.test.ts index 21da3bccbeb..637f952c60e 100644 --- a/server/src/adapters/cloud-external-adapters.test.ts +++ b/server/src/adapters/cloud-external-adapters.test.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; -import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest"; let home: string; let localPackage: string; @@ -35,6 +35,29 @@ function writePackage(packageDir: string, type: string): void { fs.writeFileSync(path.join(packageDir, "ui-parser.mjs"), parserSource); } +// The registry imports every built-in adapter. The first import in a worker +// pays the one-time transform of that whole graph: about 15s on contended CI +// runners (the first case timed out at 15s while the next case took 1.1s) and +// 20-30s on a loaded local machine. Pay it here, not inside the first case's +// budget. Each case still gets fresh module state from vi.resetModules() below, +// and its re-import measured 0.4-1.5s. The warm-up uses an empty home and a +// managed signal, so it reads and runs no persisted adapter package. The 90s +// limit is three times the slowest measured cold import. +beforeAll(async () => { + const warmHome = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-cloud-adapters-warm-")); + vi.stubEnv("PAPERCLIP_HOME", warmHome); + vi.stubEnv("PAPERCLIP_MANAGED_CONFIG", ""); + try { + const registry = await import("./registry.js"); + await registry.waitForExternalAdapters(); + await import("./plugin-loader.js"); + await import("../services/adapter-plugin-store.js"); + } finally { + vi.unstubAllEnvs(); + fs.rmSync(warmHome, { recursive: true, force: true }); + } +}, 90_000); + beforeEach(() => { vi.resetModules(); vi.stubEnv("PAPERCLIP_CLOUD_TENANT_SERVER_TOKEN", undefined);