mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
A 202 means the sync window expired while the job was still running. Tests treated it as a terminal pass: `if (isAsyncFallback(res)) return;` checked the envelope and returned, asserting nothing about the outcome and leaving the job running into the next test, which is the leak cancelAcceptedJobAndWait exists to prevent. Because the window only expires under load, coverage tracked runner load. On CI 44 tests took this path and verified nothing; the same tests on a dev machine asserted in full (one measured 6.4s locally against 31s on CI). settleAsyncFallback waits for a terminal state and asserts the job finished, and that a failure carries a message rather than being a crash. A clean failure stays valid, since the exotic-format fixtures are meant to be rejected. All 82 call sites moved over. per-fork-env no longer floors SYNC_WAIT_MS, so forcing it to 0 drives every request through its 202 path. 570 tests were validated that way and matched their normal-window results exactly. The 29-34s band dropped from 44 tests (23.4% of test time) to 6 (3.0%). Total test time rose 5.8% and CI wall went 12.8 to 13.1 min: the forks were doing real work during that wait, so this buys determinism, not speed. Per-shard totals unchanged at 9903 tests, 9435 passed, 468 skipped.
59 lines
2.5 KiB
TypeScript
59 lines
2.5 KiB
TypeScript
import crypto from "node:crypto";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import pg from "pg";
|
|
|
|
// Each test file (forks pool, isolated) gets its own Postgres database cloned
|
|
// from the migrated template built in tests/global-setup.ts, plus its own
|
|
// workspace dir. setupFiles run before any app module loads, so
|
|
// apps/api/src/config.ts captures the per-file DATABASE_URL.
|
|
const suffix = `${process.pid}_${crypto.randomUUID().slice(0, 8).replace(/-/g, "")}`;
|
|
const forkDir = path.join(os.tmpdir(), `SnapOtter-test-${suffix}`);
|
|
process.env.WORKSPACE_PATH = path.join(forkDir, "workspace");
|
|
|
|
const baseUrl = process.env.TEST_PG_BASE_URL;
|
|
if (!baseUrl) {
|
|
throw new Error("TEST_PG_BASE_URL missing; tests/global-setup.ts did not run");
|
|
}
|
|
|
|
const redisBaseUrl = process.env.TEST_REDIS_BASE_URL;
|
|
if (!redisBaseUrl) {
|
|
throw new Error("TEST_REDIS_BASE_URL missing; tests/global-setup.ts did not run");
|
|
}
|
|
process.env.REDIS_URL = redisBaseUrl;
|
|
process.env.BULLMQ_PREFIX = `snapotter_test_${suffix}`;
|
|
|
|
// Heavy format conversions can exceed the 8s production default under parallel
|
|
// test forks; 30s keeps tool routes synchronous (200) in tests while production
|
|
// stays at 8s.
|
|
//
|
|
// An explicit SYNC_WAIT_MS is now honored verbatim rather than floored. The
|
|
// constrained docker test image (macOS Docker VM, where Sharp and FFmpeg run
|
|
// ~2-3x slower) still widens the window, and forcing it *down* (SYNC_WAIT_MS=0)
|
|
// drives every tool through its 202 path, which is the only way to exercise
|
|
// that branch on a machine fast enough to never hit it naturally.
|
|
const requestedSyncWait = process.env.SYNC_WAIT_MS?.trim();
|
|
const hasExplicitSyncWait =
|
|
Boolean(requestedSyncWait) && Number.isFinite(Number(requestedSyncWait));
|
|
process.env.SYNC_WAIT_MS = hasExplicitSyncWait ? (requestedSyncWait as string) : "30000";
|
|
const dbName = `snapotter_test_${suffix}`; // pid digits + uuid hex: identifier-safe
|
|
const admin = new pg.Client({ connectionString: baseUrl });
|
|
await admin.connect();
|
|
// Concurrent CREATE DATABASE ... TEMPLATE from parallel forks can transiently
|
|
// conflict; retry briefly.
|
|
let created = false;
|
|
for (let attempt = 0; attempt < 5 && !created; attempt++) {
|
|
try {
|
|
await admin.query(`CREATE DATABASE ${dbName} TEMPLATE snapotter_template`);
|
|
created = true;
|
|
} catch (err) {
|
|
if (attempt === 4) throw err;
|
|
await new Promise((r) => setTimeout(r, 150 + Math.floor(150 * attempt)));
|
|
}
|
|
}
|
|
await admin.end();
|
|
|
|
const forkUrl = new URL(baseUrl);
|
|
forkUrl.pathname = `/${dbName}`;
|
|
process.env.DATABASE_URL = forkUrl.toString();
|