import { beforeEach, describe, expect, it, vi } from "vite-plus/test"; import { PROVIDER_SEND_TURN_MAX_ATTACHMENTS } from "@t3tools/contracts"; const missingFileCause = new Error("missing file"); const files = new Map(); vi.mock("expo-file-system", () => ({ File: class { readonly uri: string; constructor(uri: string) { this.uri = uri; } get exists(): boolean { return files.has(this.uri) && files.get(this.uri)?.deleted === false; } async base64(): Promise { const entry = files.get(this.uri); if (!entry || entry.deleted) { throw missingFileCause; } return entry.base64; } delete(): void { const entry = files.get(this.uri); if (entry) { entry.deleted = true; } } }, })); vi.mock("./uuid", () => ({ uuidv4: () => "attachment-id", })); import { convertPastedImagesToAttachments, isOwnedPastedImageUri } from "./composerImages"; describe("native pasted image cleanup", () => { beforeEach(() => { files.clear(); }); it("recognizes only files created in the native composer paste directory", () => { expect( isOwnedPastedImageUri( "file:///private/var/mobile/Containers/Data/Application/app/tmp/t3-composer-paste/id.png", ), ).toBe(true); expect(isOwnedPastedImageUri("file:///private/var/mobile/photos/id.png")).toBe(false); expect(isOwnedPastedImageUri("https://example.com/t3-composer-paste/id.png")).toBe(false); }); it("converts owned files to data-backed previews and deletes the source", async () => { const uri = "file:///private/var/mobile/Containers/Data/Application/app/tmp/t3-composer-paste/id.png"; files.set(uri, { base64: "aGVsbG8=", deleted: false }); const attachments = await convertPastedImagesToAttachments({ uris: [uri], existingCount: 0, }); expect(attachments).toEqual([ expect.objectContaining({ dataUrl: "data:image/png;base64,aGVsbG8=", previewUri: "data:image/png;base64,aGVsbG8=", }), ]); expect(files.get(uri)?.deleted).toBe(true); }); it("deletes rejected and overflow owned files without deleting user-owned files", async () => { const rejected = "file:///private/var/mobile/Containers/Data/Application/app/tmp/t3-composer-paste/bad.png"; const overflow = "file:///private/var/mobile/Containers/Data/Application/app/tmp/t3-composer-paste/overflow.png"; const userOwned = "file:///private/var/mobile/photos/library.png"; files.set(rejected, { base64: "", deleted: false }); files.set(overflow, { base64: "aGVsbG8=", deleted: false }); files.set(userOwned, { base64: "aGVsbG8=", deleted: false }); await convertPastedImagesToAttachments({ uris: [rejected, overflow, userOwned], existingCount: PROVIDER_SEND_TURN_MAX_ATTACHMENTS - 1, }); expect(files.get(rejected)?.deleted).toBe(true); expect(files.get(overflow)?.deleted).toBe(true); expect(files.get(userOwned)?.deleted).toBe(false); }); it("reports structured context when reading a pasted image fails", async () => { const uri = "file:///private/var/mobile/photos/signed-secret-token/missing.png?token=private"; const warn = vi.spyOn(console, "warn").mockImplementation(() => undefined); await expect( convertPastedImagesToAttachments({ uris: [uri], existingCount: 0 }), ).resolves.toEqual([]); expect(warn).toHaveBeenCalledWith( "[composer-images] failed to read pasted image", expect.objectContaining({ _tag: "ComposerImageOperationError", operation: "read-pasted-image", uriLength: uri.length, uriProtocol: "file:", cause: missingFileCause, message: `Composer image operation read-pasted-image failed for a file: URI (length ${uri.length}).`, }), ); const error = warn.mock.calls[0]?.[1]; expect(error).not.toHaveProperty("uri"); expect(String(error)).not.toContain("signed-secret-token"); expect(String(error)).not.toContain("token=private"); warn.mockRestore(); }); });