test(integration): accept 202 async for DNG in format-matrix smoke tests (#291)

* test(integration): accept 202 async for DNG in format-matrix smoke tests

Follow-up to #290. Full-resolution DNG decode (3474x2314 vs the old 1024px
preview) pushes expensive operations (AVIF encode, image-enhancement) past
the 8s sync window on CI runners, so the API correctly returns 202 Accepted
and processes the job asynchronously. The format-matrix smoke tests only
allowed [200, 400, 422] and required a clean error body on any non-200, so
they failed on the 202 (Integration shard 4/4 went red on main).

202 (accepted, async) is a valid clean response for these "no crash / clean
response" checks. Make the matrix allowlists 202-tolerant and require an
error body only for true error codes:
- add 202 to ACCEPTABLE_CODES / ACCEPTABLE_FALLBACK_CODES + inline allowlists
- change `if (statusCode !== 200)` error-body checks to `>= 400`

Verified locally against Postgres+Redis: DNG tests pass both normally (200)
and with SYNC_WAIT_MS=1 forcing 202 (68 passed, 0 failed each run).

* test(integration): treat 202 as non-error in DNG conversion else-branches

The first pass added 202 to status allowlists and switched `if (!== 200)`
error checks to `>= 400`, but missed the `if (200) {...} else {...}` shape in
the exotic conversion matrix and the expanded color-blindness test: their
`else` caught 202 and then asserted body.error (which async responses lack).

Reproduced locally by temporarily lowering the test sync-window floor to force
202 on every DNG op, then fixed every flagged assertion. Change the two
`else` branches to `else if (statusCode >= 400)` so 202 (accepted, async) is a
valid outcome with no sync body to verify.

