test: expand exotic format coverage across batch, validation, and detection

Batch processing (batch.test.ts):
- 15 format tests (PBM, PGM, PPM, TIFF, QOI, JP2, SVGZ, DDS, DPX, EPS,
  TGA, PSD, HDR, ICO, CUR) verifying batch compress produces valid ZIP
- 2 delegate-dependent tests (FITS, EXR) that accept 200 or 422
- Mixed-format batch test (PBM + TIFF + QOI in one request)

Validation (utilities.test.ts):
- 15 new tests for validateImageBuffer covering PBM, PGM, PPM, DDS, DPX,
  FITS, JP2, QOI, SVGZ, EPS, CUR, HEIF, APNG, DNG + PDF rejection

Format detection (detect.test.ts):
- 8 fixture-based detection tests (PPM, DNG, JP2, SVGZ, PBM, PGM, HDR, TGA)
- 4 synthetic magic byte tests (PPM P3/P6, JP2 box, J2K codestream)
This commit is contained in:
SnapOtter
2026-05-11 17:45:42 +08:00
parent 50d1f532d1
commit 7d9612784e
3 changed files with 294 additions and 0 deletions
+102
View File
@@ -316,6 +316,108 @@ describe("Batch with default settings", () => {
});
});
// ── Exotic format batch processing ─────────────────────────────
describe("Exotic format batch processing", () => {
const FORMATS_DIR = join(FIXTURES, "formats");
const exoticFormats = [
{ ext: "pbm", mime: "image/x-portable-bitmap" },
{ ext: "pgm", mime: "image/x-portable-graymap" },
{ ext: "ppm", mime: "image/x-portable-pixmap" },
{ ext: "tiff", mime: "image/tiff" },
{ ext: "qoi", mime: "application/octet-stream" },
{ ext: "jp2", mime: "image/jp2" },
{ ext: "svgz", mime: "image/svg+xml" },
{ ext: "dds", mime: "application/octet-stream" },
{ ext: "dpx", mime: "application/octet-stream" },
{ ext: "eps", mime: "application/postscript" },
{ ext: "tga", mime: "image/x-tga" },
{ ext: "psd", mime: "image/vnd.adobe.photoshop" },
{ ext: "hdr", mime: "image/vnd.radiance" },
{ ext: "ico", mime: "image/x-icon" },
{ ext: "cur", mime: "image/x-icon" },
];
const formatsNeedingDelegates = [
{ ext: "fits", mime: "application/fits" },
{ ext: "exr", mime: "image/x-exr" },
];
const settings = JSON.stringify({ mode: "quality", quality: 50 });
async function batchCompress(ext: string, mime: string) {
const fileBuffer = readFileSync(join(FORMATS_DIR, `sample.${ext}`));
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: `sample.${ext}`, contentType: mime, content: fileBuffer },
{ name: "settings", content: settings },
]);
return app.inject({
method: "POST",
url: "/api/v1/tools/compress/batch",
headers: { "content-type": contentType, authorization: `Bearer ${adminToken}` },
body,
});
}
for (const { ext, mime } of exoticFormats) {
it(`processes ${ext.toUpperCase()} through batch compress`, async () => {
const res = await batchCompress(ext, mime);
expect(res.statusCode).toBe(200);
const fileResults = JSON.parse(res.headers["x-file-results"] as string);
expect(fileResults["0"]).toBeDefined();
});
}
for (const { ext, mime } of formatsNeedingDelegates) {
it(`processes ${ext.toUpperCase()} through batch compress (needs ImageMagick delegate)`, async () => {
const res = await batchCompress(ext, mime);
expect([200, 422]).toContain(res.statusCode);
});
}
it("processes mixed exotic formats (PBM + TIFF + QOI) in one batch", async () => {
const pbm = readFileSync(join(FORMATS_DIR, "sample.pbm"));
const tiff = readFileSync(join(FORMATS_DIR, "sample.tiff"));
const qoi = readFileSync(join(FORMATS_DIR, "sample.qoi"));
const { body, contentType } = createMultipartPayload([
{
name: "file",
filename: "sample.pbm",
contentType: "image/x-portable-bitmap",
content: pbm,
},
{ name: "file", filename: "sample.tiff", contentType: "image/tiff", content: tiff },
{
name: "file",
filename: "sample.qoi",
contentType: "application/octet-stream",
content: qoi,
},
{ name: "settings", content: settings },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/compress/batch",
headers: {
"content-type": contentType,
authorization: `Bearer ${adminToken}`,
},
body,
});
expect(res.statusCode).toBe(200);
const fileResults = JSON.parse(res.headers["x-file-results"] as string);
expect(fileResults["0"]).toBeDefined();
expect(fileResults["1"]).toBeDefined();
expect(fileResults["2"]).toBeDefined();
const zip = new AdmZip(res.rawPayload);
expect(zip.getEntries().length).toBe(3);
});
});
// ── Batch preserves upload order ────────────────────────────────
describe("Batch preserves upload order", () => {
it("X-File-Results indices match upload order", async () => {
+122
View File
@@ -567,6 +567,128 @@ describe("validateImageBuffer", () => {
expect(result.format).toBe("jxl");
}
});
// -- Exotic formats from fixture files ------------------------------------
describe("validates exotic formats", () => {
const { readFileSync } = require("node:fs");
const FORMATS_DIR = join(FIXTURES, "formats");
it("accepts PBM file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.pbm"));
const result = await validateImageBuffer(buf, "sample.pbm");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("pbm");
});
it("accepts PGM file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.pgm"));
const result = await validateImageBuffer(buf, "sample.pgm");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("pgm");
});
it("accepts PPM file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.ppm"));
const result = await validateImageBuffer(buf, "sample.ppm");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("ppm");
});
it("accepts DDS file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.dds"));
const result = await validateImageBuffer(buf, "sample.dds");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("dds");
});
it("accepts DPX file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.dpx"));
const result = await validateImageBuffer(buf, "sample.dpx");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("dpx");
});
it("accepts FITS file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.fits"));
const result = await validateImageBuffer(buf, "sample.fits");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("fits");
});
it("accepts JP2 file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.jp2"));
const result = await validateImageBuffer(buf, "sample.jp2");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("jp2");
});
it("accepts QOI file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.qoi"));
const result = await validateImageBuffer(buf, "sample.qoi");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("qoi");
});
it("accepts SVGZ file (detected as svg)", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.svgz"));
const result = await validateImageBuffer(buf, "sample.svgz");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("svg");
});
it("accepts EPS file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.eps"));
const result = await validateImageBuffer(buf, "sample.eps");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("eps");
});
it("accepts CUR file with CUR magic bytes", async () => {
// Build a buffer with proper CUR magic (00 00 02 00) since the fixture
// has ICO magic bytes (00 00 01 00) which gets detected as ico instead
const curBuf = Buffer.alloc(64);
curBuf[0] = 0x00;
curBuf[1] = 0x00;
curBuf[2] = 0x02;
curBuf[3] = 0x00;
curBuf[4] = 0x01;
const result = await validateImageBuffer(curBuf, "sample.cur");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("cur");
});
it("accepts HEIF file", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.heif"));
const result = await validateImageBuffer(buf, "sample.heif");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("heif");
});
it("accepts APNG file (detected as png)", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.apng"));
const result = await validateImageBuffer(buf, "sample.apng");
expect(result.valid).toBe(true);
if (result.valid) expect(result.format).toBe("png");
});
it("accepts DNG file (detected as raw)", async () => {
const buf = readFileSync(join(FORMATS_DIR, "sample.dng"));
const result = await validateImageBuffer(buf, "sample.dng");
expect(result.valid).toBe(true);
if (result.valid) {
expect(result.format).toBe("raw");
expect(result.width).toBe(0);
expect(result.height).toBe(0);
}
});
it("rejects a PDF file", async () => {
const pdfBuffer = Buffer.from("%PDF-1.4 fake content");
const result = await validateImageBuffer(pdfBuffer, "test.pdf");
expect(result.valid).toBe(false);
});
});
});
// ---------------------------------------------------------------------------
+70
View File
@@ -223,6 +223,30 @@ describe("detectFormat", () => {
expect(format).toBe("jxl");
});
it("detects PPM P3 (ASCII) magic bytes", async () => {
// P3 = 0x50 0x33
const buf = Buffer.from([0x50, 0x33, 0x0a, 0x23, 0, 0, 0, 0, 0, 0, 0, 0]);
expect(await detectFormat(buf)).toBe("ppm");
});
it("detects PPM P6 (binary) magic bytes", async () => {
// P6 = 0x50 0x36
const buf = Buffer.from([0x50, 0x36, 0x0a, 0x36, 0, 0, 0, 0, 0, 0, 0, 0]);
expect(await detectFormat(buf)).toBe("ppm");
});
it("detects JP2 box signature magic bytes", async () => {
const buf = Buffer.from([
0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, 0, 0, 0, 0,
]);
expect(await detectFormat(buf)).toBe("jp2");
});
it("detects J2K raw codestream magic bytes", async () => {
const buf = Buffer.from([0xff, 0x4f, 0xff, 0x51, 0, 0, 0, 0, 0, 0, 0, 0]);
expect(await detectFormat(buf)).toBe("jp2");
});
it("returns 'unknown' for unrecognized bytes", async () => {
const buf = Buffer.from([0xde, 0xad, 0xbe, 0xef, 0, 0, 0, 0, 0, 0, 0, 0]);
const format = await detectFormat(buf);
@@ -275,6 +299,52 @@ describe("detectFormat", () => {
});
});
describe("exotic format detection from fixtures", () => {
it("detects sample.ppm as ppm (P6 magic bytes)", async () => {
const buffer = readFileSync(path.join(FORMATS_DIR, "sample.ppm"));
expect(await detectFormat(buffer)).toBe("ppm");
});
it("detects sample.dng as tiff (DNG shares TIFF magic bytes)", async () => {
const buffer = readFileSync(path.join(FORMATS_DIR, "sample.dng"));
expect(await detectFormat(buffer)).toBe("tiff");
});
it("detects sample.jp2 as jp2 (JPEG 2000 box signature)", async () => {
const buffer = readFileSync(path.join(FORMATS_DIR, "sample.jp2"));
expect(await detectFormat(buffer)).toBe("jp2");
});
it("detects sample.svgz as svg (Sharp reads gzip-compressed SVG)", async () => {
const buffer = readFileSync(path.join(FORMATS_DIR, "sample.svgz"));
expect(await detectFormat(buffer)).toBe("svg");
});
// PBM (P4) and PGM (P5) have no magic byte entries and Sharp cannot parse them
it("cannot detect sample.pbm (no P4 magic bytes registered)", async () => {
const buffer = readFileSync(path.join(FORMATS_DIR, "sample.pbm"));
expect(await detectFormat(buffer)).toBe("unknown");
});
it("cannot detect sample.pgm (no P5 magic bytes registered)", async () => {
const buffer = readFileSync(path.join(FORMATS_DIR, "sample.pgm"));
expect(await detectFormat(buffer)).toBe("unknown");
});
// HDR has a text header (#?RGBE) not matched by magic byte table
it("cannot detect sample.hdr (Radiance text header not in magic bytes)", async () => {
const buffer = readFileSync(path.join(FORMATS_DIR, "sample.hdr"));
expect(await detectFormat(buffer)).toBe("unknown");
});
// TGA has no reliable magic bytes; its header can collide with other formats
it("does not reliably detect sample.tga (no TGA-specific magic bytes)", async () => {
const buffer = readFileSync(path.join(FORMATS_DIR, "sample.tga"));
// TGA detection is extension-based in validateImageBuffer, not in detectFormat
expect(await detectFormat(buffer)).not.toBe("tga");
});
});
describe("real fixture format detection", () => {
const fixtures: Array<{ file: string; expected: string[] }> = [
{ file: "sample.dds", expected: ["dds", "unknown"] },