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<string, { base64: string; deleted: boolean }>(); 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<string> { 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(); });});
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<string, { base64: string; deleted: boolean }>();
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<string> {
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();
});
});