|
| 1 | +// Tool image backend-unavailable tests cover safe pass-through when native |
| 2 | +// image processing cannot load on constrained Linux/Termux platforms. |
| 3 | +import { beforeEach, describe, expect, it, vi } from "vitest"; |
| 4 | + |
| 5 | +const { backendUnavailableError, getImageMetadataMock, readImageMetadataFromHeaderMock } = |
| 6 | + vi.hoisted(() => ({ |
| 7 | + backendUnavailableError: new Error("missing image backend"), |
| 8 | + getImageMetadataMock: vi.fn(), |
| 9 | + readImageMetadataFromHeaderMock: vi.fn(), |
| 10 | + })); |
| 11 | + |
| 12 | +const PNG_BASE64 = "iVBORw0KGgo="; |
| 13 | + |
| 14 | +async function importSanitizer() { |
| 15 | + vi.resetModules(); |
| 16 | + vi.doMock("../media/media-services.js", () => ({ |
| 17 | + IMAGE_REDUCE_QUALITY_STEPS: [85, 75], |
| 18 | + MAX_IMAGE_INPUT_PIXELS: 25_000_000, |
| 19 | + buildImageResizeSideGrid: () => [1200], |
| 20 | + getImageMetadata: getImageMetadataMock, |
| 21 | + isImageProcessorUnavailableError: (error: unknown) => error === backendUnavailableError, |
| 22 | + readImageMetadataFromHeader: readImageMetadataFromHeaderMock, |
| 23 | + resizeToJpeg: async () => { |
| 24 | + throw backendUnavailableError; |
| 25 | + }, |
| 26 | + })); |
| 27 | + return await import("./tool-images.js"); |
| 28 | +} |
| 29 | + |
| 30 | +describe("tool image sanitizer without native image backend", () => { |
| 31 | + beforeEach(() => { |
| 32 | + getImageMetadataMock.mockReset(); |
| 33 | + readImageMetadataFromHeaderMock.mockReset(); |
| 34 | + }); |
| 35 | + |
| 36 | + it("keeps small header-verified images without probing the backend", async () => { |
| 37 | + readImageMetadataFromHeaderMock.mockReturnValueOnce({ width: 32, height: 24 }); |
| 38 | + getImageMetadataMock.mockRejectedValueOnce(backendUnavailableError); |
| 39 | + const { sanitizeContentBlocksImages } = await importSanitizer(); |
| 40 | + |
| 41 | + const out = await sanitizeContentBlocksImages( |
| 42 | + [{ type: "image" as const, data: PNG_BASE64, mimeType: "image/png" }], |
| 43 | + "test", |
| 44 | + { maxDimensionPx: 64, maxBytes: 1024 }, |
| 45 | + ); |
| 46 | + |
| 47 | + expect(out).toStrictEqual([{ type: "image", data: PNG_BASE64, mimeType: "image/png" }]); |
| 48 | + expect(getImageMetadataMock).not.toHaveBeenCalled(); |
| 49 | + }); |
| 50 | + |
| 51 | + it("drops images that need resizing when the backend is unavailable", async () => { |
| 52 | + readImageMetadataFromHeaderMock.mockReturnValueOnce({ width: 128, height: 24 }); |
| 53 | + const { sanitizeContentBlocksImages } = await importSanitizer(); |
| 54 | + |
| 55 | + const out = await sanitizeContentBlocksImages( |
| 56 | + [{ type: "image" as const, data: PNG_BASE64, mimeType: "image/png" }], |
| 57 | + "test", |
| 58 | + { maxDimensionPx: 64, maxBytes: 1024 }, |
| 59 | + ); |
| 60 | + |
| 61 | + expect(out).toStrictEqual([ |
| 62 | + { |
| 63 | + type: "text", |
| 64 | + text: "[test] omitted image payload: Error: missing image backend", |
| 65 | + }, |
| 66 | + ]); |
| 67 | + }); |
| 68 | + |
| 69 | + it("does not pass through compressed images over the pixel cap", async () => { |
| 70 | + readImageMetadataFromHeaderMock.mockReturnValueOnce({ width: 6000, height: 6000 }); |
| 71 | + const { sanitizeContentBlocksImages } = await importSanitizer(); |
| 72 | + |
| 73 | + const out = await sanitizeContentBlocksImages( |
| 74 | + [{ type: "image" as const, data: PNG_BASE64, mimeType: "image/png" }], |
| 75 | + "test", |
| 76 | + { maxDimensionPx: 6000, maxBytes: 1024 }, |
| 77 | + ); |
| 78 | + |
| 79 | + expect(out).toStrictEqual([ |
| 80 | + { |
| 81 | + type: "text", |
| 82 | + text: "[test] omitted image payload: Error: missing image backend", |
| 83 | + }, |
| 84 | + ]); |
| 85 | + }); |
| 86 | +}); |
0 commit comments