Files
SnapOtter/tests/e2e-docker/cross-format-docker.spec.ts
SnapOtterandGitHub d10d0f544f fix: release QA hardening across processing, media, security, and CI gates (#649)
A release-readiness QA pass over the whole product. The commits split into
defects a user would hit and gates that were reporting green while measuring
nothing.

## Fixes that change behaviour

Rate limiting was bypassable on every install: TRUST_PROXY defaulted to true, so
request.ip came from a client-set header and a forged X-Forwarded-For got past
the login limiter. The default is now a private-network trust list.

A transient Postgres outage stranded in-flight jobs, leaving finished output on
disk with no row pointing at it. A reconciler now resolves those rows and adopts
the bytes rather than dropping the work.

A Redis connection that moved to a new address wedged every read-blocked
consumer, so completions stopped signalling while health still answered 200.
Socket timeouts plus subscriber pings recover it.

Installing more than one AI bundle left the shared venv multi-versioned and
silently broke three tools. The installer now reconciles distributions to one
version each.

Converting an image to JXL at quality 1 through 4 returned a 500, because
libjxl 0.7 rejects the distance those values compute. The quality is floored at
what the encoder honours. A missing ffmpeg was also reported to the user as a
corrupt upload; it now says the engine is unavailable.

RAW uploads reached an unpatched LibRaw on arm64, so it is built from source at
0.22.2, and the release scan was split so it can fail on an unfixed critical
instead of hiding it behind ignore-unfixed.

## Gates that could not fail

Two mutation lanes ran zero mutants because Stryker crawled the gitignored docs
build; coverage discarded its whole report on any failing test; the lint gate
skipped root tests, scripts, and two workspaces; and several generated matrices
counted a host missing ffmpeg as a passing tool. Each now measures what it
claims.

Full evidence and the outstanding release items are tracked locally and are not
part of this branch.
2026-07-27 15:37:30 +08:00

916 lines
33 KiB
TypeScript

import { readFileSync } from "node:fs";
import { join } from "node:path";
import { expect, test } from "@playwright/test";
// ─── Cross-Format Docker Tests ────────────────────────────────────
// Tests key tools against multiple input formats through the real
// Docker container with real Sharp processing. Verifies actual
// output dimensions, format, and file size.
const FIXTURES = join(process.cwd(), "tests", "fixtures", "image", "valid");
const FORMATS = join(process.cwd(), "tests", "fixtures", "image", "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));
}
const PNG_200x150 = fixture("test-200x150.png");
const JPG_100x100 = fixture("test-100x100.jpg");
const WEBP_50x50 = fixture("test-50x50.webp");
const HEIC_200x150 = fixture("test-200x150.heic");
// ─── Resize: Format Matrix ───────────────────────────────────────
test.describe("Resize: JPEG", () => {
test("resize JPEG to 50px wide", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ width: 50, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Resize: PNG", () => {
test("resize PNG to 80px wide", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ width: 80, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Resize: WebP", () => {
test("resize WebP to 30px wide", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.webp", mimeType: "image/webp", buffer: WEBP_50x50 },
settings: JSON.stringify({ width: 30, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Resize: AVIF", () => {
test("resize AVIF to 60px wide", async ({ request }) => {
const avif = formatFixture("sample.avif");
const res = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.avif", mimeType: "image/avif", buffer: avif },
settings: JSON.stringify({ width: 60, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Resize: HEIC", () => {
test("resize HEIC to 80px wide", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ width: 80, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Resize: TIFF", () => {
test("resize TIFF to 60px wide", async ({ request }) => {
const tiff = formatFixture("sample.tiff");
const res = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.tiff", mimeType: "image/tiff", buffer: tiff },
settings: JSON.stringify({ width: 60, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Resize: BMP", () => {
test("resize BMP to 40px wide", async ({ request }) => {
const bmp = formatFixture("sample.bmp");
const res = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.bmp", mimeType: "image/bmp", buffer: bmp },
settings: JSON.stringify({ width: 40, fit: "contain" }),
},
});
// BMP may not be supported by Sharp natively
if (res.ok()) {
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
} else {
expect([400, 422]).toContain(res.status());
}
});
});
test.describe("Resize: GIF", () => {
test("resize GIF to 40px wide", async ({ request }) => {
const gif = formatFixture("sample.gif");
const res = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.gif", mimeType: "image/gif", buffer: gif },
settings: JSON.stringify({ width: 40, fit: "contain" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
// ─── Convert: From Each Format to PNG ─────────────────────────────
test.describe("Convert to PNG: from JPEG", () => {
test("JPEG to PNG", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/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");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Convert to PNG: from WebP", () => {
test("WebP to PNG", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.webp", mimeType: "image/webp", buffer: WEBP_50x50 },
settings: JSON.stringify({ format: "png" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".png");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Convert to PNG: from AVIF", () => {
test("AVIF to PNG", async ({ request }) => {
const avif = formatFixture("sample.avif");
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.avif", mimeType: "image/avif", buffer: avif },
settings: JSON.stringify({ format: "png" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".png");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Convert to PNG: from HEIC", () => {
test("HEIC to PNG", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/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");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Convert to PNG: from TIFF", () => {
test("TIFF to PNG", async ({ request }) => {
const tiff = formatFixture("sample.tiff");
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.tiff", mimeType: "image/tiff", buffer: tiff },
settings: JSON.stringify({ format: "png" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".png");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Convert to PNG: from GIF", () => {
test("GIF to PNG", async ({ request }) => {
const gif = formatFixture("sample.gif");
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.gif", mimeType: "image/gif", buffer: gif },
settings: JSON.stringify({ format: "png" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".png");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Convert to PNG: from HEIF", () => {
test("HEIF to PNG", async ({ request }) => {
const heif = formatFixture("sample.heif");
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.heif", mimeType: "image/heif", buffer: heif },
settings: JSON.stringify({ format: "png" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".png");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Convert to PNG: from BMP", () => {
test("BMP to PNG", async ({ request }) => {
const bmp = formatFixture("sample.bmp");
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.bmp", mimeType: "image/bmp", buffer: bmp },
settings: JSON.stringify({ format: "png" }),
},
});
// BMP decode may not be supported
if (res.ok()) {
const body = await res.json();
expect(body.downloadUrl).toContain(".png");
expect(body.processedSize).toBeGreaterThan(0);
} else {
expect([400, 422]).toContain(res.status());
}
});
});
// ─── Compress: Format Matrix ──────────────────────────────────────
test.describe("Compress: JPEG", () => {
test("compress JPEG with quality 30", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/compress", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.jpg", mimeType: "image/jpeg", buffer: formatFixture("sample.jpg") },
settings: JSON.stringify({ quality: 30 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
expect(body.processedSize).toBeLessThan(body.originalSize);
});
});
test.describe("Compress: PNG", () => {
test("compress PNG with quality 40", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/compress", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ quality: 40 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Compress: WebP", () => {
test("compress WebP with quality 25", async ({ request }) => {
const webp = formatFixture("sample.webp");
const res = await request.post("/api/v1/tools/image/compress", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.webp", mimeType: "image/webp", buffer: webp },
settings: JSON.stringify({ quality: 25 }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Compress: AVIF", () => {
test("compress AVIF with quality 40", async ({ request }) => {
const avif = formatFixture("sample.avif");
const res = await request.post("/api/v1/tools/image/compress", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.avif", mimeType: "image/avif", buffer: avif },
settings: JSON.stringify({ quality: 40 }),
},
});
// AVIF compression may not be supported
if (res.ok()) {
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
} else {
expect([400, 422]).toContain(res.status());
}
});
});
test.describe("Compress: HEIC", () => {
test("compress HEIC with quality 50", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/compress", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ quality: 50 }),
},
});
// HEIC compression may not be directly supported
if (res.ok()) {
const body = await res.json();
expect(body.downloadUrl).toBeTruthy();
expect(body.processedSize).toBeGreaterThan(0);
} else {
expect([400, 422]).toContain(res.status());
}
});
});
// ─── Info: Every Format ───────────────────────────────────────────
test.describe("Info: JPEG", () => {
test("get info for JPEG returns correct dimensions", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/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");
expect(body.fileSize).toBeGreaterThan(0);
});
});
test.describe("Info: PNG", () => {
test("get info for PNG returns correct dimensions", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/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.describe("Info: WebP", () => {
test("get info for WebP returns correct dimensions", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.webp", mimeType: "image/webp", buffer: WEBP_50x50 },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.width).toBe(50);
expect(body.height).toBe(50);
expect(body.format).toBe("webp");
expect(body.fileSize).toBeGreaterThan(0);
});
});
test.describe("Info: AVIF", () => {
test("get info for AVIF returns dimensions and format", async ({ request }) => {
const avif = formatFixture("sample.avif");
const res = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.avif", mimeType: "image/avif", buffer: avif },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.width).toBeGreaterThan(0);
expect(body.height).toBeGreaterThan(0);
expect(body.fileSize).toBeGreaterThan(0);
});
});
test.describe("Info: HEIC", () => {
test("get info for HEIC returns dimensions", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.width).toBeGreaterThan(0);
expect(body.height).toBeGreaterThan(0);
expect(body.fileSize).toBeGreaterThan(0);
});
});
test.describe("Info: TIFF", () => {
test("get info for TIFF returns dimensions and format", async ({ request }) => {
const tiff = formatFixture("sample.tiff");
const res = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.tiff", mimeType: "image/tiff", buffer: tiff },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.width).toBeGreaterThan(0);
expect(body.height).toBeGreaterThan(0);
expect(body.fileSize).toBeGreaterThan(0);
});
});
test.describe("Info: GIF", () => {
test("get info for GIF returns dimensions and format", async ({ request }) => {
const gif = formatFixture("sample.gif");
const res = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.gif", mimeType: "image/gif", buffer: gif },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.width).toBeGreaterThan(0);
expect(body.height).toBeGreaterThan(0);
expect(body.format).toBe("gif");
expect(body.fileSize).toBeGreaterThan(0);
});
});
test.describe("Info: HEIF", () => {
test("get info for HEIF returns dimensions", async ({ request }) => {
const heif = formatFixture("sample.heif");
const res = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.heif", mimeType: "image/heif", buffer: heif },
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.width).toBeGreaterThan(0);
expect(body.height).toBeGreaterThan(0);
expect(body.fileSize).toBeGreaterThan(0);
});
});
test.describe("Info: BMP", () => {
test("get info for BMP returns dimensions if supported", async ({ request }) => {
const bmp = formatFixture("sample.bmp");
const res = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "sample.bmp", mimeType: "image/bmp", buffer: bmp },
},
});
if (res.ok()) {
const body = await res.json();
expect(body.width).toBeGreaterThan(0);
expect(body.height).toBeGreaterThan(0);
expect(body.fileSize).toBeGreaterThan(0);
} else {
expect([400, 422]).toContain(res.status());
}
});
});
// ─── Cross-Format Conversions: Full Matrix ────────────────────────
test.describe("Cross-format: JPEG to all targets", () => {
test("JPEG to WebP", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ format: "webp" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".webp");
expect(body.processedSize).toBeGreaterThan(0);
});
test("JPEG to AVIF", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ format: "avif" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".avif");
expect(body.processedSize).toBeGreaterThan(0);
});
test("JPEG to TIFF", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ format: "tiff" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".tiff");
expect(body.processedSize).toBeGreaterThan(0);
});
test("JPEG to GIF", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ format: "gif" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".gif");
expect(body.processedSize).toBeGreaterThan(0);
});
test("JPEG to HEIC", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ format: "heic" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".heic");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Cross-format: PNG to all targets", () => {
test("PNG to JPEG", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/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("PNG to WebP", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/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");
expect(body.processedSize).toBeGreaterThan(0);
});
test("PNG to AVIF", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/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");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Cross-format: WebP to all targets", () => {
test("WebP to JPEG", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.webp", mimeType: "image/webp", buffer: WEBP_50x50 },
settings: JSON.stringify({ format: "jpg" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".jpg");
expect(body.processedSize).toBeGreaterThan(0);
});
test("WebP to AVIF", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.webp", mimeType: "image/webp", buffer: WEBP_50x50 },
settings: JSON.stringify({ format: "avif" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".avif");
expect(body.processedSize).toBeGreaterThan(0);
});
test("WebP to TIFF", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.webp", mimeType: "image/webp", buffer: WEBP_50x50 },
settings: JSON.stringify({ format: "tiff" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".tiff");
expect(body.processedSize).toBeGreaterThan(0);
});
});
test.describe("Cross-format: HEIC to all targets", () => {
test("HEIC to JPEG", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ format: "jpg" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".jpg");
expect(body.processedSize).toBeGreaterThan(0);
});
test("HEIC to WebP", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ format: "webp" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".webp");
expect(body.processedSize).toBeGreaterThan(0);
});
test("HEIC to AVIF", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ format: "avif" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".avif");
expect(body.processedSize).toBeGreaterThan(0);
});
test("HEIC to TIFF", async ({ request }) => {
const res = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.heic", mimeType: "image/heic", buffer: HEIC_200x150 },
settings: JSON.stringify({ format: "tiff" }),
},
});
expect(res.ok()).toBe(true);
const body = await res.json();
expect(body.downloadUrl).toContain(".tiff");
expect(body.processedSize).toBeGreaterThan(0);
});
});
// ─── Resize + Info Verification ───────────────────────────────────
test.describe("Resize + info verification: JPEG", () => {
test("resize JPEG then verify dimensions via info", async ({ request }) => {
// Step 1: Resize
const resizeRes = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ width: 50, height: 50, fit: "fill" }),
},
});
expect(resizeRes.ok()).toBe(true);
const resizeBody = await resizeRes.json();
expect(resizeBody.downloadUrl).toBeTruthy();
// Step 2: Download resized image
const dlRes = await request.get(resizeBody.downloadUrl, {
headers: { Authorization: `Bearer ${token}` },
});
expect(dlRes.ok()).toBe(true);
const resizedBuffer = Buffer.from(await dlRes.body());
// Step 3: Verify dimensions via info
const infoRes = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "resized.jpg", mimeType: "image/jpeg", buffer: resizedBuffer },
},
});
expect(infoRes.ok()).toBe(true);
const infoBody = await infoRes.json();
expect(infoBody.width).toBe(50);
expect(infoBody.height).toBe(50);
});
});
test.describe("Resize + info verification: PNG", () => {
test("resize PNG then verify dimensions via info", async ({ request }) => {
const resizeRes = await request.post("/api/v1/tools/image/resize", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ width: 100, fit: "contain" }),
},
});
expect(resizeRes.ok()).toBe(true);
const resizeBody = await resizeRes.json();
const dlRes = await request.get(resizeBody.downloadUrl, {
headers: { Authorization: `Bearer ${token}` },
});
expect(dlRes.ok()).toBe(true);
const resizedBuffer = Buffer.from(await dlRes.body());
const infoRes = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "resized.png", mimeType: "image/png", buffer: resizedBuffer },
},
});
expect(infoRes.ok()).toBe(true);
const infoBody = await infoRes.json();
expect(infoBody.width).toBe(100);
// Aspect ratio preserved: 200x150 -> 100x75
expect(infoBody.height).toBe(75);
expect(infoBody.format).toBe("png");
});
});
// ─── Convert + Info Verification ──────────────────────────────────
test.describe("Convert + info verification", () => {
test("convert JPEG to WebP then verify format via info", async ({ request }) => {
const convertRes = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.jpg", mimeType: "image/jpeg", buffer: JPG_100x100 },
settings: JSON.stringify({ format: "webp" }),
},
});
expect(convertRes.ok()).toBe(true);
const convertBody = await convertRes.json();
const dlRes = await request.get(convertBody.downloadUrl, {
headers: { Authorization: `Bearer ${token}` },
});
expect(dlRes.ok()).toBe(true);
const convertedBuffer = Buffer.from(await dlRes.body());
const infoRes = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "converted.webp", mimeType: "image/webp", buffer: convertedBuffer },
},
});
expect(infoRes.ok()).toBe(true);
const infoBody = await infoRes.json();
expect(infoBody.format).toBe("webp");
expect(infoBody.width).toBe(100);
expect(infoBody.height).toBe(100);
});
test("convert PNG to AVIF then verify format via info", async ({ request }) => {
const convertRes = await request.post("/api/v1/tools/image/convert", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "test.png", mimeType: "image/png", buffer: PNG_200x150 },
settings: JSON.stringify({ format: "avif" }),
},
});
expect(convertRes.ok()).toBe(true);
const convertBody = await convertRes.json();
const dlRes = await request.get(convertBody.downloadUrl, {
headers: { Authorization: `Bearer ${token}` },
});
expect(dlRes.ok()).toBe(true);
const convertedBuffer = Buffer.from(await dlRes.body());
const infoRes = await request.post("/api/v1/tools/image/info", {
headers: { Authorization: `Bearer ${token}` },
multipart: {
file: { name: "converted.avif", mimeType: "image/avif", buffer: convertedBuffer },
},
});
expect(infoRes.ok()).toBe(true);
const infoBody = await infoRes.json();
expect(infoBody.width).toBe(200);
expect(infoBody.height).toBe(150);
});
});