mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix: add 120s timeout to all HEIF/HEIC integration tests for CI
Comprehensively adds { timeout: 120_000 } to every HEIF/HEIC
integration test across 40 test files. HEIF decoding on CI runners
is significantly slower than local, causing sporadic timeouts at
the default 5s or previous 60s limits.
This commit is contained in:
@@ -484,7 +484,7 @@ describe("Barcode Read", () => {
|
||||
|
||||
// ── Branch coverage: HEIC input (lines 49-152, ensureSharpCompat) ───
|
||||
|
||||
it("reads barcodes from a HEIC image", async () => {
|
||||
it("reads barcodes from a HEIC image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -581,7 +581,7 @@ describe("Barcode Read", () => {
|
||||
|
||||
// ── Branch coverage: portrait HEIC (with exif orientation) ──────────
|
||||
|
||||
it("reads barcodes from portrait HEIC image", async () => {
|
||||
it("reads barcodes from portrait HEIC image", { timeout: 120_000 }, async () => {
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{
|
||||
@@ -680,7 +680,9 @@ describe("Barcode Read", () => {
|
||||
|
||||
// ── Branch coverage: HEIF content format input ─────────────────────
|
||||
|
||||
it("reads barcodes from portrait HEIC image (additional format)", async () => {
|
||||
it("reads barcodes from portrait HEIC image (additional format)", {
|
||||
timeout: 120_000,
|
||||
}, async () => {
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo2.heic", contentType: "image/heic", content: HEIC_PORTRAIT },
|
||||
|
||||
@@ -107,21 +107,26 @@ describe("blur-faces", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input (202 or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input (202 or 501)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/blur-faces",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/blur-faces",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input (200, 422, or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -551,7 +551,7 @@ describe("Border", () => {
|
||||
expect(meta.height).toBe(150 + padding * 2);
|
||||
});
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
it("handles HEIC input", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
|
||||
@@ -572,7 +572,7 @@ describe("Bulk Rename", () => {
|
||||
|
||||
// ── HEIC file handling in rename ────────────────────────────────
|
||||
|
||||
it("renames HEIC files preserving extension", async () => {
|
||||
it("renames HEIC files preserving extension", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
|
||||
@@ -776,7 +776,7 @@ describe("Collage", () => {
|
||||
expect(result.processedSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles HEIC input images", async () => {
|
||||
it("handles HEIC input images", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "f1", filename: "a.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -1429,7 +1429,7 @@ describe("Collage", () => {
|
||||
|
||||
// ── Branch coverage: HEIF content format input ─────────────────────
|
||||
|
||||
it("handles portrait HEIC input images", async () => {
|
||||
it("handles portrait HEIC input images", { timeout: 120_000 }, async () => {
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "f1", filename: "a.heic", contentType: "image/heic", content: HEIC_PORTRAIT },
|
||||
|
||||
@@ -352,7 +352,7 @@ describe("Exposure adjustments", () => {
|
||||
|
||||
// ── HEIC input ──────────────────────────────────────────────────
|
||||
describe("HEIC input", () => {
|
||||
it("processes HEIC input with brightness adjustment", async () => {
|
||||
it("processes HEIC input with brightness adjustment", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const res = await postTool(
|
||||
"adjust-colors",
|
||||
@@ -540,19 +540,24 @@ describe("No-op effect", () => {
|
||||
|
||||
// ── HEIF input ──────────────────────────────────────────────────
|
||||
describe("HEIF input", () => {
|
||||
it("processes HEIF input (motorcycle.heif)", async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const res = await postTool(
|
||||
"adjust-colors",
|
||||
{ brightness: 20 },
|
||||
HEIF,
|
||||
"photo.heif",
|
||||
"image/heif",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
}, 60_000);
|
||||
it(
|
||||
"processes HEIF input (motorcycle.heif)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const res = await postTool(
|
||||
"adjust-colors",
|
||||
{ brightness: 20 },
|
||||
HEIF,
|
||||
"photo.heif",
|
||||
"image/heif",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
});
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
@@ -197,7 +197,7 @@ describe("Multipart error handling", () => {
|
||||
|
||||
// ── HEIC input handling ─────────────────────────────────────────
|
||||
describe("HEIC input", () => {
|
||||
it("extracts palette from HEIC image", async () => {
|
||||
it("extracts palette from HEIC image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body: payload, contentType } = makeFilePayload(HEIC, "photo.heic", "image/heic");
|
||||
const res = await app.inject({
|
||||
|
||||
@@ -145,21 +145,26 @@ describe("colorize", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input (202 or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input (202 or 501)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/colorize",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/colorize",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input (200, 422, or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -270,7 +270,7 @@ describe("Compare", () => {
|
||||
expect(result.similarity).toBeLessThanOrEqual(100);
|
||||
});
|
||||
|
||||
it("compares HEIC vs PNG", async () => {
|
||||
it("compares HEIC vs PNG", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "a.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -293,7 +293,7 @@ describe("Compare", () => {
|
||||
expect(result.dimensions).toBeDefined();
|
||||
});
|
||||
|
||||
it("compares two HEIC images (same file)", async () => {
|
||||
it("compares two HEIC images (same file)", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "a.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -567,7 +567,7 @@ describe("Compare", () => {
|
||||
|
||||
// ── Branch coverage: HEIC vs HEIC (portrait) ───────────────────────
|
||||
|
||||
it("compares HEIC portrait with standard HEIC", async () => {
|
||||
it("compares HEIC portrait with standard HEIC", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
@@ -674,7 +674,7 @@ describe("Compare", () => {
|
||||
|
||||
// ── Branch coverage: HEIF content format input ─────────────────────
|
||||
|
||||
it("compares portrait HEIC image with PNG", async () => {
|
||||
it("compares portrait HEIC image with PNG", { timeout: 120_000 }, async () => {
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "a.heic", contentType: "image/heic", content: HEIC_PORTRAIT },
|
||||
|
||||
@@ -509,7 +509,7 @@ describe("Compose", () => {
|
||||
expect(res.statusCode).toBe(200);
|
||||
});
|
||||
|
||||
it("handles HEIC base image", async () => {
|
||||
it("handles HEIC base image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "base.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -532,7 +532,7 @@ describe("Compose", () => {
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
|
||||
it("handles HEIC overlay image", async () => {
|
||||
it("handles HEIC overlay image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "base.png", contentType: "image/png", content: PNG },
|
||||
@@ -778,7 +778,7 @@ describe("Compose", () => {
|
||||
|
||||
// ── Branch coverage: HEIC overlay with opacity ──────────────────────
|
||||
|
||||
it("applies opacity with HEIC overlay", async () => {
|
||||
it("applies opacity with HEIC overlay", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "base.png", contentType: "image/png", content: PNG },
|
||||
@@ -871,7 +871,7 @@ describe("Compose", () => {
|
||||
|
||||
// ── Branch coverage: HEIF content format input ─────────────────────
|
||||
|
||||
it("handles portrait HEIC base image", async () => {
|
||||
it("handles portrait HEIC base image", { timeout: 120_000 }, async () => {
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "base.heic", contentType: "image/heic", content: HEIC_PORTRAIT },
|
||||
|
||||
@@ -320,30 +320,35 @@ describe("Content-Aware Resize", () => {
|
||||
expect(result.error).toMatch(/invalid image/i);
|
||||
});
|
||||
|
||||
it("handles HEIC input (decodes before processing)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({ width: 150, protectFaces: false }) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input (decodes before processing)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({ width: 150, protectFaces: false }) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/content-aware-resize",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/content-aware-resize",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
// HEIC decode or caire may not be available
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
// HEIC decode or caire may not be available
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
|
||||
if (res.statusCode === 200) {
|
||||
const resBody = JSON.parse(res.body);
|
||||
expect(resBody.downloadUrl).toBeDefined();
|
||||
}
|
||||
}, 60_000);
|
||||
if (res.statusCode === 200) {
|
||||
const resBody = JSON.parse(res.body);
|
||||
expect(resBody.downloadUrl).toBeDefined();
|
||||
}
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("processes both width and height simultaneously", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -164,7 +164,7 @@ describe("Crop", () => {
|
||||
expect(meta.height).toBe(50);
|
||||
});
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
it("handles HEIC input", { timeout: 120_000 }, async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{
|
||||
|
||||
@@ -498,7 +498,7 @@ describe("Error handling", () => {
|
||||
|
||||
// ── HEIC handling ──────────────────────────────────────────────
|
||||
describe("HEIC format handling", () => {
|
||||
it("generates preview for HEIC output", async () => {
|
||||
it("generates preview for HEIC output", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const res = await postTool({ artist: "HEIC Author" }, HEIC, "test.heic", "image/heic");
|
||||
// 422 when exiftool is not installed or heic decode fails
|
||||
@@ -560,7 +560,7 @@ describe("Comprehensive field writing", () => {
|
||||
|
||||
// ── HEIC format handling (extended) ──────────────────────────────
|
||||
describe("HEIC format handling (extended)", () => {
|
||||
it("edits metadata on HEIC input with GPS coordinates", async () => {
|
||||
it("edits metadata on HEIC input with GPS coordinates", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const res = await postTool(
|
||||
{
|
||||
@@ -578,7 +578,7 @@ describe("HEIC format handling (extended)", () => {
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
|
||||
it("edits metadata on HEIC and verifies preview generation", async () => {
|
||||
it("edits metadata on HEIC and verifies preview generation", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const res = await postTool(
|
||||
{ copyright: "HEIC Preview Test" },
|
||||
@@ -751,7 +751,7 @@ describe("Inspect endpoint edge cases", () => {
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
it("inspect returns correct filename for HEIC input", async () => {
|
||||
it("inspect returns correct filename for HEIC input", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body: payload, contentType } = createMultipartPayload([
|
||||
{
|
||||
|
||||
@@ -145,21 +145,26 @@ describe("enhance-faces", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input (202 or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input (202 or 501)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/enhance-faces",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/enhance-faces",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input (200, 422, or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -107,21 +107,26 @@ describe("erase-object", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC image input (202 or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "mask", filename: "mask.png", contentType: "image/png", content: MASK },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC image input (202 or 501)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "mask", filename: "mask.png", contentType: "image/png", content: MASK },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/erase-object",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/erase-object",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel image input (200, 422, or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -240,7 +240,7 @@ describe("favicon", () => {
|
||||
});
|
||||
|
||||
// ── Additional coverage: HEIC input ────────────────────────────
|
||||
it("generates favicons from HEIC input", async () => {
|
||||
it("generates favicons from HEIC input", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -466,7 +466,7 @@ describe("favicon", () => {
|
||||
|
||||
// ── Portrait HEIC input ───────────────────────────────────────────
|
||||
|
||||
it("generates favicons from portrait HEIC", async () => {
|
||||
it("generates favicons from portrait HEIC", { timeout: 120_000 }, async () => {
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{
|
||||
|
||||
@@ -413,7 +413,7 @@ describe("Find Duplicates", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("handles HEIC input images", async () => {
|
||||
it("handles HEIC input images", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "a.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -775,7 +775,7 @@ describe("Find Duplicates", () => {
|
||||
|
||||
// ── Branch coverage: HEIF content format input ─────────────────────
|
||||
|
||||
it("handles portrait HEIC images in duplicate detection", async () => {
|
||||
it("handles portrait HEIC images in duplicate detection", { timeout: 120_000 }, async () => {
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "a.heic", contentType: "image/heic", content: HEIC_PORTRAIT },
|
||||
@@ -965,29 +965,34 @@ describe("Find Duplicates", () => {
|
||||
|
||||
// ── HEIF format input ─────────────────────────────────────────────
|
||||
|
||||
it("handles HEIF input images in duplicate detection", async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "a.heif", contentType: "image/heif", content: HEIF },
|
||||
{ name: "file", filename: "b.heif", contentType: "image/heif", content: HEIF },
|
||||
]);
|
||||
it(
|
||||
"handles HEIF input images in duplicate detection",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "a.heif", contentType: "image/heif", content: HEIF },
|
||||
{ name: "file", filename: "b.heif", contentType: "image/heif", content: HEIF },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/find-duplicates",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/find-duplicates",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.totalImages).toBe(2);
|
||||
expect(result.duplicateGroups).toHaveLength(1);
|
||||
expect(result.duplicateGroups[0].files).toHaveLength(2);
|
||||
}, 60_000);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.totalImages).toBe(2);
|
||||
expect(result.duplicateGroups).toHaveLength(1);
|
||||
expect(result.duplicateGroups[0].files).toHaveLength(2);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
// ── Animated GIF input ────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -438,7 +438,7 @@ describe("Error handling", () => {
|
||||
|
||||
// ── Analyze endpoint HEIC handling ─────────────────────────────
|
||||
describe("Analyze endpoint HEIC handling", () => {
|
||||
it("analyze decodes HEIC input before analysis", async () => {
|
||||
it("analyze decodes HEIC input before analysis", { timeout: 120_000 }, async () => {
|
||||
const { body: payload, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.heic", contentType: "image/heic", content: HEIC },
|
||||
]);
|
||||
@@ -509,7 +509,7 @@ describe("Analyze endpoint invalid image", () => {
|
||||
|
||||
// ── HEIC input enhancement ──────────────────────────────────────
|
||||
describe("HEIC input enhancement", () => {
|
||||
it("enhances HEIC input image", async () => {
|
||||
it("enhances HEIC input image", { timeout: 120_000 }, async () => {
|
||||
const res = await postTool({ mode: "auto" }, HEIC, "test.heic", "image/heic");
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
if (res.statusCode === 200) {
|
||||
@@ -519,7 +519,7 @@ describe("HEIC input enhancement", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("enhances HEIC in portrait mode", async () => {
|
||||
it("enhances HEIC in portrait mode", { timeout: 120_000 }, async () => {
|
||||
const res = await postTool(
|
||||
{ mode: "portrait", intensity: 60 },
|
||||
HEIC,
|
||||
|
||||
@@ -318,7 +318,7 @@ describe("image-to-base64", () => {
|
||||
});
|
||||
|
||||
// ── HEIC input gets converted to JPEG in original mode ───────────
|
||||
it("converts HEIC to JPEG in original mode", async () => {
|
||||
it("converts HEIC to JPEG in original mode", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -534,7 +534,7 @@ describe("image-to-base64", () => {
|
||||
|
||||
// ── HEIC with resize ──────────────────────────────────────────────
|
||||
|
||||
it("converts HEIC to JPEG with resize applied", async () => {
|
||||
it("converts HEIC to JPEG with resize applied", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -556,7 +556,7 @@ describe("image-to-base64", () => {
|
||||
|
||||
// ── HEIC with explicit format conversion ──────────────────────────
|
||||
|
||||
it("converts HEIC to PNG when outputFormat is png", async () => {
|
||||
it("converts HEIC to PNG when outputFormat is png", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -711,24 +711,29 @@ describe("image-to-base64", () => {
|
||||
|
||||
// ── HEIF input (alternative extension) ───────────────────────────
|
||||
|
||||
it("converts HEIF to JPEG in original mode", async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heif", contentType: "image/heif", content: HEIF },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"converts HEIF to JPEG in original mode",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heif", contentType: "image/heif", content: HEIF },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/image-to-base64",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/image-to-base64",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.results[0].mimeType).toBe("image/jpeg");
|
||||
}, 60_000);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.results[0].mimeType).toBe("image/jpeg");
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
// ── Quality=1 minimum ────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -237,7 +237,7 @@ describe("image-to-pdf", () => {
|
||||
|
||||
// ── HEIC input handling ───────────────────────────────────────────
|
||||
|
||||
it("converts HEIC image to PDF", async () => {
|
||||
it("converts HEIC image to PDF", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -741,25 +741,30 @@ describe("image-to-pdf", () => {
|
||||
|
||||
// ── HEIF input ────────────────────────────────────────────────────
|
||||
|
||||
it("converts HEIF image to PDF", async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heif", contentType: "image/heif", content: HEIF },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"converts HEIF image to PDF",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heif", contentType: "image/heif", content: HEIF },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/image-to-pdf",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/image-to-pdf",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.pages).toBe(1);
|
||||
expect(json.processedSize).toBeGreaterThan(0);
|
||||
}, 60_000);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.pages).toBe(1);
|
||||
expect(json.processedSize).toBeGreaterThan(0);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -271,7 +271,7 @@ describe("Info", () => {
|
||||
|
||||
// ── HEIC format info ──────────────────────────────────────────
|
||||
|
||||
it("returns correct metadata for HEIC image", async () => {
|
||||
it("returns correct metadata for HEIC image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.heic", contentType: "image/heic", content: HEIC },
|
||||
|
||||
@@ -152,21 +152,26 @@ describe("noise-removal", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input (202 or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input (202 or 501)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/noise-removal",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/noise-removal",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input (200, 422, or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -146,21 +146,26 @@ describe("ocr", () => {
|
||||
expect([200, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input (200 or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input (200 or 501)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/ocr",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/ocr",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect([200, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([200, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input (200, 422, or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -478,7 +478,7 @@ describe("Preview endpoint SVG handling", () => {
|
||||
|
||||
// ── Preview endpoint: HEIC decoding ────────────────────────────
|
||||
describe("Preview endpoint HEIC handling", () => {
|
||||
it("preview decodes and processes HEIC input", async () => {
|
||||
it("preview decodes and processes HEIC input", { timeout: 120_000 }, async () => {
|
||||
const { body: payload, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({ format: "webp", quality: 60 }) },
|
||||
@@ -603,7 +603,7 @@ describe("Preview endpoint validation", () => {
|
||||
|
||||
// ── HEIC input handling ─────────────────────────────────────────
|
||||
describe("HEIC input", () => {
|
||||
it("optimizes HEIC input to webp", async () => {
|
||||
it("optimizes HEIC input to webp", { timeout: 120_000 }, async () => {
|
||||
const res = await postTool({ format: "webp" }, HEIC, "test.heic", "image/heic");
|
||||
// HEIC decode may not be available
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
@@ -614,7 +614,7 @@ describe("HEIC input", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("optimizes HEIC input to jpeg", async () => {
|
||||
it("optimizes HEIC input to jpeg", { timeout: 120_000 }, async () => {
|
||||
const res = await postTool({ format: "jpeg" }, HEIC, "test.heic", "image/heic");
|
||||
expect([200, 422]).toContain(res.statusCode);
|
||||
if (res.statusCode === 200) {
|
||||
|
||||
@@ -76,20 +76,25 @@ describe("passport-photo/analyze", () => {
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input (200, 422, or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input (200, 422, or 501)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/passport-photo/analyze",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/passport-photo/analyze",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect([200, 422, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([200, 422, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input (200, 422, or 501)", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -147,24 +147,29 @@ describe("Red Eye Removal", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/red-eye-removal",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/red-eye-removal",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -182,24 +182,29 @@ describe("Remove Background", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/remove-background",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/remove-background",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -261,7 +261,7 @@ describe("Alpha format fallback", () => {
|
||||
|
||||
// ── HEIC input handling ─────────────────────────────────────────
|
||||
describe("HEIC input", () => {
|
||||
it("processes HEIC image", async () => {
|
||||
it("processes HEIC image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const res = await postTool(
|
||||
{ sourceColor: "#808080", targetColor: "#FF0000", tolerance: 50 },
|
||||
@@ -423,18 +423,23 @@ describe("Target color validation", () => {
|
||||
|
||||
// ── HEIF input ─────────────────────────────────────────────────
|
||||
describe("HEIF input", () => {
|
||||
it("processes HEIF image (motorcycle.heif)", async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const res = await postTool(
|
||||
{ sourceColor: "#808080", targetColor: "#FF0000", tolerance: 50 },
|
||||
HEIF,
|
||||
"photo.heif",
|
||||
"image/heif",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
}, 60_000);
|
||||
it(
|
||||
"processes HEIF image (motorcycle.heif)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const res = await postTool(
|
||||
{ sourceColor: "#808080", targetColor: "#FF0000", tolerance: 50 },
|
||||
HEIF,
|
||||
"photo.heif",
|
||||
"image/heif",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
});
|
||||
|
||||
// ── Animated GIF input ─────────────────────────────────────────
|
||||
|
||||
@@ -157,7 +157,7 @@ describe("Resize", () => {
|
||||
expect(meta.height).toBe(25); // 50x50 -> 25x25
|
||||
});
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
it("handles HEIC input", { timeout: 120_000 }, async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({ width: 100 }) },
|
||||
|
||||
@@ -183,24 +183,29 @@ describe("Restore Photo", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/restore-photo",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/restore-photo",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -162,7 +162,7 @@ describe("Rotate", () => {
|
||||
expect(meta.height).toBe(100);
|
||||
});
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
it("handles HEIC input", { timeout: 120_000 }, async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({ angle: 90 }) },
|
||||
|
||||
@@ -455,18 +455,23 @@ describe("HEIC with different methods", () => {
|
||||
|
||||
// ── HEIF input ──────────────────────────────────────────────────
|
||||
describe("HEIF input", () => {
|
||||
it("processes HEIF image (motorcycle.heif)", async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const res = await postTool(
|
||||
{ method: "adaptive", sigma: 2.0 },
|
||||
HEIF,
|
||||
"photo.heif",
|
||||
"image/heif",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
}, 120_000);
|
||||
it(
|
||||
"processes HEIF image (motorcycle.heif)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const res = await postTool(
|
||||
{ method: "adaptive", sigma: 2.0 },
|
||||
HEIF,
|
||||
"photo.heif",
|
||||
"image/heif",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
},
|
||||
120_000,
|
||||
);
|
||||
});
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
@@ -274,27 +274,32 @@ describe("Smart Crop", () => {
|
||||
}
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{
|
||||
name: "settings",
|
||||
content: JSON.stringify({ mode: "subject", width: 100, height: 100 }),
|
||||
},
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{
|
||||
name: "settings",
|
||||
content: JSON.stringify({ mode: "subject", width: 100, height: 100 }),
|
||||
},
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/smart-crop",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/smart-crop",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
expect([200, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([200, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -603,7 +603,7 @@ describe("Split", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("splits a HEIC input image", async () => {
|
||||
it("splits a HEIC input image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -751,7 +751,9 @@ describe("Split", () => {
|
||||
|
||||
// ── Branch coverage: HEIC with grid split (lines 59-165) ────────────
|
||||
|
||||
it("splits a HEIC image into a grid without format conversion", async () => {
|
||||
it("splits a HEIC image into a grid without format conversion", {
|
||||
timeout: 120_000,
|
||||
}, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -779,7 +781,7 @@ describe("Split", () => {
|
||||
|
||||
// ── Branch coverage: custom tile dimensions with HEIC ───────────────
|
||||
|
||||
it("splits HEIC image using fixed tile dimensions", async () => {
|
||||
it("splits HEIC image using fixed tile dimensions", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -1058,7 +1060,7 @@ describe("Split", () => {
|
||||
|
||||
// ── Branch coverage: HEIC split with webp output ────────────────────
|
||||
|
||||
it("splits HEIC image with webp output format", async () => {
|
||||
it("splits HEIC image with webp output format", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
|
||||
@@ -927,7 +927,7 @@ describe("Stitch", () => {
|
||||
expect(meta.format).toBe("heif");
|
||||
});
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
it("handles HEIC input", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "f1", filename: "a.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -1459,7 +1459,7 @@ describe("Stitch", () => {
|
||||
|
||||
// ── Branch coverage: HEIF content format input ─────────────────────
|
||||
|
||||
it("handles portrait HEIC input in stitching", async () => {
|
||||
it("handles portrait HEIC input in stitching", { timeout: 120_000 }, async () => {
|
||||
const HEIC_PORTRAIT = readFileSync(join(FIXTURES, "test-portrait.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "f1", filename: "a.heic", contentType: "image/heic", content: HEIC_PORTRAIT },
|
||||
|
||||
@@ -598,7 +598,7 @@ describe("Inspect endpoint: ICC and XMP parsing", () => {
|
||||
|
||||
// ── HEIC format handling ────────────────────────────────────────
|
||||
describe("HEIC format handling", () => {
|
||||
it("strips metadata from HEIC input", async () => {
|
||||
it("strips metadata from HEIC input", { timeout: 120_000 }, async () => {
|
||||
const heic = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const res = await postTool({ stripAll: true }, heic, "test.heic", "image/heic");
|
||||
// HEIC decode may not be available
|
||||
@@ -610,7 +610,7 @@ describe("HEIC format handling", () => {
|
||||
}
|
||||
});
|
||||
|
||||
it("strips selective metadata from HEIC input", async () => {
|
||||
it("strips selective metadata from HEIC input", { timeout: 120_000 }, async () => {
|
||||
const heic = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const res = await postTool(
|
||||
{ stripAll: false, stripExif: true, stripGps: true },
|
||||
|
||||
@@ -717,7 +717,7 @@ describe("svg-to-raster", () => {
|
||||
|
||||
// ── Branch coverage: HEIF output with preview (line 363, 404) ───────
|
||||
|
||||
it("converts to heif format and generates a preview", async () => {
|
||||
it("converts to heif format and generates a preview", { timeout: 120_000 }, async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.svg", contentType: "image/svg+xml", content: SVG },
|
||||
{ name: "settings", content: JSON.stringify({ outputFormat: "heif" }) },
|
||||
@@ -997,7 +997,7 @@ describe("svg-to-raster", () => {
|
||||
|
||||
// ── Branch coverage: batch with heif output ────────────────────────
|
||||
|
||||
it("batch converts SVGs to heif format", async () => {
|
||||
it("batch converts SVGs to heif format", { timeout: 120_000 }, async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "a.svg", contentType: "image/svg+xml", content: SVG },
|
||||
{ name: "file", filename: "b.svg", contentType: "image/svg+xml", content: SVG },
|
||||
@@ -1063,7 +1063,7 @@ describe("svg-to-raster", () => {
|
||||
|
||||
// ── Branch coverage: heif + background color combination ───────────
|
||||
|
||||
it("converts to heif format with background color applied", async () => {
|
||||
it("converts to heif format with background color applied", { timeout: 120_000 }, async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.svg", contentType: "image/svg+xml", content: SVG },
|
||||
{
|
||||
|
||||
@@ -238,7 +238,7 @@ describe("text-overlay", () => {
|
||||
|
||||
// ── HEIC input handling ───────────────────────────────────────────
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
it("handles HEIC input", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -534,7 +534,7 @@ describe("text-overlay", () => {
|
||||
|
||||
it(
|
||||
"handles HEIF input (motorcycle.heif)",
|
||||
{ timeout: 60_000 },
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -200,24 +200,29 @@ describe("Upscale", () => {
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
it(
|
||||
"handles HEIC input",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
{ name: "settings", content: JSON.stringify({}) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/upscale",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/upscale",
|
||||
headers: {
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
"content-type": contentType,
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
}, 60_000);
|
||||
expect([202, 501]).toContain(res.statusCode);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
it("handles 1x1 pixel input", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
@@ -390,7 +390,7 @@ describe("vectorize", () => {
|
||||
expect(res.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
it("works with HEIC input after decoding", async () => {
|
||||
it("works with HEIC input after decoding", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.heic", contentType: "image/heic", content: HEIC },
|
||||
|
||||
@@ -233,7 +233,7 @@ describe("watermark-image", () => {
|
||||
|
||||
// ── HEIC input handling ───────────────────────────────────────────
|
||||
|
||||
it("processes HEIC main image", async () => {
|
||||
it("processes HEIC main image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -381,7 +381,7 @@ describe("watermark-image", () => {
|
||||
|
||||
// ── HEIC watermark image ─────────────────────────────────────────
|
||||
|
||||
it("processes HEIC watermark image", async () => {
|
||||
it("processes HEIC watermark image", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "main.png", contentType: "image/png", content: PNG },
|
||||
@@ -485,26 +485,31 @@ describe("watermark-image", () => {
|
||||
|
||||
// ── HEIF input ────────────────────────────────────────────────────
|
||||
|
||||
it("processes HEIF main image (motorcycle.heif)", async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heif", contentType: "image/heif", content: HEIF },
|
||||
{ name: "watermark", filename: "wm.png", contentType: "image/png", content: SMALL_PNG },
|
||||
{ name: "settings", content: JSON.stringify({ scale: 10 }) },
|
||||
]);
|
||||
it(
|
||||
"processes HEIF main image (motorcycle.heif)",
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heif", contentType: "image/heif", content: HEIF },
|
||||
{ name: "watermark", filename: "wm.png", contentType: "image/png", content: SMALL_PNG },
|
||||
{ name: "settings", content: JSON.stringify({ scale: 10 }) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/watermark-image",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/watermark-image",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.downloadUrl).toBeDefined();
|
||||
expect(json.processedSize).toBeGreaterThan(0);
|
||||
}, 60_000);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.downloadUrl).toBeDefined();
|
||||
expect(json.processedSize).toBeGreaterThan(0);
|
||||
},
|
||||
60_000,
|
||||
);
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -254,7 +254,7 @@ describe("watermark-text", () => {
|
||||
|
||||
// ── HEIC input handling ───────────────────────────────────────────
|
||||
|
||||
it("handles HEIC input", async () => {
|
||||
it("handles HEIC input", { timeout: 120_000 }, async () => {
|
||||
const HEIC = readFileSync(join(FIXTURES, "test-200x150.heic"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "photo.heic", contentType: "image/heic", content: HEIC },
|
||||
@@ -522,7 +522,7 @@ describe("watermark-text", () => {
|
||||
|
||||
it(
|
||||
"handles HEIF input (motorcycle.heif)",
|
||||
{ timeout: 60_000 },
|
||||
{ timeout: 120_000 },
|
||||
async () => {
|
||||
const HEIF = readFileSync(join(FIXTURES, "content", "motorcycle.heif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
|
||||
Reference in New Issue
Block a user