mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Merge branch 'worktree-agent-ac2de4e3'
This commit is contained in:
@@ -573,6 +573,72 @@ describe("Border", () => {
|
||||
expect(result.processedSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles HEIF input (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: "settings",
|
||||
content: JSON.stringify({ borderWidth: 10, borderColor: "#00FF00" }),
|
||||
},
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/border",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.processedSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles SVG input", async () => {
|
||||
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "icon.svg", contentType: "image/svg+xml", content: SVG },
|
||||
{
|
||||
name: "settings",
|
||||
content: JSON.stringify({ borderWidth: 10, borderColor: "#FF0000" }),
|
||||
},
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/border",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.processedSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("handles animated GIF input", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "anim.gif", contentType: "image/gif", content: GIF },
|
||||
{
|
||||
name: "settings",
|
||||
content: JSON.stringify({ borderWidth: 5, borderColor: "#000000" }),
|
||||
},
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/border",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.processedSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it("rejects negative border width", async () => {
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "test.png", contentType: "image/png", content: PNG },
|
||||
|
||||
@@ -537,3 +537,42 @@ describe("No-op effect", () => {
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── 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);
|
||||
});
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
describe("Animated GIF input", () => {
|
||||
it("processes animated GIF", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const res = await postTool("adjust-colors", { saturation: 30 }, GIF, "anim.gif", "image/gif");
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── SVG input ───────────────────────────────────────────────────
|
||||
describe("SVG input", () => {
|
||||
it("processes SVG input", async () => {
|
||||
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
|
||||
const res = await postTool("adjust-colors", { contrast: 20 }, SVG, "icon.svg", "image/svg+xml");
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -400,6 +400,46 @@ describe("HEIF input", () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
describe("Animated GIF input", () => {
|
||||
it("extracts palette from animated GIF", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const { body: payload, contentType } = makeFilePayload(GIF, "anim.gif", "image/gif");
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/color-palette",
|
||||
payload,
|
||||
headers: {
|
||||
"content-type": contentType,
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
},
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.colors.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ── SVG input ───────────────────────────────────────────────────
|
||||
describe("SVG input", () => {
|
||||
it("extracts palette from SVG image", async () => {
|
||||
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
|
||||
const { body: payload, contentType } = makeFilePayload(SVG, "icon.svg", "image/svg+xml");
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/color-palette",
|
||||
payload,
|
||||
headers: {
|
||||
"content-type": contentType,
|
||||
authorization: `Bearer ${adminToken}`,
|
||||
},
|
||||
});
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.colors.length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Filename preserved in response ──────────────────────────────
|
||||
describe("Filename tracking", () => {
|
||||
it("returns the original filename in the response", async () => {
|
||||
|
||||
@@ -627,6 +627,59 @@ describe("favicon", () => {
|
||||
expect(manifest.name).toBe("my.app.logo");
|
||||
});
|
||||
|
||||
// ── Animated GIF input ────────────────────────────────────────────
|
||||
|
||||
it("generates favicons from animated GIF input", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "anim.gif", contentType: "image/gif", content: GIF },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/favicon",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const zip = new AdmZip(Buffer.from(res.rawPayload));
|
||||
const entries = zip.getEntries().map((e) => e.entryName);
|
||||
expect(entries).toContain("favicon-16x16.png");
|
||||
expect(entries).toContain("favicon.ico");
|
||||
});
|
||||
|
||||
// ── Batch: 5+ images ────────────────────────────────────────────
|
||||
|
||||
it("generates favicons for 5 images in subfolders", async () => {
|
||||
const JPG = readFileSync(join(FIXTURES, "test-100x100.jpg"));
|
||||
const WEBP = readFileSync(join(FIXTURES, "test-50x50.webp"));
|
||||
const TINY = readFileSync(join(FIXTURES, "test-1x1.png"));
|
||||
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.png", contentType: "image/png", content: TINY },
|
||||
{ name: "file", filename: "e.png", contentType: "image/png", content: PNG },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/favicon",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const zip = new AdmZip(Buffer.from(res.rawPayload));
|
||||
const entries = zip.getEntries().map((e) => e.entryName);
|
||||
expect(entries.some((e) => e.startsWith("a/"))).toBe(true);
|
||||
expect(entries.some((e) => e.startsWith("b/"))).toBe(true);
|
||||
expect(entries.some((e) => e.startsWith("c/"))).toBe(true);
|
||||
expect(entries.some((e) => e.startsWith("d/"))).toBe(true);
|
||||
expect(entries.some((e) => e.startsWith("e/"))).toBe(true);
|
||||
});
|
||||
|
||||
// ── HEIF format input ────────────────────────────────────────────
|
||||
|
||||
it("generates favicons from HEIF input", async () => {
|
||||
|
||||
@@ -788,6 +788,29 @@ describe("image-to-base64", () => {
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
it("converts animated GIF to base64", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "anim.gif", contentType: "image/gif", content: GIF },
|
||||
{ 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,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.results).toHaveLength(1);
|
||||
expect(json.results[0].base64.length).toBeGreaterThan(0);
|
||||
expect(json.results[0].dataUri).toMatch(/^data:image\/.+;base64,/);
|
||||
});
|
||||
|
||||
// ── maxHeight=0 means no resize ──────────────────────────────────
|
||||
|
||||
it("maxHeight=0 means no height resize (pass-through)", async () => {
|
||||
|
||||
@@ -739,6 +739,72 @@ describe("image-to-pdf", () => {
|
||||
expect(json.pages).toBe(1);
|
||||
});
|
||||
|
||||
// ── 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({}) },
|
||||
]);
|
||||
|
||||
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);
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
it("converts animated GIF to PDF", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "anim.gif", contentType: "image/gif", content: GIF },
|
||||
{ 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,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.pages).toBe(1);
|
||||
expect(json.processedSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// ── SVG input ───────────────────────────────────────────────────
|
||||
|
||||
it("converts SVG to PDF", async () => {
|
||||
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "icon.svg", contentType: "image/svg+xml", content: SVG },
|
||||
{ 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,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.pages).toBe(1);
|
||||
expect(json.processedSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// ── Rejects invalid orientation ──────────────────────────────────
|
||||
|
||||
it("rejects invalid orientation value", async () => {
|
||||
|
||||
@@ -420,3 +420,51 @@ describe("Target color validation", () => {
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
});
|
||||
|
||||
// ── 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);
|
||||
});
|
||||
|
||||
// ── Animated GIF input ─────────────────────────────────────────
|
||||
describe("Animated GIF input", () => {
|
||||
it("processes animated GIF", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const res = await postTool(
|
||||
{ sourceColor: "#808080", targetColor: "#00FF00", tolerance: 60 },
|
||||
GIF,
|
||||
"anim.gif",
|
||||
"image/gif",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── SVG input ──────────────────────────────────────────────────
|
||||
describe("SVG input", () => {
|
||||
it("processes SVG image", async () => {
|
||||
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
|
||||
const res = await postTool(
|
||||
{ sourceColor: "#000000", targetColor: "#FF0000", tolerance: 30 },
|
||||
SVG,
|
||||
"icon.svg",
|
||||
"image/svg+xml",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -452,3 +452,46 @@ describe("HEIC with different methods", () => {
|
||||
expect(res.statusCode).toBe(200);
|
||||
});
|
||||
});
|
||||
|
||||
// ── 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();
|
||||
}, 60_000);
|
||||
});
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
describe("Animated GIF input", () => {
|
||||
it("processes animated GIF", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const res = await postTool(
|
||||
{ method: "unsharp-mask", amount: 150 },
|
||||
GIF,
|
||||
"anim.gif",
|
||||
"image/gif",
|
||||
);
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
// ── SVG input ───────────────────────────────────────────────────
|
||||
describe("SVG input", () => {
|
||||
it("processes SVG image", async () => {
|
||||
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
|
||||
const res = await postTool({ method: "adaptive" }, SVG, "icon.svg", "image/svg+xml");
|
||||
expect(res.statusCode).toBe(200);
|
||||
const result = JSON.parse(res.body);
|
||||
expect(result.downloadUrl).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -530,6 +530,69 @@ describe("text-overlay", () => {
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
// ── HEIF input ───────────────────────────────────────────────────
|
||||
|
||||
it("handles HEIF input (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: "settings", content: JSON.stringify({ text: "HEIF overlay" }) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/text-overlay",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.downloadUrl).toBeDefined();
|
||||
}, 60_000);
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
it("handles animated GIF input", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "anim.gif", contentType: "image/gif", content: GIF },
|
||||
{ name: "settings", content: JSON.stringify({ text: "GIF overlay" }) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/text-overlay",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.downloadUrl).toBeDefined();
|
||||
});
|
||||
|
||||
// ── SVG input ───────────────────────────────────────────────────
|
||||
|
||||
it("handles SVG input", async () => {
|
||||
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "icon.svg", contentType: "image/svg+xml", content: SVG },
|
||||
{ name: "settings", content: JSON.stringify({ text: "SVG overlay" }) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/text-overlay",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.downloadUrl).toBeDefined();
|
||||
});
|
||||
|
||||
// ── Preserves image dimensions ───────────────────────────────────
|
||||
|
||||
it("preserves image dimensions after text overlay", async () => {
|
||||
|
||||
@@ -483,6 +483,51 @@ describe("watermark-image", () => {
|
||||
expect(res.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
// ── 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 }) },
|
||||
]);
|
||||
|
||||
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);
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
it("processes animated GIF main image", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "anim.gif", contentType: "image/gif", content: GIF },
|
||||
{ name: "watermark", filename: "wm.png", contentType: "image/png", content: SMALL_PNG },
|
||||
{ name: "settings", content: JSON.stringify({ scale: 20 }) },
|
||||
]);
|
||||
|
||||
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.processedSize).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// ── WebP watermark image ─────────────────────────────────────────
|
||||
|
||||
it("processes WebP watermark image", async () => {
|
||||
|
||||
@@ -518,6 +518,69 @@ describe("watermark-text", () => {
|
||||
expect(res.statusCode).toBe(400);
|
||||
});
|
||||
|
||||
// ── HEIF input ───────────────────────────────────────────────────
|
||||
|
||||
it("handles HEIF input (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: "settings", content: JSON.stringify({ text: "HEIF Test" }) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/watermark-text",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.downloadUrl).toBeDefined();
|
||||
}, 60_000);
|
||||
|
||||
// ── Animated GIF input ──────────────────────────────────────────
|
||||
|
||||
it("handles animated GIF input", async () => {
|
||||
const GIF = readFileSync(join(FIXTURES, "animated.gif"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "anim.gif", contentType: "image/gif", content: GIF },
|
||||
{ name: "settings", content: JSON.stringify({ text: "GIF Test" }) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/watermark-text",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.downloadUrl).toBeDefined();
|
||||
});
|
||||
|
||||
// ── SVG input ───────────────────────────────────────────────────
|
||||
|
||||
it("handles SVG input", async () => {
|
||||
const SVG = readFileSync(join(FIXTURES, "test-100x100.svg"));
|
||||
const { body, contentType } = createMultipartPayload([
|
||||
{ name: "file", filename: "icon.svg", contentType: "image/svg+xml", content: SVG },
|
||||
{ name: "settings", content: JSON.stringify({ text: "SVG Test" }) },
|
||||
]);
|
||||
|
||||
const res = await app.inject({
|
||||
method: "POST",
|
||||
url: "/api/v1/tools/watermark-text",
|
||||
headers: { authorization: `Bearer ${adminToken}`, "content-type": contentType },
|
||||
body,
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
const json = JSON.parse(res.body);
|
||||
expect(json.downloadUrl).toBeDefined();
|
||||
});
|
||||
|
||||
// ── Preserves image dimensions ───────────────────────────────────
|
||||
|
||||
it("preserves image dimensions after watermark", async () => {
|
||||
|
||||
Reference in New Issue
Block a user