mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
test: massive test coverage expansion (+1,437 tests, 22 new files)
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
This commit is contained in:
@@ -0,0 +1,709 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user