mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Expand test coverage across all layers via 14 parallel agents: Unit tests (3,378 total, +534): - First-ever AI sidecar tests (157 tests covering bridge lifecycle, all 12 tool modules) - API route infrastructure (auth, pipeline, batch, settings, teams, roles, audit, api-keys, files, docs) - Lib coverage improvements (audit 7%->95%, worker-pool 33%->100%) - Web store/lib gap fills (features-store, tool-registry) Integration tests (4,403 total, +903): - Expanded 19 tool test files with parameter variations, format edge cases, boundary values - Cross-format matrix: 290 tests covering 14 tools x 17 formats - Adversarial/edge cases: 63 tests for extreme inputs, concurrent requests, corrupted files E2E-Docker (125 new tests): - Expanded 8 spec files + 1 new file covering all 49 tools - Added HEIC/format handling, auth failures, download verification GUI E2E (expanded 28 spec files): - Navigation, responsive layout, keyboard shortcuts - All 51 tool UIs with settings, processing, display modes - Batch/pipeline workflows, settings/RBAC, visual regression - Resilience, accessibility (ARIA, contrast, focus), performance budgets
710 lines
24 KiB
TypeScript
710 lines
24 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
// ─── Meme Generator & Beautify Tools ─────────────────────────────
|
|
// Tests for: meme-generator, beautify
|
|
// These tools handle meme creation with text overlays and screenshot
|
|
// beautification with frames, shadows, backgrounds, and social presets.
|
|
|
|
const FIXTURES = join(process.cwd(), "tests", "fixtures");
|
|
const FORMATS = join(FIXTURES, "formats");
|
|
|
|
let token: string;
|
|
|
|
test.beforeAll(async ({ request }) => {
|
|
const res = await request.post("/api/auth/login", {
|
|
data: { username: "admin", password: "admin" },
|
|
});
|
|
const body = await res.json();
|
|
token = body.token;
|
|
});
|
|
|
|
function fixture(name: string): Buffer {
|
|
return readFileSync(join(FIXTURES, name));
|
|
}
|
|
|
|
function formatFixture(name: string): Buffer {
|
|
return readFileSync(join(FORMATS, name));
|
|
}
|
|
|
|
/**
|
|
* Build a raw multipart/form-data body for multi-file uploads.
|
|
*/
|
|
function buildMultipart(
|
|
files: Array<{ name: string; filename: string; contentType: string; buffer: Buffer }>,
|
|
fields: Array<{ name: string; value: string }>,
|
|
): { body: Buffer; contentType: string } {
|
|
const boundary = `----PlaywrightBoundary${Date.now()}`;
|
|
const parts: Buffer[] = [];
|
|
for (const file of files) {
|
|
parts.push(
|
|
Buffer.from(
|
|
`--${boundary}\r\nContent-Disposition: form-data; name="${file.name}"; filename="${file.filename}"\r\nContent-Type: ${file.contentType}\r\n\r\n`,
|
|
),
|
|
);
|
|
parts.push(file.buffer);
|
|
parts.push(Buffer.from("\r\n"));
|
|
}
|
|
for (const field of fields) {
|
|
parts.push(
|
|
Buffer.from(
|
|
`--${boundary}\r\nContent-Disposition: form-data; name="${field.name}"\r\n\r\n${field.value}\r\n`,
|
|
),
|
|
);
|
|
}
|
|
parts.push(Buffer.from(`--${boundary}--\r\n`));
|
|
return {
|
|
body: Buffer.concat(parts),
|
|
contentType: `multipart/form-data; boundary=${boundary}`,
|
|
};
|
|
}
|
|
|
|
const PNG_200x150 = fixture("test-200x150.png");
|
|
const JPG_100x100 = fixture("test-100x100.jpg");
|
|
const HEIC_200x150 = fixture("test-200x150.heic");
|
|
const JPG_SAMPLE = formatFixture("sample.jpg");
|
|
|
|
// ─── Meme Generator — Custom Image ───────────────────────────────
|
|
|
|
test.describe("Meme Generator — custom image", () => {
|
|
test("meme with top-bottom text layout", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
textLayout: "top-bottom",
|
|
textBoxes: [
|
|
{ id: "top", text: "ONE DOES NOT SIMPLY" },
|
|
{ id: "bottom", text: "WRITE E2E TESTS" },
|
|
],
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("meme with top-only text", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
textLayout: "top-only",
|
|
textBoxes: [{ id: "top", text: "HEADER TEXT" }],
|
|
fontFamily: "arial-black",
|
|
textColor: "#FFFF00",
|
|
strokeColor: "#000000",
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("meme with bottom-only text", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_SAMPLE },
|
|
settings: JSON.stringify({
|
|
textLayout: "bottom-only",
|
|
textBoxes: [{ id: "bottom", text: "BOTTOM TEXT" }],
|
|
fontFamily: "comic-sans",
|
|
allCaps: false,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("meme with center text", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
textLayout: "center",
|
|
textBoxes: [{ id: "center", text: "CENTER IMPACT" }],
|
|
fontSize: 48,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("meme with no text boxes passes image through", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
textLayout: "top-bottom",
|
|
textBoxes: [],
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("meme with custom font and colors", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
textLayout: "top-bottom",
|
|
textBoxes: [
|
|
{ id: "top", text: "CUSTOM FONT" },
|
|
{ id: "bottom", text: "RED STROKE" },
|
|
],
|
|
fontFamily: "bebas-neue",
|
|
textColor: "#FFFFFF",
|
|
strokeColor: "#FF0000",
|
|
textAlign: "left",
|
|
allCaps: true,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("meme on HEIC image", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
|
|
settings: JSON.stringify({
|
|
textLayout: "top-bottom",
|
|
textBoxes: [
|
|
{ id: "top", text: "HEIC" },
|
|
{ id: "bottom", text: "MEME" },
|
|
],
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("meme rejects request with no image and no template", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
data: {
|
|
textLayout: "top-bottom",
|
|
textBoxes: [{ id: "top", text: "NO IMAGE" }],
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(false);
|
|
const body = await res.json();
|
|
expect(body.error).toBeDefined();
|
|
});
|
|
});
|
|
|
|
// ─── Meme Generator — Template Mode ─────────────────────────────
|
|
|
|
test.describe("Meme Generator — template mode", () => {
|
|
test("meme with template ID (JSON body)", async ({ request }) => {
|
|
// First, fetch the available templates to find a valid ID
|
|
// Use a well-known template if available, otherwise skip
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
data: {
|
|
templateId: "drake",
|
|
textBoxes: [
|
|
{ id: "top", text: "Writing tests manually" },
|
|
{ id: "bottom", text: "Using E2E automation" },
|
|
],
|
|
},
|
|
});
|
|
// Template may not exist -- accept both success and 400
|
|
if (res.ok()) {
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
} else {
|
|
const body = await res.json();
|
|
expect(body.error).toBeDefined();
|
|
}
|
|
});
|
|
|
|
test("meme with invalid template ID returns error", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
data: {
|
|
templateId: "nonexistent-template-99999",
|
|
textBoxes: [{ id: "top", text: "TEST" }],
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(false);
|
|
const body = await res.json();
|
|
expect(body.error).toBeDefined();
|
|
});
|
|
});
|
|
|
|
// ─── Beautify — Basic Scenarios ──────────────────────────────────
|
|
|
|
test.describe("Beautify — basic", () => {
|
|
test("beautify with default settings", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("beautify with solid background", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
backgroundType: "solid",
|
|
backgroundColor: "#1a1a2e",
|
|
padding: 40,
|
|
borderRadius: 8,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("beautify with transparent background", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
backgroundType: "transparent",
|
|
padding: 32,
|
|
borderRadius: 16,
|
|
outputFormat: "png",
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with linear gradient background", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
backgroundType: "linear-gradient",
|
|
gradientStops: [
|
|
{ color: "#667eea", position: 0 },
|
|
{ color: "#764ba2", position: 100 },
|
|
],
|
|
gradientAngle: 135,
|
|
padding: 64,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with radial gradient background", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
backgroundType: "radial-gradient",
|
|
gradientStops: [
|
|
{ color: "#ff6b6b", position: 0 },
|
|
{ color: "#556270", position: 100 },
|
|
],
|
|
padding: 48,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Beautify — Shadow Presets ──────────────────────────────────
|
|
|
|
test.describe("Beautify — shadows", () => {
|
|
test("beautify with subtle shadow", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
shadowPreset: "subtle",
|
|
padding: 64,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with dramatic shadow", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_SAMPLE },
|
|
settings: JSON.stringify({
|
|
shadowPreset: "dramatic",
|
|
padding: 80,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with no shadow", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
shadowPreset: "none",
|
|
padding: 32,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Beautify — Frames ──────────────────────────────────────────
|
|
|
|
test.describe("Beautify — frames", () => {
|
|
test("beautify with macOS light frame", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
frame: "macos-light",
|
|
frameTitle: "Terminal",
|
|
padding: 48,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with macOS dark frame", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
frame: "macos-dark",
|
|
frameTitle: "Code Editor",
|
|
backgroundType: "solid",
|
|
backgroundColor: "#2d2d2d",
|
|
padding: 48,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with browser light frame", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
frame: "browser-light",
|
|
frameTitle: "https://snapotter.app",
|
|
padding: 32,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with no frame", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
frame: "none",
|
|
borderRadius: 12,
|
|
padding: 32,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Beautify — Social Presets ──────────────────────────────────
|
|
|
|
test.describe("Beautify — social presets", () => {
|
|
test("beautify for Twitter", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
socialPreset: "twitter",
|
|
padding: 64,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify for Instagram square", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
socialPreset: "instagram-square",
|
|
padding: 48,
|
|
backgroundType: "solid",
|
|
backgroundColor: "#FFFFFF",
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify for LinkedIn", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
socialPreset: "linkedin",
|
|
padding: 40,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Beautify — Watermark ───────────────────────────────────────
|
|
|
|
test.describe("Beautify — watermark", () => {
|
|
test("beautify with watermark text", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
watermarkText: "snapotter.app",
|
|
watermarkPosition: "bottom-right",
|
|
watermarkOpacity: 40,
|
|
padding: 48,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with center watermark", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
watermarkText: "DRAFT",
|
|
watermarkPosition: "center",
|
|
watermarkOpacity: 20,
|
|
padding: 32,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Beautify — Output Formats ──────────────────────────────────
|
|
|
|
test.describe("Beautify — output formats", () => {
|
|
test("beautify with JPEG output", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
outputFormat: "jpeg",
|
|
backgroundType: "solid",
|
|
backgroundColor: "#FFFFFF",
|
|
shadowPreset: "none",
|
|
borderRadius: 0,
|
|
frame: "none",
|
|
padding: 32,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify with WebP output", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
outputFormat: "webp",
|
|
padding: 24,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("beautify HEIC input image", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
|
|
settings: JSON.stringify({
|
|
padding: 32,
|
|
borderRadius: 8,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Beautify — Background Image ────────────────────────────────
|
|
|
|
test.describe("Beautify — background image", () => {
|
|
test("beautify with custom background image", async ({ request }) => {
|
|
const { body, contentType } = buildMultipart(
|
|
[
|
|
{ name: "file", filename: "screenshot.png", contentType: "image/png", buffer: PNG_200x150 },
|
|
{
|
|
name: "backgroundImage",
|
|
filename: "bg.jpg",
|
|
contentType: "image/jpeg",
|
|
buffer: JPG_SAMPLE,
|
|
},
|
|
],
|
|
[
|
|
{
|
|
name: "settings",
|
|
value: JSON.stringify({
|
|
backgroundType: "image",
|
|
padding: 64,
|
|
borderRadius: 12,
|
|
}),
|
|
},
|
|
],
|
|
);
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}`, "Content-Type": contentType },
|
|
data: body,
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const json = await res.json();
|
|
expect(json.downloadUrl).toBeTruthy();
|
|
expect(json.processedSize).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
// ─── Beautify — Validation ──────────────────────────────────────
|
|
|
|
test.describe("Beautify — validation", () => {
|
|
test("beautify rejects request with no file", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
settings: JSON.stringify({ padding: 32 }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(false);
|
|
const body = await res.json();
|
|
expect(body.error).toBeDefined();
|
|
});
|
|
});
|
|
|
|
// ─── Auth Failure ──────────────────────────────────────────────────
|
|
|
|
test.describe("Auth failure", () => {
|
|
test("meme-generator without token returns 401", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/meme-generator", {
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
textLayout: "top-bottom",
|
|
textBoxes: [{ id: "top", text: "TEST" }],
|
|
}),
|
|
},
|
|
});
|
|
expect(res.status()).toBe(401);
|
|
});
|
|
|
|
test("beautify without token returns 401", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/beautify", {
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(res.status()).toBe(401);
|
|
});
|
|
});
|