mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
The comment added in #652 was wrong. It said SYNC_WAIT_MS=0 drives every tool through its 202 path; it does the opposite. BullMQ arms its timer under `if (ttl)`, so 0 is falsy, waitUntilFinished waits forever and every route answers 200, matching the repo-wide convention that 0 means unlimited. A small positive value such as 1 is what forces the async path. That error mattered: the local validation claimed for #652 exercised the synchronous path throughout and never reached settleAsyncFallback. Redone with SYNC_WAIT_MS=1, it surfaced six specs whose status gate accepts 202 but whose else branch then demands an error body a 202 never carries, a latent flake on a slow runner. Each now settles the job first. Under a forced 1ms window the 'expected undefined to be defined' failures drop from 55 to 0, and all 1183 tests still pass on the normal 30s window. Per-shard totals unchanged at 9903 tests, 9435 passed, 468 skipped. Specs asserting a bare 200 without listing 202 are deliberately untouched: they own the synchronous contract.
682 lines
22 KiB
TypeScript
682 lines
22 KiB
TypeScript
/**
|
|
* Cross-format matrix, part 4 of 4 (see format-matrix.shared.ts).
|
|
*
|
|
* Holds the conversion, watermark-image and image-to-pdf blocks. Split out of
|
|
* format-matrix.test.ts because vitest shards by file and runs a file's tests
|
|
* serially in one fork.
|
|
*/
|
|
|
|
import { existsSync, readFileSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import { describe, expect, it } from "vitest";
|
|
import { fixtureDir, fixtures } from "../../fixtures/index.js";
|
|
import { settleAsyncFallback } from "../settle-job.js";
|
|
import { createMultipartPayload } from "../test-server.js";
|
|
import {
|
|
ACCEPTABLE_FALLBACK_CODES,
|
|
adminToken,
|
|
app,
|
|
FORMAT_SAMPLES,
|
|
needsFallback,
|
|
setupMatrixApp,
|
|
} from "./format-matrix.shared.js";
|
|
|
|
setupMatrixApp();
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Format-specific convert matrix: convert between various output formats
|
|
// ---------------------------------------------------------------------------
|
|
describe("Cross-format conversion matrix", () => {
|
|
const OUTPUT_FORMATS = ["jpg", "png", "webp", "avif", "tiff", "gif"];
|
|
|
|
// Only test core Sharp-readable formats for conversion (skip exotic ones)
|
|
const CONVERTIBLE_INPUTS = FORMAT_SAMPLES.filter(
|
|
(f) => !f.needsCliDecoder && !f.needsHeifDecoder && !f.mayFailValidation,
|
|
);
|
|
|
|
for (const fmt of CONVERTIBLE_INPUTS) {
|
|
for (const outFmt of OUTPUT_FORMATS) {
|
|
// Skip identity conversions
|
|
const inputLower = fmt.name.toLowerCase();
|
|
if (inputLower === outFmt) continue;
|
|
if (inputLower === "jpeg" && outFmt === "jpg") continue;
|
|
|
|
const testTimeout = outFmt === "avif" ? 120_000 : 120_000;
|
|
it(`${fmt.name} -> ${outFmt}`, { timeout: testTimeout }, async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath)) return;
|
|
|
|
const buffer = readFileSync(fixturePath);
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: buffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({ format: outFmt }),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/convert",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
expect(res.statusCode).toBe(200);
|
|
|
|
const body = JSON.parse(res.body);
|
|
expect(body.downloadUrl).toBeDefined();
|
|
expect(body.downloadUrl).toContain(`.${outFmt}`);
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Watermark-image cross-format matrix
|
|
//
|
|
// watermark-image requires TWO file uploads (file + watermark), so it cannot
|
|
// use the standard TOOLS/buildPayload path. We test each input format as the
|
|
// main image with a fixed PNG watermark, and also each format as the watermark
|
|
// image with a fixed PNG main image.
|
|
// ---------------------------------------------------------------------------
|
|
describe("Watermark-image cross-format matrix", () => {
|
|
// Use the PNG fixture as the known-good counterpart
|
|
const PNG_PATH = fixtures.image.formats("png");
|
|
|
|
describe("format as main image (watermark is PNG)", () => {
|
|
for (const fmt of FORMAT_SAMPLES) {
|
|
const perTestTimeout = fmt.needsHeifDecoder || fmt.needsCliDecoder ? 180_000 : 60_000;
|
|
|
|
it(
|
|
`${fmt.name} main image with PNG watermark`,
|
|
async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath) || !existsSync(PNG_PATH)) return;
|
|
|
|
const mainBuffer = readFileSync(fixturePath);
|
|
const wmBuffer = readFileSync(PNG_PATH);
|
|
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: mainBuffer,
|
|
},
|
|
{
|
|
name: "watermark",
|
|
filename: "sample.png",
|
|
contentType: "image/png",
|
|
content: wmBuffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({
|
|
position: "bottom-right",
|
|
opacity: 50,
|
|
scale: 25,
|
|
}),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/watermark-image",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
if (needsFallback(fmt)) {
|
|
expect(ACCEPTABLE_FALLBACK_CODES).toContain(res.statusCode);
|
|
} else {
|
|
expect(res.statusCode).toBe(200);
|
|
}
|
|
|
|
if (res.statusCode === 200) {
|
|
const body = JSON.parse(res.body);
|
|
expect(body.downloadUrl).toBeDefined();
|
|
expect(typeof body.downloadUrl).toBe("string");
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
expect(body.originalSize).toBeGreaterThan(0);
|
|
} else {
|
|
const body = JSON.parse(res.body);
|
|
expect(body.error).toBeDefined();
|
|
expect(typeof body.error).toBe("string");
|
|
}
|
|
},
|
|
perTestTimeout,
|
|
);
|
|
}
|
|
});
|
|
|
|
describe("format as watermark image (main is PNG)", () => {
|
|
for (const fmt of FORMAT_SAMPLES) {
|
|
const perTestTimeout = fmt.needsHeifDecoder || fmt.needsCliDecoder ? 180_000 : 60_000;
|
|
|
|
it(
|
|
`PNG main image with ${fmt.name} watermark`,
|
|
async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath) || !existsSync(PNG_PATH)) return;
|
|
|
|
const mainBuffer = readFileSync(PNG_PATH);
|
|
const wmBuffer = readFileSync(fixturePath);
|
|
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: "sample.png",
|
|
contentType: "image/png",
|
|
content: mainBuffer,
|
|
},
|
|
{
|
|
name: "watermark",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: wmBuffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({
|
|
position: "center",
|
|
opacity: 75,
|
|
scale: 30,
|
|
}),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/watermark-image",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
if (needsFallback(fmt)) {
|
|
expect(ACCEPTABLE_FALLBACK_CODES).toContain(res.statusCode);
|
|
} else {
|
|
expect(res.statusCode).toBe(200);
|
|
}
|
|
|
|
if (res.statusCode === 200) {
|
|
const body = JSON.parse(res.body);
|
|
expect(body.downloadUrl).toBeDefined();
|
|
expect(typeof body.downloadUrl).toBe("string");
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
} else {
|
|
const body = JSON.parse(res.body);
|
|
expect(body.error).toBeDefined();
|
|
expect(typeof body.error).toBe("string");
|
|
}
|
|
},
|
|
perTestTimeout,
|
|
);
|
|
}
|
|
});
|
|
|
|
describe("watermark positions across core formats", () => {
|
|
const POSITIONS = ["center", "top-left", "top-right", "bottom-left", "bottom-right"] as const;
|
|
|
|
// Only test core Sharp-readable formats for position coverage
|
|
const CORE_FORMATS = FORMAT_SAMPLES.filter(
|
|
(f) => !f.needsCliDecoder && !f.needsHeifDecoder && !f.mayFailValidation,
|
|
);
|
|
|
|
for (const fmt of CORE_FORMATS) {
|
|
for (const position of POSITIONS) {
|
|
it(`${fmt.name} with position=${position}`, async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath) || !existsSync(PNG_PATH)) return;
|
|
|
|
const mainBuffer = readFileSync(fixturePath);
|
|
const wmBuffer = readFileSync(PNG_PATH);
|
|
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: mainBuffer,
|
|
},
|
|
{
|
|
name: "watermark",
|
|
filename: "sample.png",
|
|
contentType: "image/png",
|
|
content: wmBuffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({ position, opacity: 50, scale: 20 }),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/watermark-image",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
expect(res.statusCode).toBe(200);
|
|
const body = JSON.parse(res.body);
|
|
expect(body.downloadUrl).toBeDefined();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Image-to-PDF cross-format conversion matrix
|
|
//
|
|
// Verifies that each input format can be converted to PDF, with various
|
|
// page size and orientation combinations.
|
|
// ---------------------------------------------------------------------------
|
|
describe("Image-to-PDF cross-format matrix", () => {
|
|
describe("single image conversion across formats", () => {
|
|
for (const fmt of FORMAT_SAMPLES) {
|
|
const perTestTimeout = fmt.needsHeifDecoder || fmt.needsCliDecoder ? 180_000 : 60_000;
|
|
|
|
it(
|
|
`converts ${fmt.name} to PDF`,
|
|
async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath)) return;
|
|
|
|
const buffer = readFileSync(fixturePath);
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: buffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({
|
|
pageSize: "A4",
|
|
orientation: "portrait",
|
|
margin: 20,
|
|
}),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/image-to-pdf",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
if (needsFallback(fmt)) {
|
|
expect(ACCEPTABLE_FALLBACK_CODES).toContain(res.statusCode);
|
|
} else {
|
|
expect(res.statusCode).toBe(200);
|
|
}
|
|
|
|
if (res.statusCode === 200) {
|
|
const body = JSON.parse(res.body);
|
|
expect(body.downloadUrl).toBeDefined();
|
|
expect(typeof body.downloadUrl).toBe("string");
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
expect(body.pages).toBe(1);
|
|
} else {
|
|
const body = JSON.parse(res.body);
|
|
expect(body.error).toBeDefined();
|
|
expect(typeof body.error).toBe("string");
|
|
}
|
|
},
|
|
perTestTimeout,
|
|
);
|
|
}
|
|
});
|
|
|
|
describe("page size and orientation variations per core format", () => {
|
|
const PAGE_CONFIGS = [
|
|
{ pageSize: "A4", orientation: "portrait" },
|
|
{ pageSize: "A4", orientation: "landscape" },
|
|
{ pageSize: "Letter", orientation: "portrait" },
|
|
{ pageSize: "A3", orientation: "landscape" },
|
|
{ pageSize: "A5", orientation: "portrait" },
|
|
] as const;
|
|
|
|
// Only test core Sharp-readable formats for page config coverage
|
|
const CORE_FORMATS = FORMAT_SAMPLES.filter(
|
|
(f) => !f.needsCliDecoder && !f.needsHeifDecoder && !f.mayFailValidation,
|
|
);
|
|
|
|
for (const fmt of CORE_FORMATS) {
|
|
for (const cfg of PAGE_CONFIGS) {
|
|
it(`${fmt.name} -> ${cfg.pageSize} ${cfg.orientation}`, async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath)) return;
|
|
|
|
const buffer = readFileSync(fixturePath);
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: buffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({
|
|
pageSize: cfg.pageSize,
|
|
orientation: cfg.orientation,
|
|
margin: 20,
|
|
}),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/image-to-pdf",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
expect(res.statusCode).toBe(200);
|
|
const body = JSON.parse(res.body);
|
|
expect(body.downloadUrl).toBeDefined();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
expect(body.pages).toBe(1);
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
describe("multi-format PDF (mixed inputs in one document)", () => {
|
|
// Only combine core formats that Sharp can read natively
|
|
const CORE_FORMATS = FORMAT_SAMPLES.filter(
|
|
(f) => !f.needsCliDecoder && !f.needsHeifDecoder && !f.mayFailValidation,
|
|
);
|
|
|
|
// Test pairing each core format with PNG as a 2-page PDF
|
|
for (const fmt of CORE_FORMATS) {
|
|
// Skip PNG paired with PNG (redundant)
|
|
if (fmt.name === "PNG") continue;
|
|
|
|
it(`${fmt.name} + PNG as 2-page PDF`, async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
const pngPath = fixtures.image.formats("png");
|
|
if (!existsSync(fixturePath) || !existsSync(pngPath)) return;
|
|
|
|
const fmtBuffer = readFileSync(fixturePath);
|
|
const pngBuffer = readFileSync(pngPath);
|
|
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: fmtBuffer,
|
|
},
|
|
{
|
|
name: "file",
|
|
filename: "sample.png",
|
|
contentType: "image/png",
|
|
content: pngBuffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({ pageSize: "A4" }),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/image-to-pdf",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
expect(res.statusCode).toBe(200);
|
|
const body = JSON.parse(res.body);
|
|
expect(body.pages).toBe(2);
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
});
|
|
}
|
|
});
|
|
|
|
describe("multipage TIFF to PDF", () => {
|
|
const multipagePath = fixtures.image.multipageTiff;
|
|
|
|
it("converts multipage TIFF to PDF", async () => {
|
|
if (!existsSync(multipagePath)) return;
|
|
|
|
const buffer = readFileSync(multipagePath);
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: "multipage.tiff",
|
|
contentType: "image/tiff",
|
|
content: buffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({ pageSize: "A4" }),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/image-to-pdf",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
expect([200, 202, 400, 422]).toContain(res.statusCode);
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
if (res.statusCode === 200) {
|
|
const body = JSON.parse(res.body);
|
|
expect(body.downloadUrl).toBeDefined();
|
|
expect(body.processedSize).toBeGreaterThan(0);
|
|
expect(body.pages).toBeGreaterThanOrEqual(1);
|
|
} else {
|
|
const body = JSON.parse(res.body);
|
|
expect(body.error).toBeDefined();
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("exotic format error resilience for image-to-pdf", () => {
|
|
const EXOTIC_FORMATS = FORMAT_SAMPLES.filter((f) => f.needsCliDecoder);
|
|
|
|
for (const fmt of EXOTIC_FORMATS) {
|
|
it(`${fmt.name} -> PDF: returns clean response (no crash)`, async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath)) return;
|
|
|
|
const buffer = readFileSync(fixturePath);
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: buffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({ pageSize: "A4" }),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/image-to-pdf",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
// Must not crash with 500
|
|
if (await settleAsyncFallback(res)) return;
|
|
if (await settleAsyncFallback(res)) return;
|
|
expect(res.statusCode).not.toBe(500);
|
|
expect([200, 202, 400, 422]).toContain(res.statusCode);
|
|
|
|
const body = JSON.parse(res.body);
|
|
if (res.statusCode >= 400) {
|
|
expect(body.error).toBeDefined();
|
|
expect(typeof body.error).toBe("string");
|
|
expect(body.error.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Watermark-image exotic format error resilience
|
|
// ---------------------------------------------------------------------------
|
|
describe("Watermark-image exotic format error resilience", () => {
|
|
const EXOTIC_FORMATS = FORMAT_SAMPLES.filter((f) => f.needsCliDecoder);
|
|
const PNG_PATH = fixtures.image.formats("png");
|
|
|
|
describe("exotic format as main image", () => {
|
|
for (const fmt of EXOTIC_FORMATS) {
|
|
it(`${fmt.name} main + PNG watermark: no crash`, async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath) || !existsSync(PNG_PATH)) return;
|
|
|
|
const mainBuffer = readFileSync(fixturePath);
|
|
const wmBuffer = readFileSync(PNG_PATH);
|
|
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: mainBuffer,
|
|
},
|
|
{
|
|
name: "watermark",
|
|
filename: "sample.png",
|
|
contentType: "image/png",
|
|
content: wmBuffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({ position: "center", opacity: 50, scale: 25 }),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/watermark-image",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
expect(res.statusCode).not.toBe(500);
|
|
expect([200, 202, 400, 422]).toContain(res.statusCode);
|
|
|
|
const body = JSON.parse(res.body);
|
|
if (res.statusCode >= 400) {
|
|
expect(body.error).toBeDefined();
|
|
expect(typeof body.error).toBe("string");
|
|
expect(body.error.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
describe("exotic format as watermark image", () => {
|
|
for (const fmt of EXOTIC_FORMATS) {
|
|
it(`PNG main + ${fmt.name} watermark: no crash`, async () => {
|
|
const fixturePath = join(fixtureDir.formats, fmt.file);
|
|
if (!existsSync(fixturePath) || !existsSync(PNG_PATH)) return;
|
|
|
|
const mainBuffer = readFileSync(PNG_PATH);
|
|
const wmBuffer = readFileSync(fixturePath);
|
|
|
|
const { body: payload, contentType } = createMultipartPayload([
|
|
{
|
|
name: "file",
|
|
filename: "sample.png",
|
|
contentType: "image/png",
|
|
content: mainBuffer,
|
|
},
|
|
{
|
|
name: "watermark",
|
|
filename: fmt.file,
|
|
contentType: fmt.mime,
|
|
content: wmBuffer,
|
|
},
|
|
{
|
|
name: "settings",
|
|
content: JSON.stringify({ position: "bottom-right", opacity: 75, scale: 30 }),
|
|
},
|
|
]);
|
|
|
|
const res = await app.inject({
|
|
method: "POST",
|
|
url: "/api/v1/tools/image/watermark-image",
|
|
headers: {
|
|
authorization: `Bearer ${adminToken}`,
|
|
"content-type": contentType,
|
|
},
|
|
body: payload,
|
|
});
|
|
|
|
if (await settleAsyncFallback(res)) return;
|
|
expect(res.statusCode).not.toBe(500);
|
|
expect([200, 202, 400, 422]).toContain(res.statusCode);
|
|
|
|
const body = JSON.parse(res.body);
|
|
if (res.statusCode >= 400) {
|
|
expect(body.error).toBeDefined();
|
|
expect(typeof body.error).toBe("string");
|
|
expect(body.error.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
});
|