/** * Integration tests for company branding actions (logo + stamp uploads). * Covers: * - Manager can upload a logo / stamp; the key is stored on the company * - Re-upload overwrites in place (deterministic key) * - Invalid asset type, bad mime, and oversize files are rejected * - removeCompanyAsset clears the key * - Permission gating (TECHNICAL cannot manage branding) */ import { vi, describe, it, expect, beforeAll, afterAll } from "vitest"; vi.mock("@/auth", () => ({ auth: vi.fn() })); vi.mock("next/cache", () => ({ revalidatePath: vi.fn() })); vi.mock("@/lib/storage", async (importOriginal) => ({ ...(await importOriginal()), uploadBuffer: vi.fn(), // don't touch the filesystem in tests })); import { auth } from "@/auth"; import { db } from "@/lib/db"; import { uploadBuffer } from "@/lib/storage"; import { uploadCompanyAsset, removeCompanyAsset } from "@/app/(portal)/admin/companies/actions"; import { makeSession } from "./helpers"; const mockedAuth = vi.mocked(auth); const mockedUpload = vi.mocked(uploadBuffer); let companyId: string; function pngFile(name: string, bytes = 1024): File { return new File([new Uint8Array(bytes)], name, { type: "image/png" }); } function assetForm(id: string, type: string, file: File): FormData { const form = new FormData(); form.set("companyId", id); form.set("type", type); form.set("file", file); return form; } beforeAll(async () => { const company = await db.company.create({ data: { name: "INTTEST_BRANDING_CO", code: "ZZBRAND" }, }); companyId = company.id; }); afterAll(async () => { await db.company.delete({ where: { id: companyId } }).catch(() => {}); }); describe("uploadCompanyAsset", () => { it("stores a logo key on the company", async () => { mockedAuth.mockResolvedValue(makeSession("u-mgr", "MANAGER") as never); const res = await uploadCompanyAsset(assetForm(companyId, "logo", pngFile("logo.png"))); expect(res).toEqual({ ok: true }); const c = await db.company.findUniqueOrThrow({ where: { id: companyId } }); expect(c.logoKey).toBe(`company-assets/${companyId}/logo.png`); expect(mockedUpload).toHaveBeenCalled(); }); it("stores a stamp key independently of the logo", async () => { mockedAuth.mockResolvedValue(makeSession("u-mgr", "MANAGER") as never); const res = await uploadCompanyAsset(assetForm(companyId, "stamp", pngFile("stamp.png"))); expect(res).toEqual({ ok: true }); const c = await db.company.findUniqueOrThrow({ where: { id: companyId } }); expect(c.stampKey).toBe(`company-assets/${companyId}/stamp.png`); expect(c.logoKey).toBe(`company-assets/${companyId}/logo.png`); }); it("rejects an unknown asset type", async () => { mockedAuth.mockResolvedValue(makeSession("u-mgr", "MANAGER") as never); const res = await uploadCompanyAsset(assetForm(companyId, "header", pngFile("x.png"))); expect(res).toEqual({ error: "Invalid asset type" }); }); it("rejects a non-image mime type", async () => { mockedAuth.mockResolvedValue(makeSession("u-mgr", "MANAGER") as never); const pdf = new File([new Uint8Array(10)], "x.pdf", { type: "application/pdf" }); const res = await uploadCompanyAsset(assetForm(companyId, "logo", pdf)); expect(res).toEqual({ error: "Image must be a PNG, JPG, or WebP" }); }); it("rejects a file over 4 MB", async () => { mockedAuth.mockResolvedValue(makeSession("u-mgr", "MANAGER") as never); const big = pngFile("big.png", 5 * 1024 * 1024); const res = await uploadCompanyAsset(assetForm(companyId, "logo", big)); expect(res).toEqual({ error: "Image must be under 4 MB" }); }); it("refuses callers without manage_vessels_accounts", async () => { mockedAuth.mockResolvedValue(makeSession("u-tech", "TECHNICAL") as never); const res = await uploadCompanyAsset(assetForm(companyId, "logo", pngFile("logo.png"))); expect(res).toEqual({ error: "Unauthorized" }); }); }); describe("removeCompanyAsset", () => { it("clears the stored key", async () => { mockedAuth.mockResolvedValue(makeSession("u-mgr", "MANAGER") as never); const res = await removeCompanyAsset(companyId, "logo"); expect(res).toEqual({ ok: true }); const c = await db.company.findUniqueOrThrow({ where: { id: companyId } }); expect(c.logoKey).toBeNull(); expect(c.stampKey).toBe(`company-assets/${companyId}/stamp.png`); // stamp untouched }); it("refuses unauthorized callers", async () => { mockedAuth.mockResolvedValue(makeSession("u-tech", "TECHNICAL") as never); const res = await removeCompanyAsset(companyId, "stamp"); expect(res).toEqual({ error: "Unauthorized" }); }); });