Verified: forced-202 across all 4 DNG matrix files = 123 passed / 0 failed;
normal sync window = 34 DNG tests passed; typecheck + biome clean.
This commit is contained in:
SnapOtter
2026-06-22 11:39:13 +08:00
committed by GitHub
parent 3d9ff1e0d2
commit 9b1c105089
4 changed files with 46 additions and 40 deletions
@@ -189,7 +189,7 @@ const CORE_FORMATS = PRIMARY_FORMATS.filter(
(f) => !f.needsCliDecoder && !f.needsHeifDecoder && !f.mayFailValidation, (f) => !f.needsCliDecoder && !f.needsHeifDecoder && !f.mayFailValidation,
); );
const ACCEPTABLE_FALLBACK_CODES = [200, 400, 422]; const ACCEPTABLE_FALLBACK_CODES = [200, 202, 400, 422];
function needsFallback(fmt: FormatDef): boolean { function needsFallback(fmt: FormatDef): boolean {
return fmt.needsCliDecoder || fmt.needsHeifDecoder || fmt.mayFailValidation; return fmt.needsCliDecoder || fmt.needsHeifDecoder || fmt.mayFailValidation;
@@ -699,7 +699,7 @@ describe("Image enhancement across all 16 primary formats", () => {
}); });
if (needsFallback(fmt)) { if (needsFallback(fmt)) {
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
} else { } else {
expect(res.statusCode).toBe(200); expect(res.statusCode).toBe(200);
} }
@@ -922,7 +922,7 @@ describe("No-crash matrix: 16 formats x 12 tools", () => {
expect(typeof body).toBe("object"); expect(typeof body).toBe("object");
// If error, verify clean error shape // If error, verify clean error shape
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
expect(body.error.length).toBeGreaterThan(0); expect(body.error.length).toBeGreaterThan(0);
@@ -1031,7 +1031,7 @@ describe("Exotic format error shape verification", () => {
if (!res) return; if (!res) return;
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
// Response must be valid JSON (not HTML, not raw text) // Response must be valid JSON (not HTML, not raw text)
let body: Record<string, unknown>; let body: Record<string, unknown>;
@@ -1043,7 +1043,7 @@ describe("Exotic format error shape verification", () => {
); );
} }
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
// Error message should not contain raw stack trace indicators // Error message should not contain raw stack trace indicators
@@ -1093,10 +1093,10 @@ describe("HEIC/HEIF graceful handling", () => {
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
// Accept success (200) or clean error (400/422) // Accept success (200) or clean error (400/422)
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
} }
@@ -1188,7 +1188,7 @@ describe("SVG through raster tools", () => {
// SVG should either be rasterized and processed (200) or cleanly rejected // SVG should either be rasterized and processed (200) or cleanly rejected
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode === 200) { if (res.statusCode === 200) {
@@ -1234,10 +1234,10 @@ describe("ICO (multi-size format) through tools", () => {
// ICO requires CLI decoder; accept success or clean error // ICO requires CLI decoder; accept success or clean error
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
} }
@@ -70,7 +70,7 @@ const ALL_OUTPUT_FORMATS = ["jpg", "png", "webp", "avif", "tiff", "gif"];
const EXTENDED_OUTPUT_FORMATS = ["heic", "jxl", "bmp", "ico", "jp2", "qoi"]; const EXTENDED_OUTPUT_FORMATS = ["heic", "jxl", "bmp", "ico", "jp2", "qoi"];
/** Acceptable status codes for exotic formats that may lack decoders */ /** Acceptable status codes for exotic formats that may lack decoders */
const ACCEPTABLE_CODES = [200, 400, 422]; const ACCEPTABLE_CODES = [200, 202, 400, 422];
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Shared state // Shared state
@@ -131,7 +131,7 @@ function assertNoServerCrash(statusCode: number) {
function assertSuccessOrCleanError(res: { statusCode: number; body: string }) { function assertSuccessOrCleanError(res: { statusCode: number; body: string }) {
assertNoServerCrash(res.statusCode); assertNoServerCrash(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
expect(body.error.length).toBeGreaterThan(0); expect(body.error.length).toBeGreaterThan(0);
@@ -308,7 +308,9 @@ describe("Exotic format output conversion matrix", () => {
// Verify output URL contains correct extension // Verify output URL contains correct extension
const ext = outFmt === "jpg" ? ".jpg" : `.${outFmt}`; const ext = outFmt === "jpg" ? ".jpg" : `.${outFmt}`;
expect(body.downloadUrl).toContain(ext); expect(body.downloadUrl).toContain(ext);
} else { } else if (res.statusCode >= 400) {
// 202 (accepted, async) has no sync body to verify; only error
// codes carry a JSON error.
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
@@ -363,7 +365,7 @@ describe("Core format to extended output format matrix", () => {
}); });
// Extended encoders may not be available // Extended encoders may not be available
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
if (res.statusCode === 200) { if (res.statusCode === 200) {
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
@@ -204,7 +204,7 @@ const EXOTIC_FORMATS = FORMAT_SAMPLES.filter(
); );
/** Status codes we accept for formats that may lack decoder support */ /** Status codes we accept for formats that may lack decoder support */
const ACCEPTABLE_FALLBACK_CODES = [200, 400, 422]; const ACCEPTABLE_FALLBACK_CODES = [200, 202, 400, 422];
function needsFallback(fmt: FormatSample): boolean { function needsFallback(fmt: FormatSample): boolean {
return fmt.needsCliDecoder || fmt.needsHeifDecoder || fmt.mayFailValidation; return fmt.needsCliDecoder || fmt.needsHeifDecoder || fmt.mayFailValidation;
@@ -280,7 +280,9 @@ describe("Color-blindness simulation cross-format", () => {
expect(typeof body.downloadUrl).toBe("string"); expect(typeof body.downloadUrl).toBe("string");
expect(body.processedSize).toBeGreaterThan(0); expect(body.processedSize).toBeGreaterThan(0);
expect(body.originalSize).toBeGreaterThan(0); expect(body.originalSize).toBeGreaterThan(0);
} else { } else if (res.statusCode >= 400) {
// 202 (accepted, async) has no sync body to verify; only error
// codes carry a JSON error.
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
@@ -348,7 +350,9 @@ describe("Beautify cross-format", () => {
expect(typeof body.downloadUrl).toBe("string"); expect(typeof body.downloadUrl).toBe("string");
expect(body.processedSize).toBeGreaterThan(0); expect(body.processedSize).toBeGreaterThan(0);
expect(body.originalSize).toBeGreaterThan(0); expect(body.originalSize).toBeGreaterThan(0);
} else { } else if (res.statusCode >= 400) {
// 202 (accepted, async) has no sync body to verify; only error
// codes carry a JSON error.
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
@@ -407,7 +411,7 @@ describe("Edit-metadata cross-format", () => {
expect(ACCEPTABLE_FALLBACK_CODES).toContain(res.statusCode); expect(ACCEPTABLE_FALLBACK_CODES).toContain(res.statusCode);
} else { } else {
// edit-metadata may also fail with 422 if ExifTool is not installed // edit-metadata may also fail with 422 if ExifTool is not installed
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
} }
if (res.statusCode === 200) { if (res.statusCode === 200) {
@@ -458,7 +462,7 @@ describe("Edit-metadata cross-format", () => {
// Inspect uses ExifTool, which may not be installed; accept clean errors // Inspect uses ExifTool, which may not be installed; accept clean errors
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode === 200) { if (res.statusCode === 200) {
@@ -618,7 +622,7 @@ describe("Split cross-format", () => {
// Must not crash. Accept 200 (ZIP streamed) or 422 (clean error) // Must not crash. Accept 200 (ZIP streamed) or 422 (clean error)
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
}, },
perTestTimeout, perTestTimeout,
); );
@@ -1104,7 +1108,7 @@ describe("Compose cross-format", () => {
}); });
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
}, },
perTestTimeout, perTestTimeout,
); );
@@ -1204,7 +1208,7 @@ describe("Compare cross-format", () => {
}); });
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
}, },
perTestTimeout, perTestTimeout,
); );
@@ -1319,7 +1323,7 @@ describe("Collage cross-format", () => {
}); });
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
}, },
perTestTimeout, perTestTimeout,
); );
@@ -1433,7 +1437,7 @@ describe("Stitch cross-format", () => {
}); });
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
}, },
perTestTimeout, perTestTimeout,
); );
@@ -1494,7 +1498,7 @@ describe("Transparency-fixer cross-format", () => {
} else if (res.statusCode === 202) { } else if (res.statusCode === 202) {
expect(body.jobId).toBeDefined(); expect(body.jobId).toBeDefined();
expect(body.async).toBe(true); expect(body.async).toBe(true);
} else if (res.statusCode !== 200) { } else if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
} }
@@ -1869,7 +1873,7 @@ describe("Find-duplicates cross-format", () => {
}); });
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
}, },
perTestTimeout, perTestTimeout,
); );
@@ -419,7 +419,7 @@ const TOOLS: ToolDef[] = [
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
/** Status codes we accept for formats that may lack decoder support */ /** Status codes we accept for formats that may lack decoder support */
const ACCEPTABLE_FALLBACK_CODES = [200, 400, 422]; const ACCEPTABLE_FALLBACK_CODES = [200, 202, 400, 422];
function needsFallback(fmt: FormatSample): boolean { function needsFallback(fmt: FormatSample): boolean {
return fmt.needsCliDecoder || fmt.needsHeifDecoder || fmt.mayFailValidation; return fmt.needsCliDecoder || fmt.needsHeifDecoder || fmt.mayFailValidation;
@@ -606,7 +606,7 @@ describe("Cross-format matrix", () => {
// If the API returned an error, verify it is a clean JSON error // If the API returned an error, verify it is a clean JSON error
// (not a raw crash / stack trace / HTML error page) // (not a raw crash / stack trace / HTML error page)
// ------------------------------------------------------------------ // ------------------------------------------------------------------
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
@@ -653,7 +653,7 @@ describe("Multipage TIFF handling", () => {
}); });
// Multipage TIFF should either succeed or return a clean error // Multipage TIFF should either succeed or return a clean error
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
if (res.statusCode === 200) { if (res.statusCode === 200) {
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
@@ -781,11 +781,11 @@ describe("Exotic format error resilience", () => {
// Must not crash (500) — either succeed or return a clean error // Must not crash (500) — either succeed or return a clean error
if (isAsyncFallback(res)) return; if (isAsyncFallback(res)) return;
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
// Response must always be valid JSON // Response must always be valid JSON
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
expect(body.error.length).toBeGreaterThan(0); expect(body.error.length).toBeGreaterThan(0);
@@ -867,10 +867,10 @@ describe("Image enhancement analysis across formats", () => {
}); });
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
} }
@@ -1344,7 +1344,7 @@ describe("Image-to-PDF cross-format matrix", () => {
body: payload, body: payload,
}); });
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
if (res.statusCode === 200) { if (res.statusCode === 200) {
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
@@ -1394,10 +1394,10 @@ describe("Image-to-PDF cross-format matrix", () => {
if (isAsyncFallback(res)) return; if (isAsyncFallback(res)) return;
if (isAsyncFallback(res)) return; if (isAsyncFallback(res)) return;
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
expect(body.error.length).toBeGreaterThan(0); expect(body.error.length).toBeGreaterThan(0);
@@ -1453,10 +1453,10 @@ describe("Watermark-image exotic format error resilience", () => {
}); });
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
expect(body.error.length).toBeGreaterThan(0); expect(body.error.length).toBeGreaterThan(0);
@@ -1505,10 +1505,10 @@ describe("Watermark-image exotic format error resilience", () => {
if (isAsyncFallback(res)) return; if (isAsyncFallback(res)) return;
expect(res.statusCode).not.toBe(500); expect(res.statusCode).not.toBe(500);
expect([200, 400, 422]).toContain(res.statusCode); expect([200, 202, 400, 422]).toContain(res.statusCode);
const body = JSON.parse(res.body); const body = JSON.parse(res.body);
if (res.statusCode !== 200) { if (res.statusCode >= 400) {
expect(body.error).toBeDefined(); expect(body.error).toBeDefined();
expect(typeof body.error).toBe("string"); expect(typeof body.error).toBe("string");
expect(body.error.length).toBeGreaterThan(0); expect(body.error.length).toBeGreaterThan(0);