Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/files/uploadFileClient.chat-scan.test.ts
T

79 lines
3.3 KiB
TypeScript

import { afterEach, describe, expect, it, vi } from "vitest";
import { uploadFileInChunks, waitForChatAttachmentScan } from "./uploadFileClient";
afterEach(() => {
vi.unstubAllGlobals();
document.head.replaceChildren();
});
describe("uploadFileInChunks", () => {
it("sends the session CSRF token with every upload mutation", async () => {
document.head.innerHTML = '<meta name="csrf-token" content="csrf-token">';
const fetchMock = vi
.fn<typeof fetch>()
.mockResolvedValueOnce(Response.json({ uploadId: "upload_1", chunkSize: 4, totalChunks: 1 }))
.mockResolvedValueOnce(Response.json({ receivedChunks: 1, totalChunks: 1 }))
.mockResolvedValueOnce(Response.json({ id: "file_1", displayName: "pilot.txt" }));
vi.stubGlobal("fetch", fetchMock);
await uploadFileInChunks({
tenantSlug: "demo",
file: new File(["test"], "pilot.txt", { type: "text/plain" }),
});
expect(fetchMock).toHaveBeenCalledTimes(3);
for (const [, init] of fetchMock.mock.calls) {
expect(init?.headers).toMatchObject({ "x-csrf-token": "csrf-token" });
}
});
});
describe("waitForChatAttachmentScan", () => {
it("polls pending scans until the owner-only endpoint reports clean", async () => {
const fetchImpl = vi
.fn<typeof fetch>()
.mockResolvedValueOnce(Response.json({ scanStatus: "pending" }))
.mockResolvedValueOnce(Response.json({ scanStatus: "clean" }));
await expect(waitForChatAttachmentScan({ tenantSlug: "demo", fileId: "file_1", fetchImpl, pollIntervalMs: 0, maxAttempts: 2 })).resolves.toBeUndefined();
expect(fetchImpl).toHaveBeenNthCalledWith(1, "/demo/chat/attachments/file_1/status", expect.objectContaining({ headers: { accept: "application/json" } }));
});
it("removes each abort listener after a completed polling delay", async () => {
const controller = new AbortController();
const add = vi.spyOn(controller.signal, "addEventListener");
const remove = vi.spyOn(controller.signal, "removeEventListener");
const fetchImpl = vi
.fn<typeof fetch>()
.mockResolvedValueOnce(Response.json({ scanStatus: "pending" }))
.mockResolvedValueOnce(Response.json({ scanStatus: "clean" }));
await waitForChatAttachmentScan({
tenantSlug: "demo",
fileId: "file_1",
fetchImpl,
signal: controller.signal,
pollIntervalMs: 1,
maxAttempts: 2,
});
expect(add).toHaveBeenCalledOnce();
expect(remove).toHaveBeenCalledWith("abort", expect.any(Function));
});
it.each(["infected", "skipped"])("fails closed when the scan result is %s", async (scanStatus) => {
const fetchImpl = vi.fn<typeof fetch>().mockResolvedValue(Response.json({ scanStatus }));
await expect(waitForChatAttachmentScan({ tenantSlug: "demo", fileId: "file_1", fetchImpl, pollIntervalMs: 0, maxAttempts: 1 })).rejects.toMatchObject({
errorKey: "chat.attachments.blocked",
});
});
it("returns a clear retry error when scanning stays pending", async () => {
const fetchImpl = vi.fn<typeof fetch>().mockImplementation(async () => Response.json({ scanStatus: "pending" }));
await expect(waitForChatAttachmentScan({ tenantSlug: "demo", fileId: "file_1", fetchImpl, pollIntervalMs: 0, maxAttempts: 2 })).rejects.toMatchObject({
errorKey: "chat.attachments.scanPendingRetry",
});
});
});