/** * Integration tests for the pixelate tool (/api/v1/tools/pixelate). * * Covers full-image pixelation, region pixelation, dimension preservation, * region bounds validation, region clamping, and schema validation. */ import { readFileSync } from "node:fs"; import { join } from "node:path"; import sharp from "sharp"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { buildTestApp, createMultipartPayload, loginAsAdmin, type TestApp } from "./test-server.js"; const FIXTURES = join(__dirname, "..", "fixtures"); const PNG = readFileSync(join(FIXTURES, "test-200x150.png")); let testApp: TestApp; let app: TestApp["app"]; let adminToken: string; beforeAll(async () => { testApp = await buildTestApp(); app = testApp.app; adminToken = await loginAsAdmin(app); }, 30_000); afterAll(async () => { await testApp.cleanup(); }, 10_000); describe("Pixelate", () => { it("pixelates entire image with default blockSize", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({}) }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pixelate", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const result = JSON.parse(res.body); expect(result.downloadUrl).toBeDefined(); expect(result.processedSize).toBeGreaterThan(0); }); it("preserves dimensions after pixelation", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({ blockSize: 20 }) }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pixelate", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const result = JSON.parse(res.body); const dlRes = await app.inject({ method: "GET", url: result.downloadUrl, headers: { authorization: `Bearer ${adminToken}` }, }); const meta = await sharp(dlRes.rawPayload).metadata(); expect(meta.width).toBe(200); expect(meta.height).toBe(150); }); it("pixelates a specific region", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({ blockSize: 10, region: { left: 10, top: 10, width: 50, height: 50 }, }), }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pixelate", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const result = JSON.parse(res.body); expect(result.processedSize).toBeGreaterThan(0); const dlRes = await app.inject({ method: "GET", url: result.downloadUrl, headers: { authorization: `Bearer ${adminToken}` }, }); const meta = await sharp(dlRes.rawPayload).metadata(); expect(meta.width).toBe(200); expect(meta.height).toBe(150); }); it("clamps region that slightly exceeds image edges", async () => { // Region extends past right/bottom edge: left 180 + width 50 = 230 > 200 // Backend should clamp width to 20 and height to 20, then succeed. const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({ blockSize: 10, region: { left: 180, top: 130, width: 50, height: 50 }, }), }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pixelate", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const result = JSON.parse(res.body); expect(result.processedSize).toBeGreaterThan(0); const dlRes = await app.inject({ method: "GET", url: result.downloadUrl, headers: { authorization: `Bearer ${adminToken}` }, }); const meta = await sharp(dlRes.rawPayload).metadata(); expect(meta.width).toBe(200); expect(meta.height).toBe(150); }); it("rejects region with origin outside image bounds", async () => { // left=200 is at/past the right edge of a 200px wide image const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({ blockSize: 10, region: { left: 200, top: 0, width: 50, height: 50 }, }), }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pixelate", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(422); const result = JSON.parse(res.body); expect(result.error).toBeDefined(); }); it("rejects blockSize below minimum (1)", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({ blockSize: 1 }) }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/pixelate", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(400); }); });