mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
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:
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -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"] },
|
||||
|
||||
Reference in New Issue
Block a user