mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test: add integration tests for new input and output formats
This commit is contained in:
@@ -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,
|
||||
},
|
||||
];
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user