/** * Integration tests for the vignette tool (/api/v1/tools/image/vignette). * * Covers vignette application, corner-darker-than-source assertion, * dimension preservation, and schema validation. */ import sharp from "sharp"; import { afterAll, beforeAll, describe, expect, it } from "vitest"; import { fixtures, readFixture } from "../../../fixtures/index.js"; import { buildTestApp, createMultipartPayload, loginAsAdmin, type TestApp, } from "../../test-server.js"; const PNG = readFixture(fixtures.image.base.png200); 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("Vignette", () => { it("applies vignette effect with default settings", 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/image/vignette", 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("corner pixel is darker than the original source corner", async () => { // Create a known white image so we can measure darkening const whiteBuf = await sharp({ create: { width: 100, height: 100, channels: 3, background: { r: 255, g: 255, b: 255 } }, }) .png() .toBuffer(); const { body, contentType } = createMultipartPayload([ { name: "file", filename: "white.png", contentType: "image/png", content: whiteBuf }, { name: "settings", content: JSON.stringify({ strength: 0.8, color: "#000000" }), }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/image/vignette", 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 rawMeta = await sharp(dlRes.rawPayload) .removeAlpha() .raw() .toBuffer({ resolveWithObject: true }); const { data } = rawMeta; // Corner pixel (0,0) should be darker than 255 (the original white) const cornerR = data[0]; expect(cornerR).toBeLessThan(255); }); it("preserves dimensions", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({ strength: 0.5 }), }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/image/vignette", 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("applies custom radius, softness, roundness, and center", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({ radius: 40, softness: 80, roundness: 0, centerX: 30, centerY: 70, }), }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/image/vignette", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(200); const result = JSON.parse(res.body); expect(result.downloadUrl).toBeDefined(); 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 strength above maximum", async () => { const { body, contentType } = createMultipartPayload([ { name: "file", filename: "test.png", contentType: "image/png", content: PNG }, { name: "settings", content: JSON.stringify({ strength: 2 }) }, ]); const res = await app.inject({ method: "POST", url: "/api/v1/tools/image/vignette", headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType, }, body, }); expect(res.statusCode).toBe(400); }); });