/** * Schema constraint integration tests. * * Verifies DB-level enforcement of: * - Partial unique index idx_yoh_active (one active ownership row per yacht) * - Partial unique index idx_br_active (one active reservation per berth) * - Non-active reservations on the same berth are permitted * - Case-insensitive company name uniqueness within a port * - Same company name allowed across different ports */ import { describe, it, expect, beforeAll } from 'vitest'; import { db } from '@/lib/db'; import { yachtOwnershipHistory } from '@/lib/db/schema/yachts'; import { berthReservations } from '@/lib/db/schema/reservations'; import { companies } from '@/lib/db/schema/companies'; import { makeBerth, makeClient, makeCompany, makePort, makeYacht } from '../helpers/factories'; // ─── DB availability ───────────────────────────────────────────────────────── let dbAvailable = false; beforeAll(async () => { try { await db.execute(`SELECT 1`); dbAvailable = true; } catch (err) { console.warn( '[schema-constraints] DATABASE_URL not reachable — skipping integration tests', err, ); } }); function itDb(name: string, fn: () => Promise) { it(name, async () => { if (!dbAvailable) return; await fn(); }); } // ─── Tests ─────────────────────────────────────────────────────────────────── describe('schema constraints', () => { itDb( 'rejects a second active ownership row per yacht (partial unique idx_yoh_active)', async () => { const port = await makePort(); const clientA = await makeClient({ portId: port.id }); const clientB = await makeClient({ portId: port.id }); const yacht = await makeYacht({ portId: port.id, ownerType: 'client', ownerId: clientA.id, }); // makeYacht already inserted one active (end_date IS NULL) ownership row. await expect( db.insert(yachtOwnershipHistory).values({ yachtId: yacht.id, ownerType: 'client', ownerId: clientB.id, startDate: new Date(), endDate: null, // another open row — should violate partial unique createdBy: 'test', }), ).rejects.toThrow(/duplicate key|unique/i); }, ); itDb('rejects a second active reservation per berth (partial unique idx_br_active)', async () => { const port = await makePort(); const clientA = await makeClient({ portId: port.id }); const clientB = await makeClient({ portId: port.id }); const yachtA = await makeYacht({ portId: port.id, ownerType: 'client', ownerId: clientA.id, }); const yachtB = await makeYacht({ portId: port.id, ownerType: 'client', ownerId: clientB.id, }); const berth = await makeBerth({ portId: port.id }); await db.insert(berthReservations).values({ berthId: berth.id, portId: port.id, clientId: clientA.id, yachtId: yachtA.id, status: 'active', startDate: new Date(), createdBy: 'test', }); await expect( db.insert(berthReservations).values({ berthId: berth.id, portId: port.id, clientId: clientB.id, yachtId: yachtB.id, status: 'active', startDate: new Date(), createdBy: 'test', }), ).rejects.toThrow(/duplicate key|unique/i); }); itDb( 'allows multiple non-active reservations on the same berth (partial index ignores non-active)', async () => { const port = await makePort(); const clientA = await makeClient({ portId: port.id }); const yachtA = await makeYacht({ portId: port.id, ownerType: 'client', ownerId: clientA.id, }); const berth = await makeBerth({ portId: port.id }); // Two ended reservations on same berth — both should succeed // (partial index only constrains status='active'). await expect( db.insert(berthReservations).values([ { berthId: berth.id, portId: port.id, clientId: clientA.id, yachtId: yachtA.id, status: 'ended', startDate: new Date('2024-01-01'), endDate: new Date('2024-06-30'), createdBy: 'test', }, { berthId: berth.id, portId: port.id, clientId: clientA.id, yachtId: yachtA.id, status: 'ended', startDate: new Date('2024-07-01'), endDate: new Date('2024-12-31'), createdBy: 'test', }, ]), ).resolves.toBeDefined(); }, ); itDb('enforces case-insensitive company name uniqueness per port', async () => { const port = await makePort(); await makeCompany({ portId: port.id, overrides: { name: 'Aegean Holdings' } }); await expect( db.insert(companies).values({ portId: port.id, name: 'AEGEAN HOLDINGS' }), ).rejects.toThrow(/duplicate key|unique/i); }); itDb('allows same-name companies in different ports', async () => { const portA = await makePort(); const portB = await makePort(); await makeCompany({ portId: portA.id, overrides: { name: 'Aegean Holdings' } }); await expect( db.insert(companies).values({ portId: portB.id, name: 'Aegean Holdings' }), ).resolves.toBeDefined(); }); });