mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
73 lines
2.5 KiB
TypeScript
73 lines
2.5 KiB
TypeScript
import { isSafeMessageError, markToolInputError, SafeError } from "@snapotter/shared";
|
|||
|
|
import { describe, expect, it } from "vitest";
|
||
|
|
import { withImageEncodeContext } from "../../../apps/api/src/lib/image-error.js";
|
||
|
|
|
||
|
|
interface Settings {
|
||
|
|
format: string;
|
||
|
|
}
|
||
|
|
const settings: Settings = { format: "webp" };
|
||
|
|
const input = Buffer.from("");
|
||
|
|
|
||
|
|
describe("withImageEncodeContext", () => {
|
||
|
|
it("returns the process result unchanged when it succeeds", async () => {
|
||
|
|
const wrapped = withImageEncodeContext(
|
||
|
|
"Image conversion failed",
|
||
|
|
(s: Settings) => s.format,
|
||
|
|
async () => ({ buffer: Buffer.from("ok"), filename: "out.webp", contentType: "image/webp" }),
|
||
|
|
);
|
||
|
|
const result = await wrapped(input, settings, "in.png");
|
||
|
|
expect(result.filename).toBe("out.webp");
|
||
|
|
});
|
||
|
|
|
||
|
|
it("wraps an opaque encode failure in a SafeError with the target format as code", async () => {
|
||
|
|
// Sharp .toBuffer() failures throw an Error whose message is scrubbed to
|
||
|
|
// type-only ("Error: Error") in Sentry; the wrapper must author a title.
|
||
|
|
const sharpErr = new Error("");
|
||
|
|
const wrapped = withImageEncodeContext(
|
||
|
|
"Image conversion failed",
|
||
|
|
(s: Settings) => s.format,
|
||
|
|
async () => {
|
||
|
|
throw sharpErr;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
|
||
|
|
let caught: unknown;
|
||
|
|
try {
|
||
|
|
await wrapped(input, settings, "in.png");
|
||
|
|
} catch (e) {
|
||
|
|
caught = e;
|
||
|
|
}
|
||
|
|
|
||
|
|
expect(isSafeMessageError(caught)).toBe(true);
|
||
|
|
expect((caught as SafeError).message).toBe("Image conversion failed");
|
||
|
|
expect((caught as SafeError).kind).toBe("bug");
|
||
|
|
expect((caught as SafeError).code).toBe("webp");
|
||
|
|
// Original error kept so its stack/location survives in the cause chain.
|
||
|
|
expect((caught as SafeError).cause).toBe(sharpErr);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("passes an already-authored SafeError through unchanged (no double-wrap)", async () => {
|
||
|
|
const inner = new SafeError("Process killed (out of memory)", { kind: "operational" });
|
||
|
|
const wrapped = withImageEncodeContext(
|
||
|
|
"Image conversion failed",
|
||
|
|
() => "webp",
|
||
|
|
async () => {
|
||
|
|
throw inner;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
await expect(wrapped(input, settings, "in.png")).rejects.toBe(inner);
|
||
|
|
});
|
||
|
|
|
||
|
|
it("passes a ToolInputError through unchanged (stays a 400, not a masked bug)", async () => {
|
||
|
|
const inputErr = markToolInputError(new Error("Unsupported input"));
|
||
|
|
const wrapped = withImageEncodeContext(
|
||
|
|
"Image conversion failed",
|
||
|
|
() => "webp",
|
||
|
|
async () => {
|
||
|
|
throw inputErr;
|
||
|
|
},
|
||
|
|
);
|
||
|
|
await expect(wrapped(input, settings, "in.png")).rejects.toBe(inputErr);
|
||
|
|
});
|
||
|
|
});
|