mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
Group 245 flat integration tests and 25 loose unit tests into
discoverable subdirectories per spec section 6:
integration/tools/{image,video,audio,document,data}/ (156 files)
integration/platform/ (64 files)
integration/generated/ (14 files)
integration/security/ (10 files)
unit/security/ (8 files, new subdir)
unit/api/ (7 files moved in)
unit/web/ (3 files moved in)
unit/shared/ (6 files moved in)
unit/image-engine/ (1 file moved in)
All moves via git mv (history preserved). Relative imports repaired
for both depth levels (platform/generated/security = +1, tools/ = +2):
static from-imports, dynamic import() calls, vi.mock() paths,
import.meta.dirname joins, and __dirname joins.
Vitest discovery unchanged (no test.include in config, recursive glob
matches subdirs, shard-by-hash unaffected). test-server.ts and
tool-route-drift.test.ts stay at integration root. fixtures/ untouched.
Parity gate: 13189 passing test names before = 13189 after (0 dropped).
187 lines
5.9 KiB
TypeScript
187 lines
5.9 KiB
TypeScript
import { mkdtempSync, rmSync } from "node:fs";
|
|
import { readFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
import {
|
|
pdfcpuAvailable,
|
|
pdfcpuBooklet,
|
|
pdfcpuCropMargin,
|
|
pdfcpuNup,
|
|
pdfcpuTextStamp,
|
|
} from "@snapotter/doc-engine";
|
|
import { describe, expect, it } from "vitest";
|
|
import { fixtures } from "../../fixtures/index.js";
|
|
|
|
const PDF = fixtures.document.pdf3;
|
|
|
|
// --- Gated integration tests: skip when pdfcpu is not installed locally ---
|
|
describe.skipIf(!pdfcpuAvailable())("doc-engine pdfcpu (requires pdfcpu binary)", () => {
|
|
it("nup(2) reduces 3 pages to 2 sheets", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "pdfcpu-"));
|
|
try {
|
|
const out = join(dir, "nup2.pdf");
|
|
await pdfcpuNup(PDF, 2, out);
|
|
const bytes = await readFile(out);
|
|
expect(bytes.subarray(0, 5).toString()).toBe("%PDF-");
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("crop preserves page count", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "pdfcpu-"));
|
|
try {
|
|
const out = join(dir, "cropped.pdf");
|
|
await pdfcpuCropMargin(PDF, 20, out);
|
|
const bytes = await readFile(out);
|
|
expect(bytes.subarray(0, 5).toString()).toBe("%PDF-");
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("textStamp produces valid pdf output", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "pdfcpu-"));
|
|
try {
|
|
const out = join(dir, "stamped.pdf");
|
|
await pdfcpuTextStamp(
|
|
PDF,
|
|
{ text: "CONFIDENTIAL", position: "c", fontSize: 24, opacity: 0.5, rotation: 45 },
|
|
out,
|
|
);
|
|
const bytes = await readFile(out);
|
|
expect(bytes.subarray(0, 5).toString()).toBe("%PDF-");
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("booklet produces valid pdf output", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "pdfcpu-"));
|
|
try {
|
|
const out = join(dir, "booklet.pdf");
|
|
await pdfcpuBooklet(PDF, 2, out);
|
|
const bytes = await readFile(out);
|
|
expect(bytes.subarray(0, 5).toString()).toBe("%PDF-");
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
|
|
it("textStamp with page numbers (%p/%P) produces valid output", async () => {
|
|
const dir = mkdtempSync(join(tmpdir(), "pdfcpu-"));
|
|
try {
|
|
const out = join(dir, "numbered.pdf");
|
|
await pdfcpuTextStamp(
|
|
PDF,
|
|
{ text: "Page %p of %P", position: "bc", fontSize: 10, opacity: 1, rotation: 0 },
|
|
out,
|
|
);
|
|
const bytes = await readFile(out);
|
|
expect(bytes.subarray(0, 5).toString()).toBe("%PDF-");
|
|
} finally {
|
|
rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
});
|
|
});
|
|
|
|
// --- Ungated validation tests: run without the binary ---
|
|
describe("pdfcpu validation (no binary required)", () => {
|
|
it("rejects invalid stamp position", async () => {
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "test", position: "xx", fontSize: 12, opacity: 0.5, rotation: 0 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/position/i);
|
|
});
|
|
|
|
it("rejects empty stamp text", async () => {
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "", position: "c", fontSize: 12, opacity: 0.5, rotation: 0 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/text/i);
|
|
});
|
|
|
|
it("rejects oversize stamp text (>200 chars)", async () => {
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "x".repeat(201), position: "c", fontSize: 12, opacity: 0.5, rotation: 0 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/text/i);
|
|
});
|
|
|
|
it("rejects fontSize out of range", async () => {
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "test", position: "c", fontSize: 5, opacity: 0.5, rotation: 0 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/font size/i);
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "test", position: "c", fontSize: 73, opacity: 0.5, rotation: 0 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/font size/i);
|
|
});
|
|
|
|
it("rejects opacity out of range", async () => {
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "test", position: "c", fontSize: 12, opacity: 0.04, rotation: 0 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/opacity/i);
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "test", position: "c", fontSize: 12, opacity: 1.1, rotation: 0 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/opacity/i);
|
|
});
|
|
|
|
it("rejects rotation out of range", async () => {
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "test", position: "c", fontSize: 12, opacity: 0.5, rotation: -181 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/rotation/i);
|
|
await expect(
|
|
pdfcpuTextStamp(
|
|
"/fake.pdf",
|
|
{ text: "test", position: "c", fontSize: 12, opacity: 0.5, rotation: 181 },
|
|
"/out.pdf",
|
|
),
|
|
).rejects.toThrow(/rotation/i);
|
|
});
|
|
|
|
it("rejects crop margin out of range", async () => {
|
|
await expect(pdfcpuCropMargin("/fake.pdf", -1, "/out.pdf")).rejects.toThrow(/margin/i);
|
|
await expect(pdfcpuCropMargin("/fake.pdf", 2001, "/out.pdf")).rejects.toThrow(/margin/i);
|
|
await expect(pdfcpuCropMargin("/fake.pdf", NaN, "/out.pdf")).rejects.toThrow(/margin/i);
|
|
});
|
|
|
|
it("rejects invalid nup value", async () => {
|
|
await expect(pdfcpuNup("/fake.pdf", 5 as never, "/out.pdf")).rejects.toThrow(/n-up/i);
|
|
await expect(pdfcpuNup("/fake.pdf", 7 as never, "/out.pdf")).rejects.toThrow(/n-up/i);
|
|
});
|
|
|
|
it("rejects invalid booklet value", async () => {
|
|
await expect(pdfcpuBooklet("/fake.pdf", 3 as never, "/out.pdf")).rejects.toThrow(/booklet/i);
|
|
await expect(pdfcpuBooklet("/fake.pdf", 5 as never, "/out.pdf")).rejects.toThrow(/booklet/i);
|
|
});
|
|
});
|