Files
kandev/apps/web/components/task/chat/file-attachment.test.ts
T
2026-05-23 01:51:04 +01:00

80 lines
3.1 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { processFile } from "./file-attachment";
const UUID_V4_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
// Minimal FileReader stub — jsdom's version is fine, but we stub for deterministic output.
class FakeFileReader {
public result: string | null = null;
public onload: ((event: { target: { result: string } }) => void) | null = null;
public onerror: (() => void) | null = null;
readAsDataURL(file: File) {
// Small synthetic data URL — content doesn't matter for the id assertion.
const payload = "AAAA"; // 3 bytes base64
this.result = `data:${file.type || "application/octet-stream"};base64,${payload}`;
queueMicrotask(() => this.onload?.({ target: { result: this.result as string } }));
}
}
// Image stub — fires onload synchronously so the image branch resolves.
class FakeImage {
public onload: (() => void) | null = null;
public onerror: (() => void) | null = null;
set src(_: string) {
queueMicrotask(() => this.onload?.());
}
}
describe("processFile in insecure context (HTTP, no crypto.randomUUID)", () => {
afterEach(() => {
vi.unstubAllGlobals();
});
it("assigns a fallback UUID to non-image attachments when crypto.randomUUID is unavailable", async () => {
vi.stubGlobal("crypto", {}); // simulate non-secure context
vi.stubGlobal("FileReader", FakeFileReader);
const file = new File(["hello"], "notes.txt", { type: "text/plain" });
Object.defineProperty(file, "size", { value: 5 });
const attachment = await processFile(file);
expect(attachment).not.toBeNull();
expect(attachment!.id).toMatch(UUID_V4_REGEX);
expect(attachment!.isImage).toBe(false);
expect(attachment!.fileName).toBe("notes.txt");
});
it("assigns a fallback UUID to image attachments when crypto.randomUUID is unavailable", async () => {
vi.stubGlobal("crypto", {});
vi.stubGlobal("FileReader", FakeFileReader);
vi.stubGlobal("Image", FakeImage);
const file = new File(["img"], "shot.png", { type: "image/png" });
Object.defineProperty(file, "size", { value: 3 });
const attachment = await processFile(file);
expect(attachment).not.toBeNull();
expect(attachment!.id).toMatch(UUID_V4_REGEX);
expect(attachment!.isImage).toBe(true);
expect(attachment!.preview).toMatch(/^data:image\/png;base64,/);
});
it("treats non-previewable image MIME types as regular file attachments", async () => {
const ImageSpy = vi.fn();
vi.stubGlobal("crypto", {});
vi.stubGlobal("FileReader", FakeFileReader);
vi.stubGlobal("Image", ImageSpy);
const file = new File(["svg"], "icon.svg", { type: "image/svg+xml" });
Object.defineProperty(file, "size", { value: 3 });
const attachment = await processFile(file);
expect(attachment).not.toBeNull();
expect(attachment!.id).toMatch(UUID_V4_REGEX);
expect(attachment!.isImage).toBe(false);
expect(attachment!.mimeType).toBe("image/svg+xml");
expect(attachment!.preview).toBeUndefined();
expect(ImageSpy).not.toHaveBeenCalled();
});
});