diff --git a/tests/integration/beautify.test.ts b/tests/integration/beautify.test.ts
index d724fb9f..8e8c92c8 100644
--- a/tests/integration/beautify.test.ts
+++ b/tests/integration/beautify.test.ts
@@ -489,4 +489,610 @@ describe("Beautify", () => {
const result = JSON.parse(res.body);
expect(result.downloadUrl).toBeDefined();
});
+
+ // ── Dark device frame variants ──────────────────────────────────────
+
+ const DARK_FRAMES = ["iphone-dark", "macbook-dark", "ipad-dark"] as const;
+
+ for (const frame of DARK_FRAMES) {
+ it(`frame: ${frame}`, async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({ frame, shadowPreset: "none" }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ const dlRes = await app.inject({
+ method: "GET",
+ url: result.downloadUrl,
+ headers: { authorization: `Bearer ${adminToken}` },
+ });
+ expect(dlRes.statusCode).toBe(200);
+
+ const meta = await sharp(dlRes.rawPayload).metadata();
+ expect(meta.width).toBeGreaterThan(200);
+ expect(meta.height).toBeGreaterThan(150);
+ });
+ }
+
+ // ── Frame with title ────────────────────────────────────────────────
+
+ it("macOS frame with custom title", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ frame: "macos-light",
+ frameTitle: "My Application",
+ shadowPreset: "none",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.downloadUrl).toBeDefined();
+ });
+
+ it("browser frame with URL title", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ frame: "browser-light",
+ frameTitle: "snapotter.com",
+ shadowPreset: "none",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ const dlRes = await app.inject({
+ method: "GET",
+ url: result.downloadUrl,
+ headers: { authorization: `Bearer ${adminToken}` },
+ });
+ const meta = await sharp(dlRes.rawPayload).metadata();
+ expect(meta.height).toBeGreaterThan(150 + 72);
+ });
+
+ // ── All watermark positions ─────────────────────────────────────────
+
+ const WATERMARK_POSITIONS = [
+ "top-left",
+ "top-right",
+ "bottom-left",
+ "bottom-right",
+ "center",
+ ] as const;
+
+ for (const pos of WATERMARK_POSITIONS) {
+ it(`watermark position: ${pos}`, async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ watermarkText: "SnapOtter",
+ watermarkPosition: pos,
+ watermarkOpacity: 70,
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.downloadUrl).toBeDefined();
+ });
+ }
+
+ // ── WebP output format ──────────────────────────────────────────────
+
+ it("WebP output format", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "solid",
+ backgroundColor: "#ffffff",
+ shadowPreset: "none",
+ borderRadius: 0,
+ frame: "none",
+ outputFormat: "webp",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.downloadUrl).toContain(".webp");
+
+ const dlRes = await app.inject({
+ method: "GET",
+ url: result.downloadUrl,
+ headers: { authorization: `Bearer ${adminToken}` },
+ });
+ const meta = await sharp(dlRes.rawPayload).metadata();
+ expect(meta.format).toBe("webp");
+ });
+
+ it("WebP output forced to PNG when alpha needed", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "transparent",
+ outputFormat: "webp",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ // WebP supports alpha, so it should NOT be forced to PNG
+ expect(result.downloadUrl).toContain(".webp");
+ });
+
+ it("JPEG output forced to PNG when border radius applied", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "photo.jpg", contentType: "image/jpeg", content: JPG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ borderRadius: 20,
+ backgroundType: "solid",
+ shadowPreset: "none",
+ outputFormat: "jpeg",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.downloadUrl).toContain(".png");
+ });
+
+ // ── Feature combinations ────────────────────────────────────────────
+
+ it("all features combined: frame + shadow + gradient + watermark + social", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "linear-gradient",
+ gradientStops: [
+ { color: "#ff6b6b", position: 0 },
+ { color: "#4ecdc4", position: 100 },
+ ],
+ gradientAngle: 45,
+ frame: "macos-dark",
+ frameTitle: "Terminal",
+ shadowPreset: "dramatic",
+ padding: 80,
+ borderRadius: 16,
+ watermarkText: "snapotter.com",
+ watermarkPosition: "bottom-right",
+ watermarkOpacity: 40,
+ socialPreset: "twitter",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ const dlRes = await app.inject({
+ method: "GET",
+ url: result.downloadUrl,
+ headers: { authorization: `Bearer ${adminToken}` },
+ });
+ const meta = await sharp(dlRes.rawPayload).metadata();
+ expect(meta.width).toBe(1600);
+ expect(meta.height).toBe(900);
+ });
+
+ it("device frame + shadow + solid background", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ frame: "iphone",
+ shadowPreset: "medium",
+ backgroundType: "solid",
+ backgroundColor: "#1a1a2e",
+ padding: 100,
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.processedSize).toBeGreaterThan(0);
+ });
+
+ // ── Edge cases ──────────────────────────────────────────────────────
+
+ it("image backgroundType without bgImage falls back to transparent", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "image",
+ shadowPreset: "none",
+ padding: 20,
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ const dlRes = await app.inject({
+ method: "GET",
+ url: result.downloadUrl,
+ headers: { authorization: `Bearer ${adminToken}` },
+ });
+ const meta = await sharp(dlRes.rawPayload).metadata();
+ expect(meta.hasAlpha).toBe(true);
+ });
+
+ it("shadow with zero padding (shadow extends beyond image)", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ padding: 0,
+ shadowPreset: "dramatic",
+ backgroundType: "transparent",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.downloadUrl).toBeDefined();
+ });
+
+ it("gradient angle boundary 0", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "linear-gradient",
+ gradientAngle: 0,
+ gradientStops: [
+ { color: "#000000", position: 0 },
+ { color: "#ffffff", position: 100 },
+ ],
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+ });
+
+ it("gradient angle boundary 360", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "linear-gradient",
+ gradientAngle: 360,
+ gradientStops: [
+ { color: "#000000", position: 0 },
+ { color: "#ffffff", position: 100 },
+ ],
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+ });
+
+ it("large border radius on small image (clamped to half)", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.jpg", contentType: "image/jpeg", content: JPG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ borderRadius: 64,
+ shadowPreset: "none",
+ padding: 20,
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.downloadUrl).toBeDefined();
+ });
+
+ // ── XSS prevention in text inputs ───────────────────────────────────
+
+ it("watermark with HTML/SVG special characters", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ watermarkText: '&"test\'',
+ watermarkPosition: "center",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.downloadUrl).toBeDefined();
+ });
+
+ it("frame title with special characters", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ frame: "macos-light",
+ frameTitle: '
& "quotes"',
+ shadowPreset: "none",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ expect(result.downloadUrl).toBeDefined();
+ });
+
+ // ── Validation edge cases ───────────────────────────────────────────
+
+ it("padding out of range (>256) returns 400", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({ padding: 300 }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(400);
+ });
+
+ it("border radius out of range (>64) returns 400", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({ borderRadius: 100 }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(400);
+ });
+
+ it("shadow blur out of range (>100) returns 400", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({ shadowPreset: "custom", shadowBlur: 200 }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(400);
+ });
+
+ it("invalid background type returns 400", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({ backgroundType: "neon-glow" }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(400);
+ });
+
+ it("invalid frame type returns 400", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({ frame: "android" }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(400);
+ });
+
+ it("gradient with fewer than 2 stops returns 400", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "linear-gradient",
+ gradientStops: [{ color: "#ff0000", position: 0 }],
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(400);
+ });
+
+ it("gradient stop with invalid hex color returns 400", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "linear-gradient",
+ gradientStops: [
+ { color: "red", position: 0 },
+ { color: "#0000ff", position: 100 },
+ ],
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(400);
+ });
+
+ it("malformed JSON settings returns 400", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ { name: "settings", content: "{not valid json" },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(400);
+ });
+
+ // ── Output content verification ─────────────────────────────────────
+
+ it("solid background pixel color is correct", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "solid",
+ backgroundColor: "#ff0000",
+ padding: 50,
+ shadowPreset: "none",
+ borderRadius: 0,
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ const dlRes = await app.inject({
+ method: "GET",
+ url: result.downloadUrl,
+ headers: { authorization: `Bearer ${adminToken}` },
+ });
+
+ const { data, info } = await sharp(dlRes.rawPayload)
+ .raw()
+ .toBuffer({ resolveWithObject: true });
+ const topLeftR = data[0];
+ const topLeftG = data[1];
+ const topLeftB = data[2];
+ expect(topLeftR).toBe(255);
+ expect(topLeftG).toBe(0);
+ expect(topLeftB).toBe(0);
+ expect(info.width).toBe(200 + 100);
+ expect(info.height).toBe(150 + 100);
+ });
+
+ it("transparent background has alpha=0 at corners", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ backgroundType: "transparent",
+ padding: 30,
+ shadowPreset: "none",
+ borderRadius: 0,
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ const dlRes = await app.inject({
+ method: "GET",
+ url: result.downloadUrl,
+ headers: { authorization: `Bearer ${adminToken}` },
+ });
+
+ const { data } = await sharp(dlRes.rawPayload).raw().toBuffer({ resolveWithObject: true });
+ const topLeftAlpha = data[3];
+ expect(topLeftAlpha).toBe(0);
+ });
+
+ it("padding=0, no shadow, no radius preserves original dimensions", async () => {
+ const payload = createMultipartPayload([
+ { name: "file", filename: "test.png", contentType: "image/png", content: PNG },
+ {
+ name: "settings",
+ content: JSON.stringify({
+ padding: 0,
+ shadowPreset: "none",
+ borderRadius: 0,
+ backgroundType: "solid",
+ }),
+ },
+ ]);
+
+ const res = await post("/api/v1/tools/beautify", payload);
+ expect(res.statusCode).toBe(200);
+
+ const result = JSON.parse(res.body);
+ const dlRes = await app.inject({
+ method: "GET",
+ url: result.downloadUrl,
+ headers: { authorization: `Bearer ${adminToken}` },
+ });
+ const meta = await sharp(dlRes.rawPayload).metadata();
+ expect(meta.width).toBe(200);
+ expect(meta.height).toBe(150);
+ });
});