test: deepen integration tests for 6 simpler tools (HEIC, large file, batch, edge cases)

This commit is contained in:
SnapOtter
2026-05-01 02:46:04 +08:00
parent 710b580cee
commit 270f54950d
5 changed files with 351 additions and 0 deletions
+160
View File
@@ -15,6 +15,9 @@ const FIXTURES = join(__dirname, "..", "fixtures");
const PNG = readFileSync(join(FIXTURES, "test-200x150.png"));
const JPG = readFileSync(join(FIXTURES, "test-100x100.jpg"));
const WEBP = readFileSync(join(FIXTURES, "test-50x50.webp"));
const TINY_PNG = readFileSync(join(FIXTURES, "test-1x1.png"));
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
/** Extract sorted filenames from a ZIP buffer. */
function zipEntryNames(buf: Buffer): string[] {
@@ -614,4 +617,161 @@ describe("Bulk Rename", () => {
expect(filenames).toHaveLength(1);
expect(filenames[0]).toBe("solo-1.png");
});
// ── HEIF file handling ─────────────────────────────────────────
it("renames HEIF files preserving extension", async () => {
const HEIF = readFileSync(join(FIXTURES, "formats", "sample.heif"));
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "photo.heif", contentType: "image/heif", content: HEIF },
{ name: "settings", content: JSON.stringify({ pattern: "renamed-{{index}}" }) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/bulk-rename",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect(res.statusCode).toBe(200);
const filenames = zipEntryNames(res.rawPayload);
expect(filenames).toContain("renamed-1.heif");
});
// ── Large file handling ────────────────────────────────────────
it("renames a large stress image preserving content", async () => {
const LARGE = readFileSync(join(FIXTURES, "content", "stress-large.jpg"));
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "big.jpg", contentType: "image/jpeg", content: LARGE },
{ name: "settings", content: JSON.stringify({ pattern: "large-{{index}}" }) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/bulk-rename",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect(res.statusCode).toBe(200);
const filenames = zipEntryNames(res.rawPayload);
expect(filenames).toContain("large-1.jpg");
const zip = new AdmZip(res.rawPayload);
const entry = zip.getEntry("large-1.jpg");
expect(entry).not.toBeNull();
expect(entry!.getData().length).toBe(LARGE.length);
});
// ── Tiny file handling ─────────────────────────────────────────
it("renames a 1x1 pixel image", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "tiny.png", contentType: "image/png", content: TINY_PNG },
{ name: "settings", content: JSON.stringify({ pattern: "small-{{index}}" }) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/bulk-rename",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect(res.statusCode).toBe(200);
const filenames = zipEntryNames(res.rawPayload);
expect(filenames).toContain("small-1.png");
});
// ── SVG file handling ──────────────────────────────────────────
it("renames SVG files preserving extension", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "icon.svg", contentType: "image/svg+xml", content: SVG },
{ name: "settings", content: JSON.stringify({ pattern: "vector-{{index}}" }) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/bulk-rename",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect(res.statusCode).toBe(200);
const filenames = zipEntryNames(res.rawPayload);
expect(filenames).toContain("vector-1.svg");
});
// ── Animated GIF handling ──────────────────────────────────────
it("renames animated GIF files preserving extension", async () => {
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "animation.gif", contentType: "image/gif", content: GIF },
{ name: "settings", content: JSON.stringify({ pattern: "anim-{{index}}" }) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/bulk-rename",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect(res.statusCode).toBe(200);
const filenames = zipEntryNames(res.rawPayload);
expect(filenames).toContain("anim-1.gif");
});
// ── Mixed format batch (5+ files) ─────────────────────────────
it("renames a batch of 5+ mixed format files", async () => {
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "a.png", contentType: "image/png", content: PNG },
{ name: "file", filename: "b.jpg", contentType: "image/jpeg", content: JPG },
{ name: "file", filename: "c.webp", contentType: "image/webp", content: WEBP },
{ name: "file", filename: "d.gif", contentType: "image/gif", content: GIF },
{ name: "file", filename: "e.svg", contentType: "image/svg+xml", content: SVG },
{ name: "file", filename: "f.heic", contentType: "image/heic", content: HEIC },
{ name: "settings", content: JSON.stringify({ pattern: "batch-{{padded}}" }) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/bulk-rename",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
expect(res.statusCode).toBe(200);
const filenames = zipEntryNames(res.rawPayload);
expect(filenames).toHaveLength(6);
expect(filenames).toContain("batch-1.png");
expect(filenames).toContain("batch-2.jpg");
expect(filenames).toContain("batch-3.webp");
expect(filenames).toContain("batch-4.gif");
expect(filenames).toContain("batch-5.svg");
expect(filenames).toContain("batch-6.heic");
});
});
@@ -17,6 +17,8 @@ const PNG = readFileSync(join(FIXTURES, "test-200x150.png"));
const JPG = readFileSync(join(FIXTURES, "test-100x100.jpg"));
const WEBP = readFileSync(join(FIXTURES, "test-50x50.webp"));
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
let testApp: TestApp;
let app: TestApp["app"];
@@ -583,6 +585,57 @@ describe("Document mode variations", () => {
});
});
// ── Authentication ──────────────────────────────────────────────
describe("Authentication", () => {
it("returns 401 for unauthenticated request", async () => {
const { body: payload, contentType } = makePayload({ mode: "auto" });
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/image-enhancement",
payload,
headers: { "content-type": contentType },
});
expect(res.statusCode).toBe(401);
});
});
// ── HEIF input ─────────────────────────────────────────────────
describe("HEIF input", () => {
it("enhances HEIF (sample.heif) input", async () => {
const HEIF = readFileSync(join(FIXTURES, "formats", "sample.heif"));
const res = await postTool({ mode: "auto" }, HEIF, "sample.heif", "image/heif");
expect([200, 422]).toContain(res.statusCode);
if (res.statusCode === 200) {
const result = JSON.parse(res.body);
expect(result.downloadUrl).toBeDefined();
expect(result.processedSize).toBeGreaterThan(0);
}
});
});
// ── SVG input ──────────────────────────────────────────────────
describe("SVG input", () => {
it("enhances SVG input after rasterization", async () => {
const res = await postTool({ mode: "auto" }, SVG, "test.svg", "image/svg+xml");
expect([200, 400, 422]).toContain(res.statusCode);
if (res.statusCode === 200) {
const result = JSON.parse(res.body);
expect(result.downloadUrl).toBeDefined();
}
});
});
// ── Animated GIF input ─────────────────────────────────────────
describe("Animated GIF input", () => {
it("enhances animated GIF input", async () => {
const res = await postTool({ mode: "auto" }, GIF, "animated.gif", "image/gif");
expect(res.statusCode).toBe(200);
const result = JSON.parse(res.body);
expect(result.downloadUrl).toBeDefined();
expect(result.processedSize).toBeGreaterThan(0);
});
});
// ── Selective correction combinations ───────────────────────────
describe("Selective correction edge cases", () => {
it("enables only contrast and white balance", async () => {
+27
View File
@@ -587,6 +587,33 @@ describe("Info", () => {
expect(typeof result.hasXmp).toBe("boolean");
});
// ── HEIF format info ──────────────────────────────────────────
it("returns correct metadata for HEIF (sample.heif) image", async () => {
const HEIF = readFileSync(join(FIXTURES, "formats", "sample.heif"));
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "sample.heif", contentType: "image/heif", content: HEIF },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/info",
headers: {
authorization: `Bearer ${adminToken}`,
"content-type": contentType,
},
body,
});
// HEIF might need system decoder -- skip if 422
if (res.statusCode === 422) return;
expect(res.statusCode).toBe(200);
const result = JSON.parse(res.body);
expect(result.width).toBeGreaterThan(0);
expect(result.height).toBeGreaterThan(0);
expect(result.filename).toBe("sample.heif");
});
// ── Alpha channel detection ─────────────────────────────────────
it("detects alpha channel in PNG with transparency", async () => {
@@ -723,6 +723,47 @@ describe("Preview endpoint format variations", () => {
});
});
// ── Unauthenticated request ────────────────────────────────────
describe("Authentication", () => {
it("returns 401 for unauthenticated request", async () => {
const { body: payload, contentType } = makePayload({ format: "webp" });
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/optimize-for-web",
payload,
headers: { "content-type": contentType },
});
expect(res.statusCode).toBe(401);
});
});
// ── HEIF input handling ───────────────────────────────────────
describe("HEIF input", () => {
it("optimizes HEIF (sample.heif) input to webp", async () => {
const HEIF = readFileSync(join(FIXTURES, "formats", "sample.heif"));
const res = await postTool({ format: "webp" }, HEIF, "sample.heif", "image/heif");
// HEIF decode may not be available
expect([200, 422]).toContain(res.statusCode);
if (res.statusCode === 200) {
const result = JSON.parse(res.body);
expect(result.downloadUrl).toBeDefined();
expect(result.downloadUrl).toContain(".webp");
}
});
});
// ── Animated GIF input ────────────────────────────────────────
describe("Animated GIF input", () => {
it("optimizes animated GIF to webp", async () => {
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
const res = await postTool({ format: "webp" }, GIF, "animated.gif", "image/gif");
expect(res.statusCode).toBe(200);
const result = JSON.parse(res.body);
expect(result.downloadUrl).toBeDefined();
expect(result.processedSize).toBeGreaterThan(0);
});
});
// ── File with no extension ──────────────────────────────────────
describe("File naming edge cases", () => {
it("handles file without extension", async () => {
+70
View File
@@ -699,4 +699,74 @@ describe("vectorize", () => {
expect(res.statusCode).toBe(200);
});
// ── HEIF input ───────────────────────────────────────────────
it("works with HEIF (sample.heif) input after decoding", async () => {
const HEIF = readFileSync(join(FIXTURES, "formats", "sample.heif"));
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "sample.heif", contentType: "image/heif", content: HEIF },
{ name: "settings", content: JSON.stringify({}) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/vectorize",
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
body,
});
// HEIF may fail if system decoder missing
expect([200, 422]).toContain(res.statusCode);
if (res.statusCode === 200) {
const json = JSON.parse(res.body);
expect(json.downloadUrl).toMatch(/\.svg$/);
}
});
// ── SVG input ────────────────────────────────────────────────
it("vectorizes SVG input (re-traces after rasterization)", async () => {
const SVG_BUF = readFileSync(join(FIXTURES, "test-100x100.svg"));
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "test.svg", contentType: "image/svg+xml", content: SVG_BUF },
{ name: "settings", content: JSON.stringify({ colorMode: "bw" }) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/vectorize",
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
body,
});
// SVG may need rasterization first; accept success or processing error
expect([200, 422]).toContain(res.statusCode);
if (res.statusCode === 200) {
const json = JSON.parse(res.body);
expect(json.downloadUrl).toMatch(/\.svg$/);
}
});
// ── Animated GIF input ───────────────────────────────────────
it("vectorizes animated GIF input", async () => {
const GIF_BUF = readFileSync(join(FIXTURES, "animated.gif"));
const { body, contentType } = createMultipartPayload([
{ name: "file", filename: "animated.gif", contentType: "image/gif", content: GIF_BUF },
{ name: "settings", content: JSON.stringify({ colorMode: "bw" }) },
]);
const res = await app.inject({
method: "POST",
url: "/api/v1/tools/vectorize",
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
body,
});
expect(res.statusCode).toBe(200);
const json = JSON.parse(res.body);
expect(json.downloadUrl).toMatch(/\.svg$/);
expect(json.processedSize).toBeGreaterThan(0);
});
});