mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
117 lines
4.0 KiB
TypeScript
117 lines
4.0 KiB
TypeScript
import { resize, type Sharp } from "@snapotter/image-engine";
|
|||
|
|
import { ToolInputError } from "@snapotter/shared";
|
||
|
|
import { describe, expect, it, vi } from "vitest";
|
||
|
|
|
||
|
|
const MAX_RESIZE_PERCENTAGE = 1000;
|
||
|
|
const MAX_RESIZE_OUTPUT_DIMENSION = 16383;
|
||
|
|
const MAX_RESIZE_OUTPUT_PIXELS = 67_108_864;
|
||
|
|
|
||
|
|
function imageWithMetadata(width: number, height: number) {
|
||
|
|
const resized = { operation: "resize" } as unknown as Sharp;
|
||
|
|
const resizeCall = vi.fn(() => resized);
|
||
|
|
const metadata = vi.fn(async () => ({ width, height }));
|
||
|
|
const image = { metadata, resize: resizeCall } as unknown as Sharp;
|
||
|
|
|
||
|
|
return { image, metadata, resizeCall, resized };
|
||
|
|
}
|
||
|
|
|
||
|
|
describe("resize output allocation limits", () => {
|
||
|
|
it("rejects the deterministic fuzz counterexample as typed input before resize", async () => {
|
||
|
|
const { image, resizeCall } = imageWithMetadata(200, 150);
|
||
|
|
|
||
|
|
await expect(resize(image, { percentage: 896202.8004871072 })).rejects.toBeInstanceOf(
|
||
|
|
ToolInputError,
|
||
|
|
);
|
||
|
|
expect(resizeCall).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("preserves percentage semantics at the product boundary", async () => {
|
||
|
|
const { image, resizeCall, resized } = imageWithMetadata(640, 320);
|
||
|
|
|
||
|
|
await expect(resize(image, { percentage: MAX_RESIZE_PERCENTAGE })).resolves.toBe(resized);
|
||
|
|
expect(resizeCall).toHaveBeenCalledWith({
|
||
|
|
width: 6400,
|
||
|
|
height: 3200,
|
||
|
|
fit: "cover",
|
||
|
|
withoutEnlargement: false,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
it("rejects a percentage immediately above the product boundary", async () => {
|
||
|
|
const { image, resizeCall } = imageWithMetadata(1, 1);
|
||
|
|
|
||
|
|
await expect(
|
||
|
|
resize(image, { percentage: MAX_RESIZE_PERCENTAGE + Number.EPSILON * 1024 }),
|
||
|
|
).rejects.toBeInstanceOf(ToolInputError);
|
||
|
|
expect(resizeCall).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("uses actual metadata to reject a percentage-derived oversized side", async () => {
|
||
|
|
const { image, metadata, resizeCall } = imageWithMetadata(2000, 1000);
|
||
|
|
|
||
|
|
await expect(resize(image, { percentage: MAX_RESIZE_PERCENTAGE })).rejects.toBeInstanceOf(
|
||
|
|
ToolInputError,
|
||
|
|
);
|
||
|
|
expect(metadata).toHaveBeenCalledOnce();
|
||
|
|
expect(resizeCall).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("allows the exact output dimension boundary and rejects one pixel above it", async () => {
|
||
|
|
const atBoundary = imageWithMetadata(MAX_RESIZE_OUTPUT_DIMENSION, 1);
|
||
|
|
await expect(
|
||
|
|
resize(atBoundary.image, {
|
||
|
|
width: MAX_RESIZE_OUTPUT_DIMENSION,
|
||
|
|
height: 1,
|
||
|
|
fit: "fill",
|
||
|
|
}),
|
||
|
|
).resolves.toBe(atBoundary.resized);
|
||
|
|
|
||
|
|
const aboveBoundary = imageWithMetadata(MAX_RESIZE_OUTPUT_DIMENSION, 1);
|
||
|
|
await expect(
|
||
|
|
resize(aboveBoundary.image, {
|
||
|
|
width: MAX_RESIZE_OUTPUT_DIMENSION + 1,
|
||
|
|
height: 1,
|
||
|
|
fit: "fill",
|
||
|
|
}),
|
||
|
|
).rejects.toBeInstanceOf(ToolInputError);
|
||
|
|
expect(aboveBoundary.resizeCall).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("allows the exact output pixel boundary and rejects one row above it", async () => {
|
||
|
|
const boundarySide = Math.sqrt(MAX_RESIZE_OUTPUT_PIXELS);
|
||
|
|
expect(Number.isInteger(boundarySide)).toBe(true);
|
||
|
|
|
||
|
|
const atBoundary = imageWithMetadata(boundarySide, boundarySide);
|
||
|
|
await expect(
|
||
|
|
resize(atBoundary.image, { width: boundarySide, height: boundarySide, fit: "fill" }),
|
||
|
|
).resolves.toBe(atBoundary.resized);
|
||
|
|
|
||
|
|
const aboveBoundary = imageWithMetadata(boundarySide, boundarySide + 1);
|
||
|
|
await expect(
|
||
|
|
resize(aboveBoundary.image, {
|
||
|
|
width: boundarySide,
|
||
|
|
height: boundarySide + 1,
|
||
|
|
fit: "fill",
|
||
|
|
}),
|
||
|
|
).rejects.toBeInstanceOf(ToolInputError);
|
||
|
|
expect(aboveBoundary.resizeCall).not.toHaveBeenCalled();
|
||
|
|
});
|
||
|
|
|
||
|
|
it("applies withoutEnlargement before validating the effective output", async () => {
|
||
|
|
const { image, resizeCall, resized } = imageWithMetadata(200, 150);
|
||
|
|
|
||
|
|
await expect(
|
||
|
|
resize(image, {
|
||
|
|
percentage: MAX_RESIZE_PERCENTAGE,
|
||
|
|
withoutEnlargement: true,
|
||
|
|
}),
|
||
|
|
).resolves.toBe(resized);
|
||
|
|
expect(resizeCall).toHaveBeenCalledWith({
|
||
|
|
width: 200,
|
||
|
|
height: 150,
|
||
|
|
fit: "cover",
|
||
|
|
withoutEnlargement: true,
|
||
|
|
});
|
||
|
|
});
|
||
|
|
});
|