import { describe, it, expect } from 'vitest'; import { createYacht, updateYacht, archiveYacht } from '@/lib/services/yachts.service'; import { makeClient, makePort, makeYacht, makeAuditMeta } from '../../helpers/factories'; import { db } from '@/lib/db'; import { yachts, yachtOwnershipHistory } from '@/lib/db/schema'; import { eq } from 'drizzle-orm'; describe('yachts.service — createYacht', () => { it('creates a yacht with a client owner and opens an ownership history row', async () => { const port = await makePort(); const client = await makeClient({ portId: port.id }); const yacht = await createYacht( port.id, { name: 'Sea Breeze', owner: { type: 'client', id: client.id }, }, makeAuditMeta(), ); expect(yacht.currentOwnerType).toBe('client'); expect(yacht.currentOwnerId).toBe(client.id); const history = await db .select() .from(yachtOwnershipHistory) .where(eq(yachtOwnershipHistory.yachtId, yacht.id)); expect(history).toHaveLength(1); expect(history[0]!.endDate).toBeNull(); }); it('rejects when ownerType=client but ownerId does not exist', async () => { const port = await makePort(); await expect( createYacht( port.id, { name: 'Phantom', owner: { type: 'client', id: 'nonexistent' } }, makeAuditMeta(), ), ).rejects.toThrow(/owner not found/i); }); it('rejects when ownerType=company but ownerId does not exist', async () => { const port = await makePort(); await expect( createYacht( port.id, { name: 'Phantom', owner: { type: 'company', id: 'nonexistent' } }, makeAuditMeta(), ), ).rejects.toThrow(/owner not found/i); }); it('rejects owner from a different tenant (cross-tenant guard)', async () => { const portA = await makePort(); const portB = await makePort(); const clientInB = await makeClient({ portId: portB.id }); await expect( createYacht( portA.id, { name: 'Wrong Port', owner: { type: 'client', id: clientInB.id } }, makeAuditMeta(), ), ).rejects.toThrow(/owner not found/i); }); }); describe('yachts.service — updateYacht', () => { it('updates name and notes', async () => { const port = await makePort(); const client = await makeClient({ portId: port.id }); const yacht = await makeYacht({ portId: port.id, ownerType: 'client', ownerId: client.id, overrides: { name: 'Original Name' }, }); const updated = await updateYacht( yacht.id, port.id, { name: 'New Name', notes: 'Updated notes' }, makeAuditMeta(), ); expect(updated.name).toBe('New Name'); expect(updated.notes).toBe('Updated notes'); const [row] = await db.select().from(yachts).where(eq(yachts.id, yacht.id)); expect(row!.name).toBe('New Name'); expect(row!.notes).toBe('Updated notes'); }); it('rejects when id does not exist or is cross-tenant', async () => { const portA = await makePort(); const portB = await makePort(); const clientInB = await makeClient({ portId: portB.id }); const yachtInB = await makeYacht({ portId: portB.id, ownerType: 'client', ownerId: clientInB.id, }); await expect( updateYacht(yachtInB.id, portA.id, { name: 'Hijack' }, makeAuditMeta()), ).rejects.toThrow(/yacht/i); await expect( updateYacht('nonexistent-id', portA.id, { name: 'Phantom' }, makeAuditMeta()), ).rejects.toThrow(/yacht/i); }); it('rejects attempt to change currentOwnerId via update', async () => { const port = await makePort(); const client = await makeClient({ portId: port.id }); const yacht = await makeYacht({ portId: port.id, ownerType: 'client', ownerId: client.id, }); await expect( updateYacht( yacht.id, port.id, { currentOwnerId: 'some-other-id' } as unknown as { name: string }, makeAuditMeta(), ), ).rejects.toThrow(/transfer to change ownership/i); }); }); describe('yachts.service — archiveYacht', () => { it('sets archivedAt to a non-null timestamp', async () => { const port = await makePort(); const client = await makeClient({ portId: port.id }); const yacht = await makeYacht({ portId: port.id, ownerType: 'client', ownerId: client.id, }); await archiveYacht(yacht.id, port.id, makeAuditMeta()); const [row] = await db.select().from(yachts).where(eq(yachts.id, yacht.id)); expect(row!.archivedAt).not.toBeNull(); }); it('throws NotFound for cross-tenant or missing yacht', async () => { const portA = await makePort(); const portB = await makePort(); const clientInB = await makeClient({ portId: portB.id }); const yachtInB = await makeYacht({ portId: portB.id, ownerType: 'client', ownerId: clientInB.id, }); await expect(archiveYacht(yachtInB.id, portA.id, makeAuditMeta())).rejects.toThrow(/yacht/i); }); });