Files
SnapOtter/tests/e2e-docker/essential-tools.spec.ts
T
SnapOtter 733ebe8010 test: major coverage expansion — 18 new test files, ~830 new tests
Unit tests: 1354 → 1781 (+427)
- 11 new AI bridge module tests (packages/ai/ from 2/13 → 13/13 files)
- files-page-store (0% → full), pdf-to-image-store, features-store expanded
- saturation and edit-metadata image-engine operations
- analytics route, features route, web analytics lib, api-extended

Integration tests: ~2070 → 2320 (+250)
- 31 integration files expanded with branch-coverage-targeted tests
- progress.ts SSE endpoints (28% → comprehensive, +18 tests)
- gif-tools all modes (+18), pdf-to-image format variants (+13)
- Cross-format matrix expanded to 17 tools × 17 formats (467 tests)
- Adversarial: concurrent, memory pressure, unicode filenames, pipeline limits

E2E-Docker: +1020 lines across 6 spec files
- Info, colors, sharpening, base64, QR read, JXL/ICO/SVG formats
- Strip-metadata, image-enhancement, content-aware-resize expanded
- Batch pipelines, multi-format batches, HEIC input coverage
2026-04-26 12:03:08 +08:00

621 lines
23 KiB
TypeScript

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { expect, test } from "@playwright/test";
// ─── Essential Tools ────────────────────────────────────────────────
// Tests for: resize, crop, rotate, convert, compress
// These are the core Sharp-based image manipulation tools.
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));
}
/** 200x150 PNG for dimension-sensitive tests. */
const PNG_200x150 = fixture("test-200x150.png");
/** 100x100 JPEG for format variety. */
const JPG_100x100 = fixture("test-100x100.jpg");
/** HEIC image for format decode testing. */
const HEIC_200x150 = fixture("test-200x150.heic");
// ─── Resize ──────────────────────────────────────────────────────────
test.describe("Resize", () => {
test("resize to explicit width and height", async ({ request }) => {
const res = await request.post("/api/v1/tools/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ width: 100, height: 75, fit: "fill" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
test("resize with only width preserves aspect ratio", async ({ request }) => {
const res = await request.post("/api/v1/tools/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ width: 100, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
test("resize with only height preserves aspect ratio", async ({ request }) => {
const res = await request.post("/api/v1/tools/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ height: 50, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("resize with fit=cover", async ({ request }) => {
const res = await request.post("/api/v1/tools/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ width: 80, height: 80, fit: "cover" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("resize with withoutEnlargement prevents upscaling", async ({ request }) => {
const res = await request.post("/api/v1/tools/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ width: 400, withoutEnlargement: true }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
test("resize HEIC image succeeds", async ({ request }) => {
const res = await request.post("/api/v1/tools/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ width: 100, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("resize rejects invalid width=0", async ({ request }) => {
const res = await request.post("/api/v1/tools/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ width: 0 }),
},
});
expect(res.ok()).toBe(false);
const body = await res.json();
expect(body.error).toBeDefined();
});
test("resize rejects request with no file", async ({ request }) => {
const res = await request.post("/api/v1/tools/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
settings: JSON.stringify({ width: 100 }),
},
});
expect(res.ok()).toBe(false);
});
});
// ─── Crop ────────────────────────────────────────────────────────────
test.describe("Crop", () => {
test("crop to specific region", async ({ request }) => {
const res = await request.post("/api/v1/tools/crop", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ left: 10, top: 10, width: 100, height: 75 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
// Cropped image should be smaller than original
expect(body.processedSize).toBeLessThan(body.originalSize);
});
test("crop with zero offset extracts from corner", async ({ request }) => {
const res = await request.post("/api/v1/tools/crop", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ left: 0, top: 0, width: 50, height: 50 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("crop JPEG image", async ({ request }) => {
const res = await request.post("/api/v1/tools/crop", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ left: 10, top: 10, width: 50, height: 50 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
});
// ─── Rotate ──────────────────────────────────────────────────────────
test.describe("Rotate", () => {
test("rotate 90 degrees", async ({ request }) => {
const res = await request.post("/api/v1/tools/rotate", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ angle: 90 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
test("rotate 180 degrees", async ({ request }) => {
const res = await request.post("/api/v1/tools/rotate", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ angle: 180 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("rotate 270 degrees", async ({ request }) => {
const res = await request.post("/api/v1/tools/rotate", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ angle: 270 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("rotate 45 degrees with background fill", async ({ request }) => {
const res = await request.post("/api/v1/tools/rotate", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ angle: 45, background: "#ffffff" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
// 45-degree rotation expands the canvas, so output should be larger
expect(body.processedSize).toBeGreaterThan(0);
});
test("rotate 0 degrees returns image unchanged", async ({ request }) => {
const res = await request.post("/api/v1/tools/rotate", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ angle: 0 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
});
// ─── Convert ─────────────────────────────────────────────────────────
test.describe("Convert", () => {
test("convert PNG to JPEG", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "jpg", quality: 85 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".jpg");
expect(body.processedSize).toBeGreaterThan(0);
});
test("convert PNG to WebP", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "webp" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".webp");
});
test("convert PNG to AVIF", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "avif" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".avif");
});
test("convert PNG to TIFF", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "tiff" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".tiff");
});
test("convert PNG to GIF", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "gif" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".gif");
});
test("convert JPEG to PNG", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ format: "png" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".png");
});
test("convert HEIC to PNG", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ format: "png" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".png");
});
test("convert PNG to HEIC", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "heic" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".heic");
});
test("convert with quality parameter", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "jpg", quality: 10 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.processedSize).toBeGreaterThan(0);
});
test("convert rejects invalid format", async ({ request }) => {
const res = await request.post("/api/v1/tools/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "bmp" }),
},
});
expect(res.ok()).toBe(false);
const body = await res.json();
expect(body.error).toBeDefined();
});
});
// ─── Compress ────────────────────────────────────────────────────────
test.describe("Compress", () => {
test("compress with default settings", async ({ request }) => {
const res = await request.post("/api/v1/tools/compress", {
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("compress with low quality produces smaller file", async ({ request }) => {
const res = await request.post("/api/v1/tools/compress", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.jpg", mimeType: "image/jpeg", buffer: formatFixture("sample.jpg") },
settings: JSON.stringify({ quality: 10 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
// Low quality should produce a smaller file than the original
expect(body.processedSize).toBeLessThan(body.originalSize);
});
test("compress PNG image", async ({ request }) => {
const res = await request.post("/api/v1/tools/compress", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ quality: 50 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("compress WebP image", async ({ request }) => {
const webp = readFileSync(join(FIXTURES, "test-50x50.webp"));
const res = await request.post("/api/v1/tools/compress", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.webp", mimeType: "image/webp", buffer: webp },
settings: JSON.stringify({ quality: 50 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
});
// ─── Metadata (Info) ────────────────────────────────────────────────
test.describe("Metadata", () => {
test("returns dimensions and format for PNG", async ({ request }) => {
const res = await request.post("/api/v1/tools/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.width).toBe(200);
expect(body.height).toBe(150);
expect(body.format).toBe("png");
expect(body.fileSize).toBeGreaterThan(0);
});
test("returns dimensions for JPEG", async ({ request }) => {
const res = await request.post("/api/v1/tools/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.width).toBe(100);
expect(body.height).toBe(100);
expect(body.format).toBe("jpeg");
});
test("returns EXIF data when present", async ({ request }) => {
const jpgExif = fixture("test-with-exif.jpg");
const res = await request.post("/api/v1/tools/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test-with-exif.jpg", mimeType: "image/jpeg", buffer: jpgExif },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.hasExif).toBe(true);
});
test("returns channel and alpha info", async ({ request }) => {
const res = await request.post("/api/v1/tools/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.channels).toBeGreaterThan(0);
expect(typeof body.hasAlpha).toBe("boolean");
expect(body.colorSpace).toBeTruthy();
});
});
// ─── Color Adjustments ──────────────────────────────────────────────
test.describe("Colors", () => {
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 and saturation together", 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: 20, saturation: -10 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("convert to 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("negative brightness darkens image", 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: -30 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
// ─── Sharpening ─────────────────────────────────────────────────────
test.describe("Sharpening", () => {
test("sharpen with default sigma", 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", 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: 2.0 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
test("sharpen HEIC image", async ({ request }) => {
const res = await request.post("/api/v1/tools/sharpening", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ sigma: 1.5 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
});
});