import { afterEach, beforeEach, describe, expect, it } from "vitest"; import { GalleryPlacement } from "@/generated/prisma/enums"; import { addGalleryImage, deleteGalleryImage, GALLERY_IMAGE_URL_MAX_LENGTH, listGalleryImages, reorderGalleryImages, replaceGalleryImage, updateGalleryImage, } from "@/services/gallery/gallery.service"; import { cleanupTestBusiness, createTestBusiness, prisma } from "@/services/scheduling/__tests__/fixtures"; import type { TestBusiness } from "@/services/scheduling/__tests__/fixtures"; describe("Gallery service (integration)", () => { let business: TestBusiness; beforeEach(async () => { business = await createTestBusiness(); }); afterEach(async () => { await cleanupTestBusiness(business); }); it("keeps HERO and PORTFOLIO images in separate lists for the same business", async () => { await addGalleryImage(prisma, { businessId: business.businessId, placement: GalleryPlacement.HERO, imageUrl: "/media/gallery/hero-1.png" }); await addGalleryImage(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO, imageUrl: "/media/gallery/portfolio-1.png", category: "Fades" }); const hero = await listGalleryImages(prisma, { businessId: business.businessId, placement: GalleryPlacement.HERO }); const portfolio = await listGalleryImages(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO }); expect(hero).toHaveLength(1); expect(portfolio).toHaveLength(1); expect(portfolio[0]?.category).toBe("Fades"); }); it("assigns increasing displayOrder on add, and reorder fully replaces it", async () => { const a = await addGalleryImage(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO, imageUrl: "/media/a.png" }); const b = await addGalleryImage(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO, imageUrl: "/media/b.png" }); expect(a.displayOrder).toBe(0); expect(b.displayOrder).toBe(1); await reorderGalleryImages(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO, orderedIds: [b.id, a.id] }); const reordered = await listGalleryImages(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO }); expect(reordered.map((i) => i.id)).toEqual([b.id, a.id]); }); it("updates category/caption/isActive, and activeOnly filtering respects isActive", async () => { const image = await addGalleryImage(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO, imageUrl: "/media/a.png" }); await updateGalleryImage(prisma, { id: image.id, businessId: business.businessId, category: "Beards", caption: "Client X", isActive: false }); const all = await listGalleryImages(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO }); expect(all[0]?.category).toBe("Beards"); expect(all[0]?.caption).toBe("Client X"); expect(all[0]?.isActive).toBe(false); const activeOnly = await listGalleryImages(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO, activeOnly: true }); expect(activeOnly).toHaveLength(0); }); it("never lets one business update or delete another business's gallery image", async () => { const otherBusiness = await createTestBusiness(); try { const image = await addGalleryImage(prisma, { businessId: otherBusiness.businessId, placement: GalleryPlacement.PORTFOLIO, imageUrl: "/media/other.png" }); const updateResult = await updateGalleryImage(prisma, { id: image.id, businessId: business.businessId, caption: "hijacked" }); expect(updateResult.success).toBe(false); const deleteResult = await deleteGalleryImage(prisma, { id: image.id, businessId: business.businessId }); expect(deleteResult.success).toBe(false); const stillThere = await prisma.businessGalleryImage.findUnique({ where: { id: image.id } }); expect(stillThere).not.toBeNull(); expect(stillThere?.caption).not.toBe("hijacked"); } finally { await cleanupTestBusiness(otherBusiness); } }); it("deletes an image it owns", async () => { const image = await addGalleryImage(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO, imageUrl: "/media/a.png" }); const result = await deleteGalleryImage(prisma, { id: image.id, businessId: business.businessId }); expect(result.success).toBe(true); expect(await prisma.businessGalleryImage.findUnique({ where: { id: image.id } })).toBeNull(); }); describe("replacing an image", () => { const add = (url: string, extra: { category?: string } = {}) => addGalleryImage(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO, imageUrl: url, ...extra }); it("swaps the picture but keeps its position, category, caption and visibility", async () => { const first = await add("/media/first.png"); const middle = await add("/media/middle.png", { category: "Fades" }); const last = await add("/media/last.png"); await updateGalleryImage(prisma, { id: middle.id, businessId: business.businessId, caption: "Client X", isActive: false }); const result = await replaceGalleryImage(prisma, { id: middle.id, businessId: business.businessId, imageUrl: "/media/new.png" }); expect(result).toEqual({ success: true }); const all = await listGalleryImages(prisma, { businessId: business.businessId, placement: GalleryPlacement.PORTFOLIO }); expect(all.map((i) => i.id)).toEqual([first.id, middle.id, last.id]); expect(all[1]).toMatchObject({ imageUrl: "/media/new.png", category: "Fades", caption: "Client X", isActive: false }); }); it("refuses an empty or over-long address, leaving the picture as it was", async () => { const image = await add("/media/keep.png"); for (const imageUrl of [" ", "x".repeat(GALLERY_IMAGE_URL_MAX_LENGTH + 1)]) { const result = await replaceGalleryImage(prisma, { id: image.id, businessId: business.businessId, imageUrl }); expect(result.success).toBe(false); } expect((await prisma.businessGalleryImage.findUniqueOrThrow({ where: { id: image.id } })).imageUrl).toBe("/media/keep.png"); }); it("can't replace another business's image", async () => { const otherBusiness = await createTestBusiness(); try { const image = await add("/media/mine.png"); const result = await replaceGalleryImage(prisma, { id: image.id, businessId: otherBusiness.businessId, imageUrl: "/media/hijack.png" }); expect(result.success).toBe(false); expect((await prisma.businessGalleryImage.findUniqueOrThrow({ where: { id: image.id } })).imageUrl).toBe("/media/mine.png"); } finally { await cleanupTestBusiness(otherBusiness); } }); }); });