import { beforeEach, describe, expect, it, vi } from 'vitest' const deleteObjectMock = vi.hoisted(() => vi.fn()) const resolveStorageKeyFromMediaValueMock = vi.hoisted(() => vi.fn()) const prismaMock = vi.hoisted(() => ({ novelPromotionLocation: { findUnique: vi.fn(), update: vi.fn(), }, locationImage: { update: vi.fn(), deleteMany: vi.fn(), }, $transaction: vi.fn(), })) vi.mock('@/lib/prisma', () => ({ prisma: prismaMock, })) vi.mock('@/lib/storage', () => ({ deleteObject: deleteObjectMock, })) vi.mock('@/lib/media/service', () => ({ resolveStorageKeyFromMediaValue: resolveStorageKeyFromMediaValueMock, })) describe('project location-backed selection service', () => { beforeEach(() => { vi.clearAllMocks() prismaMock.$transaction.mockImplementation(async ( callback: (tx: { locationImage: { update: typeof prismaMock.locationImage.update deleteMany: typeof prismaMock.locationImage.deleteMany } novelPromotionLocation: { update: typeof prismaMock.novelPromotionLocation.update } }) => Promise, ) => callback({ locationImage: prismaMock.locationImage, novelPromotionLocation: prismaMock.novelPromotionLocation, })) resolveStorageKeyFromMediaValueMock.mockImplementation(async (value: string) => `key:${value}`) deleteObjectMock.mockResolvedValue(undefined) prismaMock.locationImage.deleteMany.mockResolvedValue({ count: 1 }) prismaMock.locationImage.update.mockResolvedValue(undefined) prismaMock.novelPromotionLocation.update.mockResolvedValue(undefined) }) it('confirms a prop selection by keeping only the selected render', async () => { prismaMock.novelPromotionLocation.findUnique.mockResolvedValue({ id: 'prop-1', selectedImageId: 'prop-image-2', images: [ { id: 'prop-image-1', imageIndex: 0, imageUrl: 'https://example.com/prop-1.png', isSelected: false, }, { id: 'prop-image-2', imageIndex: 1, imageUrl: 'https://example.com/prop-2.png', isSelected: true, }, ], }) const mod = await import('@/lib/assets/services/project-location-backed-selection') const result = await mod.confirmProjectLocationBackedSelection('prop-1') expect(result).toEqual({ success: true }) expect(resolveStorageKeyFromMediaValueMock).toHaveBeenCalledWith('https://example.com/prop-1.png') expect(deleteObjectMock).toHaveBeenCalledWith('key:https://example.com/prop-1.png') expect(prismaMock.locationImage.deleteMany).toHaveBeenCalledWith({ where: { locationId: 'prop-1', id: { not: 'prop-image-2' }, }, }) expect(prismaMock.locationImage.update).toHaveBeenCalledWith({ where: { id: 'prop-image-2' }, data: { imageIndex: 0, isSelected: true, }, }) expect(prismaMock.novelPromotionLocation.update).toHaveBeenCalledWith({ where: { id: 'prop-1' }, data: { selectedImageId: 'prop-image-2' }, }) }) it('fails explicitly when confirming without a selected prop render', async () => { prismaMock.novelPromotionLocation.findUnique.mockResolvedValue({ id: 'prop-1', selectedImageId: null, images: [ { id: 'prop-image-1', imageIndex: 0, imageUrl: 'https://example.com/prop-1.png', isSelected: false, }, { id: 'prop-image-2', imageIndex: 1, imageUrl: 'https://example.com/prop-2.png', isSelected: false, }, ], }) const mod = await import('@/lib/assets/services/project-location-backed-selection') await expect(mod.confirmProjectLocationBackedSelection('prop-1')).rejects.toMatchObject({ code: 'INVALID_PARAMS', }) expect(prismaMock.locationImage.deleteMany).not.toHaveBeenCalled() expect(deleteObjectMock).not.toHaveBeenCalled() }) })