Files
HiveGrid-Wireframes/03 Quellen und Referenzen/hivegrid-cloud/apps/web/app/utils/integrations/fileService.hard-delete.test.ts
T

80 lines
2.9 KiB
TypeScript

import { beforeEach, describe, expect, it, vi } from "vitest";
const mocks = vi.hoisted(() => ({
deleteFile: vi.fn(),
findFirst: vi.fn(),
deleteRow: vi.fn(),
publishSearchProjectionDelete: vi.fn(),
}));
vi.mock("./storageService", () => ({
assembleChunkedUpload: vi.fn(),
deleteFile: mocks.deleteFile,
putBufferAsFile: vi.fn(),
resolveStorageBucket: () => "tenant-files",
storeFile: vi.fn(),
}));
vi.mock("@hivegrid/storage", () => ({
buildFileStorageKeys: vi.fn(),
buildUploadChunkKey: vi.fn(),
uploadChunkPrefix: vi.fn(),
createMagicBytesScanAdapter: () => ({ scan: vi.fn() }),
DEFAULT_ALLOW_LIST: { formUpload: [] },
}));
vi.mock("~/utils/db/.server/tenantRls.server", () => ({
withTenantRLS: async (_tenantId: string, callback: (tx: unknown) => Promise<unknown>) =>
callback({ file: { findFirst: mocks.findFirst, delete: mocks.deleteRow } }),
}));
vi.mock("~/services/search/searchIndexProjection.server", () => ({
publishFileSearchUpsert: vi.fn(),
publishSearchProjectionDelete: mocks.publishSearchProjectionDelete,
}));
import { hardDeleteFile } from "./fileService.server";
describe("hardDeleteFile", () => {
beforeEach(() => {
vi.clearAllMocks();
mocks.findFirst.mockResolvedValue({
id: "file_1",
storageKey: "tenants/tenant_1/files/file_1/original.pdf",
previewStorageKey: "tenants/tenant_1/files/file_1/preview.png",
thumbnailStorageKey: "tenants/tenant_1/files/file_1/thumbnail.png",
});
mocks.deleteFile.mockResolvedValue(undefined);
mocks.deleteRow.mockResolvedValue({ id: "file_1" });
mocks.publishSearchProjectionDelete.mockResolvedValue(undefined);
});
it("removes every stored artifact before releasing the database quota", async () => {
await expect(hardDeleteFile({ tenantId: "tenant_1", fileId: "file_1" })).resolves.toBeUndefined();
expect(mocks.deleteFile).toHaveBeenNthCalledWith(1, {
bucket: "tenant-files",
key: "tenants/tenant_1/files/file_1/original.pdf",
});
expect(mocks.deleteFile).toHaveBeenNthCalledWith(2, {
bucket: "tenant-files",
key: "tenants/tenant_1/files/file_1/preview.png",
});
expect(mocks.deleteFile).toHaveBeenNthCalledWith(3, {
bucket: "tenant-files",
key: "tenants/tenant_1/files/file_1/thumbnail.png",
});
expect(mocks.deleteRow).toHaveBeenCalledWith({ where: { id: "file_1" } });
expect(mocks.deleteFile.mock.invocationCallOrder[2]).toBeLessThan(mocks.deleteRow.mock.invocationCallOrder[0]!);
});
it("keeps the database row when object deletion fails so accounting cannot lose the object", async () => {
mocks.deleteFile.mockRejectedValueOnce(new Error("object storage unavailable"));
await expect(hardDeleteFile({ tenantId: "tenant_1", fileId: "file_1" })).rejects.toThrow("object storage unavailable");
expect(mocks.deleteRow).not.toHaveBeenCalled();
expect(mocks.publishSearchProjectionDelete).not.toHaveBeenCalled();
});
});