import { NotFoundException } from '@nestjs/common'; import { ColoniesService } from './colonies.service'; import { PrismaService } from '../prisma/prisma.service'; import { AuditLogService } from '../audit/audit-log.service'; describe('ColoniesService', () => { let service: ColoniesService; let tx: { colony: { create: jest.Mock; update: jest.Mock; findUnique: jest.Mock }; colonyApiaryHistory: { create: jest.Mock }; colonyLineage: { create: jest.Mock }; }; let prisma: { colony: { findMany: jest.Mock; findUnique: jest.Mock; create: jest.Mock; update: jest.Mock; }; colonyApiaryHistory: { findMany: jest.Mock }; queenColonyHistory: { findMany: jest.Mock }; colonyLineage: { findMany: jest.Mock }; $transaction: jest.Mock; }; let auditLog: { record: jest.Mock }; const parentColony = { id: 'colony-1', code: 'V-001', apiaryId: 'apiary-1', status: 'ACTIVE', deactivatedAt: null, }; beforeEach(() => { tx = { colony: { create: jest.fn(), update: jest.fn(), findUnique: jest.fn() }, colonyApiaryHistory: { create: jest.fn() }, colonyLineage: { create: jest.fn() }, }; prisma = { colony: { findMany: jest.fn(), findUnique: jest.fn(), create: jest.fn(), update: jest.fn(), }, colonyApiaryHistory: { findMany: jest.fn() }, queenColonyHistory: { findMany: jest.fn() }, colonyLineage: { findMany: jest.fn() }, $transaction: jest.fn((callback: (txArg: typeof tx) => unknown) => callback(tx), ), }; auditLog = { record: jest.fn().mockResolvedValue(undefined) }; service = new ColoniesService( prisma as unknown as PrismaService, auditLog as unknown as AuditLogService, ); }); it('findById throws NotFoundException when missing', async () => { prisma.colony.findUnique.mockResolvedValue(null); await expect(service.findById('missing')).rejects.toBeInstanceOf( NotFoundException, ); }); it('move updates the apiary and records apiary history', async () => { prisma.colony.findUnique.mockResolvedValue(parentColony); tx.colony.update.mockResolvedValue({ ...parentColony, apiaryId: 'apiary-2', }); tx.colonyApiaryHistory.create.mockResolvedValue({}); const result = await service.move( 'colony-1', { apiaryId: 'apiary-2' }, 'user-1', ); expect(result.apiaryId).toBe('apiary-2'); const [[historyArg]] = tx.colonyApiaryHistory.create.mock.calls as [ [{ data: { colonyId: string; apiaryId: string } }], ]; expect(historyArg.data.colonyId).toBe('colony-1'); expect(historyArg.data.apiaryId).toBe('apiary-2'); }); it('split creates a new colony and a SPLIT lineage entry', async () => { prisma.colony.findUnique.mockResolvedValue(parentColony); const child = { id: 'colony-2', code: 'V-001-A', apiaryId: 'apiary-1' }; tx.colony.create.mockResolvedValue(child); tx.colonyLineage.create.mockResolvedValue({}); const result = await service.split( 'colony-1', { code: 'V-001-A', colonyType: 'NUCLEUS_COLONY' }, 'user-1', ); expect(result).toEqual(child); const [[lineageArg]] = tx.colonyLineage.create.mock.calls as [ [{ data: { type: string; fromColonyId: string; toColonyId: string } }], ]; expect(lineageArg.data.type).toBe('SPLIT'); expect(lineageArg.data.fromColonyId).toBe('colony-1'); expect(lineageArg.data.toColonyId).toBe('colony-2'); }); it('merge marks the merged colony as MERGED and records a MERGE lineage entry', async () => { prisma.colony.findUnique .mockResolvedValueOnce(parentColony) .mockResolvedValueOnce({ ...parentColony, id: 'colony-1', }); tx.colony.findUnique.mockResolvedValue({ id: 'colony-3', status: 'ACTIVE', }); tx.colony.update.mockResolvedValue({}); tx.colonyLineage.create.mockResolvedValue({}); await service.merge( 'colony-1', { mergedColonyIds: ['colony-3'] }, 'user-1', ); const [[updateArg]] = tx.colony.update.mock.calls as [ [{ where: { id: string }; data: { status: string } }], ]; expect(updateArg.where.id).toBe('colony-3'); expect(updateArg.data.status).toBe('MERGED'); const [[lineageArg]] = tx.colonyLineage.create.mock.calls as [ [{ data: { type: string; fromColonyId: string; toColonyId: string } }], ]; expect(lineageArg.data.type).toBe('MERGE'); expect(lineageArg.data.fromColonyId).toBe('colony-3'); expect(lineageArg.data.toColonyId).toBe('colony-1'); }); it('dissolve sets status DISSOLVED and records DEACTIVATE', async () => { prisma.colony.findUnique.mockResolvedValue(parentColony); prisma.colony.update.mockResolvedValue({ ...parentColony, status: 'DISSOLVED', }); const result = await service.dissolve('colony-1', 'user-1'); expect(result.status).toBe('DISSOLVED'); expect(auditLog.record).toHaveBeenCalledWith( expect.objectContaining({ action: 'DEACTIVATE' }), ); }); });