mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
435 lines
16 KiB
TypeScript
435 lines
16 KiB
TypeScript
import { readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { expect, test } from "@playwright/test";
|
|
|
|
// ─── Adjustment Tools ───────────────────────────────────────────────
|
|
// Tests for: color-adjustments, sharpening, replace-color, strip-metadata,
|
|
// edit-metadata, optimize-for-web, image-enhancement
|
|
// These tools modify image properties without changing dimensions.
|
|
|
|
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));
|
|
}
|
|
|
|
const PNG_200x150 = fixture("test-200x150.png");
|
|
const JPG_100x100 = fixture("test-100x100.jpg");
|
|
const JPG_WITH_EXIF = fixture("test-with-exif.jpg");
|
|
const JPG_SAMPLE = readFileSync(join(FORMATS, "sample.jpg"));
|
|
|
|
// ─── Color Adjustments ──────────────────────────────────────────────
|
|
|
|
test.describe("Color Adjustments", () => {
|
|
test("adjust brightness", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/adjust-colors", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({ brightness: 20 }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("adjust contrast", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/adjust-colors", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({ contrast: 30 }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("adjust saturation", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/adjust-colors", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({ saturation: -20 }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("apply grayscale", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/adjust-colors", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({ grayscale: true }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("multiple adjustments at once", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/adjust-colors", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
brightness: 10,
|
|
contrast: 15,
|
|
saturation: -10,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Sharpening ─────────────────────────────────────────────────────
|
|
|
|
test.describe("Sharpening", () => {
|
|
test("sharpen with default settings", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/sharpening", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("sharpen with explicit sigma and amount", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/sharpening", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({ sigma: 1.5, amount: 1.0 }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("heavy sharpening changes file size", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/sharpening", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "sample.jpg", mimeType: "image/jpeg", buffer: JPG_SAMPLE },
|
|
settings: JSON.stringify({ sigma: 3.0, amount: 2.0 }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
// Sharpening adds high-frequency detail, so file may differ in size
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
});
|
|
|
|
// ─── Replace Color ──────────────────────────────────────────────────
|
|
|
|
test.describe("Replace Color", () => {
|
|
test("replace white with black", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/replace-color", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
targetColor: "#ffffff",
|
|
replacementColor: "#000000",
|
|
tolerance: 30,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("replace with low tolerance", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/replace-color", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
targetColor: "#ff0000",
|
|
replacementColor: "#00ff00",
|
|
tolerance: 5,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("replace with high tolerance", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/replace-color", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
targetColor: "#808080",
|
|
replacementColor: "#0000ff",
|
|
tolerance: 80,
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Strip Metadata ─────────────────────────────────────────────────
|
|
|
|
test.describe("Strip Metadata", () => {
|
|
test("strip metadata from JPEG with EXIF", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/strip-metadata", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_WITH_EXIF },
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
// Stripped image should be smaller or equal (no EXIF overhead)
|
|
expect(body.processedSize).toBeLessThanOrEqual(body.originalSize);
|
|
});
|
|
|
|
test("strip metadata from PNG", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/strip-metadata", {
|
|
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();
|
|
});
|
|
|
|
test("stripped image has no EXIF data", async ({ request }) => {
|
|
// First strip metadata
|
|
const stripRes = await request.post("/api/v1/tools/strip-metadata", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_WITH_EXIF },
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(stripRes.ok()).toBe(true);
|
|
const stripBody = await stripRes.json();
|
|
|
|
// Download the stripped image
|
|
const downloadRes = await request.get(stripBody.downloadUrl, {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
expect(downloadRes.ok()).toBe(true);
|
|
const strippedBuffer = Buffer.from(await downloadRes.body());
|
|
|
|
// Verify stripped image via info tool
|
|
const infoRes = await request.post("/api/v1/tools/info", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "stripped.jpg", mimeType: "image/jpeg", buffer: strippedBuffer },
|
|
},
|
|
});
|
|
expect(infoRes.ok()).toBe(true);
|
|
const infoBody = await infoRes.json();
|
|
expect(infoBody.hasExif).toBe(false);
|
|
});
|
|
});
|
|
|
|
// ─── Edit Metadata ──────────────────────────────────────────────────
|
|
|
|
test.describe("Edit Metadata", () => {
|
|
test("set artist and copyright fields", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/edit-metadata", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({
|
|
artist: "Test Artist",
|
|
copyright: "CC-BY-4.0",
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("edit metadata on PNG", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/edit-metadata", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({
|
|
title: "Test Image",
|
|
description: "A test image for e2e testing",
|
|
}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Optimize for Web ───────────────────────────────────────────────
|
|
|
|
test.describe("Optimize for Web", () => {
|
|
test("optimize with default settings", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/optimize-for-web", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "sample.jpg", mimeType: "image/jpeg", buffer: JPG_SAMPLE },
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("optimize with maxWidth constraint", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/optimize-for-web", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "sample.jpg", mimeType: "image/jpeg", buffer: JPG_SAMPLE },
|
|
settings: JSON.stringify({ maxWidth: 800, quality: 75 }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
// Optimized should be smaller than the original large sample
|
|
expect(body.processedSize).toBeLessThan(body.originalSize);
|
|
});
|
|
|
|
test("optimize PNG image", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/optimize-for-web", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({ maxWidth: 1920, quality: 80 }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
// ─── Image Enhancement ──────────────────────────────────────────────
|
|
|
|
test.describe("Image Enhancement", () => {
|
|
test("auto enhancement", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/image-enhancement", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({ preset: "auto" }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("vivid enhancement preset", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/image-enhancement", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({ preset: "vivid" }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
});
|
|
|
|
test("enhancement changes the image", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/image-enhancement", {
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
multipart: {
|
|
file: { name: "sample.jpg", mimeType: "image/jpeg", buffer: JPG_SAMPLE },
|
|
settings: JSON.stringify({ preset: "auto" }),
|
|
},
|
|
});
|
|
expect(res.ok()).toBe(true);
|
|
const body = await res.json();
|
|
expect(body.downloadUrl).toBeTruthy();
|
|
// Enhanced image should differ from original
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
expect(body.processedSize).not.toBe(body.originalSize);
|
|
});
|
|
});
|
|
|
|
// ─── Auth Failure ──────────────────────────────────────────────────
|
|
|
|
test.describe("Auth failure", () => {
|
|
test("adjust-colors without token returns 401", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/adjust-colors", {
|
|
multipart: {
|
|
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
|
|
settings: JSON.stringify({ brightness: 20 }),
|
|
},
|
|
});
|
|
expect(res.status()).toBe(401);
|
|
});
|
|
|
|
test("strip-metadata without token returns 401", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/strip-metadata", {
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_WITH_EXIF },
|
|
settings: JSON.stringify({}),
|
|
},
|
|
});
|
|
expect(res.status()).toBe(401);
|
|
});
|
|
|
|
test("edit-metadata without token returns 401", async ({ request }) => {
|
|
const res = await request.post("/api/v1/tools/edit-metadata", {
|
|
multipart: {
|
|
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
|
|
settings: JSON.stringify({ artist: "Test" }),
|
|
},
|
|
});
|
|
expect(res.status()).toBe(401);
|
|
});
|
|
});
|