Gateway: normalize HEIC input_image sources (#38122)

* Media: normalize HEIC input images

* Gateway: accept HEIC image input schema

* Media: add HEIC input normalization tests

* Gateway: cover HEIC input schema parity

* Docs: document HEIC input image support

* Changelog: note HEIC input image fix
This commit is contained in:
Vincent Koc
2026-03-06 11:19:36 -05:00
committed by GitHub
parent 81f22ae109
commit 9aceb51379
6 changed files with 163 additions and 13 deletions

View File

@@ -1,11 +1,16 @@
import { beforeAll, describe, expect, it, vi } from "vitest";
import { beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
const fetchWithSsrFGuardMock = vi.fn();
const convertHeicToJpegMock = vi.fn();
vi.mock("../infra/net/fetch-guard.js", () => ({
fetchWithSsrFGuard: (...args: unknown[]) => fetchWithSsrFGuardMock(...args),
}));
vi.mock("./image-ops.js", () => ({
convertHeicToJpeg: (...args: unknown[]) => convertHeicToJpegMock(...args),
}));
async function waitForMicrotaskTurn(): Promise<void> {
await new Promise<void>((resolve) => queueMicrotask(resolve));
}
@@ -19,6 +24,75 @@ beforeAll(async () => {
await import("./input-files.js"));
});
beforeEach(() => {
vi.clearAllMocks();
});
describe("HEIC input image normalization", () => {
it("converts base64 HEIC images to JPEG before returning them", async () => {
const normalized = Buffer.from("jpeg-normalized");
convertHeicToJpegMock.mockResolvedValueOnce(normalized);
const image = await extractImageContentFromSource(
{
type: "base64",
data: Buffer.from("heic-source").toString("base64"),
mediaType: "image/heic",
},
{
allowUrl: false,
allowedMimes: new Set(["image/heic", "image/jpeg"]),
maxBytes: 1024 * 1024,
maxRedirects: 0,
timeoutMs: 1,
},
);
expect(convertHeicToJpegMock).toHaveBeenCalledTimes(1);
expect(image).toEqual({
type: "image",
data: normalized.toString("base64"),
mimeType: "image/jpeg",
});
});
it("converts URL HEIC images to JPEG before returning them", async () => {
const release = vi.fn(async () => {});
fetchWithSsrFGuardMock.mockResolvedValueOnce({
response: new Response(Buffer.from("heic-url-source"), {
status: 200,
headers: { "content-type": "image/heic" },
}),
release,
finalUrl: "https://example.com/photo.heic",
});
const normalized = Buffer.from("jpeg-url-normalized");
convertHeicToJpegMock.mockResolvedValueOnce(normalized);
const image = await extractImageContentFromSource(
{
type: "url",
url: "https://example.com/photo.heic",
},
{
allowUrl: true,
allowedMimes: new Set(["image/heic", "image/jpeg"]),
maxBytes: 1024 * 1024,
maxRedirects: 0,
timeoutMs: 1000,
},
);
expect(convertHeicToJpegMock).toHaveBeenCalledTimes(1);
expect(image).toEqual({
type: "image",
data: normalized.toString("base64"),
mimeType: "image/jpeg",
});
expect(release).toHaveBeenCalledTimes(1);
});
});
describe("fetchWithGuard", () => {
it("rejects oversized streamed payloads and cancels the stream", async () => {
let canceled = false;