From e9fb35b7cd4c9b9f782f27d33ed72a0d97526f3c Mon Sep 17 00:00:00 2001 From: SnapOtter Date: Fri, 8 May 2026 00:24:45 +0800 Subject: [PATCH] test: add integration tests for new input and output formats --- tests/integration/format-matrix.test.ts | 88 ++++++++++++++ tests/integration/new-formats.test.ts | 147 ++++++++++++++++++++++++ 2 files changed, 235 insertions(+) create mode 100644 tests/integration/new-formats.test.ts diff --git a/tests/integration/format-matrix.test.ts b/tests/integration/format-matrix.test.ts index d0c8d214..0cc00b30 100644 --- a/tests/integration/format-matrix.test.ts +++ b/tests/integration/format-matrix.test.ts @@ -179,6 +179,94 @@ const FORMAT_SAMPLES: FormatSample[] = [ needsHeifDecoder: false, mayFailValidation: true, }, + { + name: "SVGZ", + file: "sample.svgz", + mime: "image/svg+xml", + needsCliDecoder: false, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "JP2", + file: "sample.jp2", + mime: "image/jp2", + needsCliDecoder: true, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "EPS", + file: "sample.eps", + mime: "application/postscript", + needsCliDecoder: true, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "PPM", + file: "sample.ppm", + mime: "image/x-portable-pixmap", + needsCliDecoder: false, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "PGM", + file: "sample.pgm", + mime: "image/x-portable-graymap", + needsCliDecoder: false, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "PBM", + file: "sample.pbm", + mime: "image/x-portable-bitmap", + needsCliDecoder: false, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "DDS", + file: "sample.dds", + mime: "image/vnd.ms-dds", + needsCliDecoder: true, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "CUR", + file: "sample.cur", + mime: "image/x-icon", + needsCliDecoder: true, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "DPX", + file: "sample.dpx", + mime: "image/x-dpx", + needsCliDecoder: true, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "FITS", + file: "sample.fits", + mime: "image/fits", + needsCliDecoder: true, + needsHeifDecoder: false, + mayFailValidation: false, + }, + { + name: "APNG", + file: "sample.apng", + mime: "image/apng", + needsCliDecoder: false, + needsHeifDecoder: false, + mayFailValidation: false, + }, ]; // --------------------------------------------------------------------------- diff --git a/tests/integration/new-formats.test.ts b/tests/integration/new-formats.test.ts new file mode 100644 index 00000000..0110dc69 --- /dev/null +++ b/tests/integration/new-formats.test.ts @@ -0,0 +1,147 @@ +/** + * Integration tests for new input/output format support. + * + * - New output formats: convert PNG to jxl, bmp, ico, jp2, qoi + * - SVGZ input: verify compressed SVG decodes correctly + * - JXL quality: verify lower quality produces smaller files + */ + +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; +import { buildTestApp, createMultipartPayload, loginAsAdmin, type TestApp } from "./test-server.js"; + +const FORMATS_DIR = join(__dirname, "..", "fixtures", "formats"); + +describe("New format support", () => { + 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); + + // --------------------------------------------------------------------------- + // New output format conversions + // --------------------------------------------------------------------------- + const NEW_OUTPUT_FORMATS = ["jxl", "bmp", "ico", "jp2", "qoi"]; + + for (const format of NEW_OUTPUT_FORMATS) { + it(`converts PNG to ${format}`, async () => { + const fileBuffer = readFileSync(join(FORMATS_DIR, "sample.png")); + const { body, contentType } = createMultipartPayload([ + { + name: "file", + filename: "sample.png", + contentType: "image/png", + content: fileBuffer, + }, + { + name: "settings", + content: JSON.stringify({ format, quality: 80 }), + }, + ]); + + const res = await app.inject({ + method: "POST", + url: "/api/v1/tools/convert", + headers: { + authorization: `Bearer ${adminToken}`, + "content-type": contentType, + }, + body, + }); + + // Accept 200 (success) or 422 (encoder not available in test env) + expect([200, 422]).toContain(res.statusCode); + if (res.statusCode === 200) { + const json = JSON.parse(res.body); + expect(json.processedSize).toBeGreaterThan(0); + expect(json.downloadUrl).toBeTruthy(); + } + }); + } + + // --------------------------------------------------------------------------- + // SVGZ input decoding + // --------------------------------------------------------------------------- + it("decodes SVGZ input correctly", async () => { + const fileBuffer = readFileSync(join(FORMATS_DIR, "sample.svgz")); + const { body, contentType } = createMultipartPayload([ + { + name: "file", + filename: "sample.svgz", + contentType: "image/svg+xml", + content: fileBuffer, + }, + { + name: "settings", + content: JSON.stringify({ format: "png" }), + }, + ]); + + const res = await app.inject({ + method: "POST", + url: "/api/v1/tools/convert", + headers: { + authorization: `Bearer ${adminToken}`, + "content-type": contentType, + }, + body, + }); + + expect([200, 422]).toContain(res.statusCode); + if (res.statusCode === 200) { + const json = JSON.parse(res.body); + expect(json.processedSize).toBeGreaterThan(0); + } + }); + + // --------------------------------------------------------------------------- + // JXL quality affects file size + // --------------------------------------------------------------------------- + it("JXL quality affects file size", async () => { + const fileBuffer = readFileSync(join(FORMATS_DIR, "sample.png")); + + const convert = async (quality: number) => { + const { body, contentType } = createMultipartPayload([ + { + name: "file", + filename: "sample.png", + contentType: "image/png", + content: fileBuffer, + }, + { + name: "settings", + content: JSON.stringify({ format: "jxl", quality }), + }, + ]); + return app.inject({ + method: "POST", + url: "/api/v1/tools/convert", + headers: { + authorization: `Bearer ${adminToken}`, + "content-type": contentType, + }, + body, + }); + }; + + const lowQ = await convert(30); + const highQ = await convert(90); + + // Only compare sizes if both succeeded (JXL encoder may not be available) + if (lowQ.statusCode === 200 && highQ.statusCode === 200) { + const lowJson = JSON.parse(lowQ.body); + const highJson = JSON.parse(highQ.body); + expect(lowJson.processedSize).toBeLessThan(highJson.processedSize); + } + }); +});