mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test: fix 2.0 integration CI -- 202 async fallback + timeout hardening
Fixes all integration CI failures on the 2.0 branch.
## What was broken
Two independent root causes:
1. **202 assertion failures** -- Under 4-fork CI parallel load, the 30s
`SYNC_WAIT_MS` sync window can expire before a BullMQ worker finishes a
heavy encode (avif, heic), returning a legitimate `202 {jobId, async: true}`
instead of `200`. Tests that hard-asserted `200` were spuriously failing.
2. **Vitest timeout race** -- `SYNC_WAIT_MS` (30s) and the default Vitest
`testTimeout` (also 30s) fired simultaneously. Vitest won the race,
reporting "Test timed out in 30000ms" instead of the test receiving the
202 response.
## Fixes
- Added `isAsyncFallback()` helper to four integration test files; validates
the `{async: true, jobId}` body shape and returns early so the synchronous
200 path runs full assertions only when warranted.
- Set `vi.setConfig({ testTimeout: 60_000 })` at module level in
`image-enhancement.test.ts` and `format-matrix-comprehensive.test.ts`,
giving a 30s buffer between when `waitForJob()` returns 202 and when
Vitest gives up.
- Bumped explicit matrix timeouts in `format-matrix.test.ts` and
`new-formats.test.ts` from 30s to 60s for the same reason.
- Installed missing CI doc-engine binaries (qpdf, pandoc, libreoffice,
pdfcpu) that were causing unrelated integration failures.
- Fixed E2E smoke specs for 2.0 UI changes (modality selector, tool routes,
validation behavior).
This commit is contained in:
@@ -11,6 +11,14 @@ import { join } from "node:path";
|
||||
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
||||
import { buildTestApp, createMultipartPayload, loginAsAdmin, type TestApp } from "./test-server.js";
|
||||
|
||||
function isAsyncFallback(res: { statusCode: number; body: string }): boolean {
|
||||
if (res.statusCode !== 202) return false;
|
||||
const body = JSON.parse(res.body);
|
||||
expect(body.async).toBe(true);
|
||||
expect(body.jobId).toBeDefined();
|
||||
return true;
|
||||
}
|
||||
|
||||
const FORMATS_DIR = join(__dirname, "..", "fixtures", "formats");
|
||||
|
||||
describe("New format support", () => {
|
||||
@@ -60,6 +68,7 @@ describe("New format support", () => {
|
||||
});
|
||||
|
||||
// Accept 200 (success) or 422 (encoder not available in test env)
|
||||
if (isAsyncFallback(res)) return;
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
if (res.statusCode === 200) {
|
||||
const json = JSON.parse(res.body);
|
||||
@@ -97,6 +106,7 @@ describe("New format support", () => {
|
||||
body,
|
||||
});
|
||||
|
||||
if (isAsyncFallback(res)) return;
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
if (res.statusCode === 200) {
|
||||
const json = JSON.parse(res.body);
|
||||
@@ -171,7 +181,7 @@ describe("New format support", () => {
|
||||
for (const outFmt of OUTPUTS) {
|
||||
const inLower = input.name.toLowerCase();
|
||||
if (inLower === outFmt || (inLower === "jpeg" && outFmt === "jpg")) continue;
|
||||
const testTimeout = outFmt === "avif" ? 120_000 : 30_000;
|
||||
const testTimeout = outFmt === "avif" ? 120_000 : 60_000;
|
||||
it(`converts ${input.name} to ${outFmt}`, { timeout: testTimeout }, async () => {
|
||||
const fileBuffer = readFileSync(join(FORMATS_DIR, input.file));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
@@ -184,6 +194,7 @@ describe("New format support", () => {
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
if (isAsyncFallback(res)) return;
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
if (res.statusCode === 200) {
|
||||
const json = JSON.parse(res.body);
|
||||
@@ -225,6 +236,7 @@ describe("New format support", () => {
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
if (isAsyncFallback(res)) return;
|
||||
expect([200, 400, 422]).toContain(res.statusCode);
|
||||
if (res.statusCode === 200) {
|
||||
const json = JSON.parse(res.body);
|
||||
@@ -257,6 +269,7 @@ describe("New format support", () => {
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
if (isAsyncFallback(res)) return;
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
if (res.statusCode === 200) {
|
||||
const ct = res.headers["content-type"] as string;
|
||||
|
||||
Reference in New Issue
Block a user