From cd24bb92b60ed7d4a935857733696fe5afa9e049 Mon Sep 17 00:00:00 2001 From: SnapOtter Date: Thu, 14 May 2026 00:09:31 +0800 Subject: [PATCH] fix: update corrupted image test expectations from 422 to 400 validateImageBuffer catches corrupt image data before processing reaches the tool handler, so the correct status code is 400 (bad request) rather than 422 (processing failure). Also fix SVGZ watermark validation by returning early for compressed SVG (Sharp cannot read gzip-compressed SVGZ directly) and passing the actual watermark filename to validateImageBuffer for correct format detection. --- apps/api/src/lib/file-validation.ts | 6 ++++-- apps/api/src/routes/tools/watermark-image.ts | 4 +++- tests/integration/color-palette.test.ts | 5 +++-- tests/integration/compare.test.ts | 15 ++++++++------- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/apps/api/src/lib/file-validation.ts b/apps/api/src/lib/file-validation.ts index 65e95160..b18465f7 100644 --- a/apps/api/src/lib/file-validation.ts +++ b/apps/api/src/lib/file-validation.ts @@ -229,10 +229,12 @@ export async function validateImageBuffer( detectedFormat = "tga"; } - // SVGZ: gzip-compressed SVG, detected by extension + gzip magic + // SVGZ: gzip-compressed SVG, detected by extension + gzip magic. + // Return early because Sharp cannot read compressed SVGZ directly; + // decompression happens later in the route pipeline. if (!detectedFormat && ext === "svgz") { if (buffer.length >= 2 && buffer[0] === 0x1f && buffer[1] === 0x8b) { - detectedFormat = "svg"; + return { valid: true, format: "svg", width: 0, height: 0 }; } } diff --git a/apps/api/src/routes/tools/watermark-image.ts b/apps/api/src/routes/tools/watermark-image.ts index 4a61ce42..f9babe2f 100644 --- a/apps/api/src/routes/tools/watermark-image.ts +++ b/apps/api/src/routes/tools/watermark-image.ts @@ -23,6 +23,7 @@ export function registerWatermarkImage(app: FastifyInstance) { let mainBuffer: Buffer | null = null; let watermarkBuffer: Buffer | null = null; let filename = "image"; + let watermarkFilename = "watermark"; let settingsRaw: string | null = null; try { @@ -36,6 +37,7 @@ export function registerWatermarkImage(app: FastifyInstance) { const buf = Buffer.concat(chunks); if (part.fieldname === "watermark") { watermarkBuffer = buf; + watermarkFilename = sanitizeFilename(part.filename ?? "watermark"); } else { mainBuffer = buf; filename = sanitizeFilename(part.filename ?? "image"); @@ -117,7 +119,7 @@ export function registerWatermarkImage(app: FastifyInstance) { } mainBuffer = await autoOrient(mainBuffer); - const valWm = await validateImageBuffer(watermarkBuffer, "watermark"); + const valWm = await validateImageBuffer(watermarkBuffer, watermarkFilename); if (!valWm.valid) { return reply.status(400).send({ error: `Invalid watermark image: ${valWm.reason}` }); } diff --git a/tests/integration/color-palette.test.ts b/tests/integration/color-palette.test.ts index 89c9a9d1..10a6600b 100644 --- a/tests/integration/color-palette.test.ts +++ b/tests/integration/color-palette.test.ts @@ -158,7 +158,7 @@ describe("Error handling", () => { expect(result.error).toBeDefined(); }); - it("returns 422 for corrupted image data", async () => { + it("returns 400 for corrupted image data", async () => { const badBuffer = Buffer.from("not an image at all"); const { body: payload, contentType } = makeFilePayload(badBuffer, "bad.png", "image/png"); const res = await app.inject({ @@ -170,7 +170,8 @@ describe("Error handling", () => { authorization: `Bearer ${adminToken}`, }, }); - expect(res.statusCode).toBe(422); + // validateImageBuffer catches corrupt data before processing + expect(res.statusCode).toBe(400); }); }); diff --git a/tests/integration/compare.test.ts b/tests/integration/compare.test.ts index 9a1d9bf2..8f39ca11 100644 --- a/tests/integration/compare.test.ts +++ b/tests/integration/compare.test.ts @@ -463,7 +463,7 @@ describe("Compare", () => { // ── Branch coverage: multipart parse error (lines 35-39) ──────────── - it("returns 422 when corrupt image data fails processing", async () => { + it("returns 400 when corrupt image data fails validation", async () => { // Create a buffer that looks like an image but corrupts Sharp const corruptBuffer = Buffer.from("not a real image content at all"); const { body, contentType } = createMultipartPayload([ @@ -481,10 +481,10 @@ describe("Compare", () => { body, }); - // Should return 422 due to processing failure - expect(res.statusCode).toBe(422); + // validateImageBuffer catches corrupt data before processing + expect(res.statusCode).toBe(400); const result = JSON.parse(res.body); - expect(result.error).toMatch(/comparison failed/i); + expect(result.error).toBeDefined(); }); // ── Branch coverage: 1x1 tiny images (line 117-121 area) ─────────── @@ -649,7 +649,7 @@ describe("Compare", () => { // ── Branch coverage: both corrupt images fail processing ─────────── - it("returns 422 when both images are corrupt", async () => { + it("returns 400 when both images are corrupt", async () => { const corrupt1 = Buffer.from("this is not an image"); const corrupt2 = Buffer.from("neither is this one"); const { body, contentType } = createMultipartPayload([ @@ -667,9 +667,10 @@ describe("Compare", () => { body, }); - expect(res.statusCode).toBe(422); + // validateImageBuffer catches corrupt data before processing + expect(res.statusCode).toBe(400); const result = JSON.parse(res.body); - expect(result.error).toMatch(/comparison failed/i); + expect(result.error).toBeDefined(); }); // ── Branch coverage: HEIF content format input ─────────────────────