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 ─────────────────────