import { describe, it, expect } from 'vitest'; import { eq } from 'drizzle-orm'; import { search } from '@/lib/services/search.service'; import { db } from '@/lib/db'; import { yachts, companies } from '@/lib/db/schema'; import { makePort, makeClient, makeYacht, makeCompany } from '../../helpers/factories'; // ─── Yachts ────────────────────────────────────────────────────────────────── describe('search.service — yachts', () => { it('matches yachts by name (case-insensitive)', async () => { const port = await makePort(); const owner = await makeClient({ portId: port.id }); await makeYacht({ portId: port.id, ownerType: 'client', ownerId: owner.id, name: 'Sea Breeze', }); await makeYacht({ portId: port.id, ownerType: 'client', ownerId: owner.id, name: 'Wind Dancer', }); const results = await search(port.id, 'BREEZE'); expect(results.yachts.some((y) => y.name === 'Sea Breeze')).toBe(true); expect(results.yachts.some((y) => y.name === 'Wind Dancer')).toBe(false); }); it('matches yachts by hull number', async () => { const port = await makePort(); const owner = await makeClient({ portId: port.id }); await makeYacht({ portId: port.id, ownerType: 'client', ownerId: owner.id, name: 'Nomad', hullNumber: 'HULL-XYZ-999', }); const results = await search(port.id, 'hull-xyz'); expect(results.yachts.some((y) => y.hullNumber === 'HULL-XYZ-999')).toBe(true); }); it('matches yachts by registration', async () => { const port = await makePort(); const owner = await makeClient({ portId: port.id }); await makeYacht({ portId: port.id, ownerType: 'client', ownerId: owner.id, name: 'Registered One', registration: 'REG-ABC-123', }); const results = await search(port.id, 'reg-abc'); expect(results.yachts.some((y) => y.registration === 'REG-ABC-123')).toBe(true); }); it('excludes archived yachts', async () => { const port = await makePort(); const owner = await makeClient({ portId: port.id }); const yacht = await makeYacht({ portId: port.id, ownerType: 'client', ownerId: owner.id, name: 'Ghost Ship', }); await db.update(yachts).set({ archivedAt: new Date() }).where(eq(yachts.id, yacht.id)); const results = await search(port.id, 'ghost ship'); expect(results.yachts.some((y) => y.id === yacht.id)).toBe(false); }); it('is tenant-scoped', async () => { const portA = await makePort(); const portB = await makePort(); const ownerA = await makeClient({ portId: portA.id }); const ownerB = await makeClient({ portId: portB.id }); await makeYacht({ portId: portA.id, ownerType: 'client', ownerId: ownerA.id, name: 'UniqueYachtNameA', }); await makeYacht({ portId: portB.id, ownerType: 'client', ownerId: ownerB.id, name: 'UniqueYachtNameB', }); const resultsA = await search(portA.id, 'UniqueYachtName'); expect(resultsA.yachts.some((y) => y.name === 'UniqueYachtNameA')).toBe(true); expect(resultsA.yachts.some((y) => y.name === 'UniqueYachtNameB')).toBe(false); }); }); // ─── Companies ─────────────────────────────────────────────────────────────── describe('search.service — companies', () => { it('matches companies by name', async () => { const port = await makePort(); await makeCompany({ portId: port.id, overrides: { name: 'Poseidon Maritime Ltd' } }); await makeCompany({ portId: port.id, overrides: { name: 'Neptune Holdings' } }); const results = await search(port.id, 'poseidon'); expect(results.companies.some((c) => c.name === 'Poseidon Maritime Ltd')).toBe(true); expect(results.companies.some((c) => c.name === 'Neptune Holdings')).toBe(false); }); it('matches companies by legal name', async () => { const port = await makePort(); await makeCompany({ portId: port.id, overrides: { name: 'AcmeShort', legalName: 'Acme Legal Holdings Inc.' }, }); const results = await search(port.id, 'Acme Legal'); expect(results.companies.some((c) => c.legalName === 'Acme Legal Holdings Inc.')).toBe(true); }); it('matches companies by tax ID', async () => { const port = await makePort(); await makeCompany({ portId: port.id, overrides: { name: 'TaxyCo', taxId: 'VAT-112233445' }, }); const results = await search(port.id, 'vat-112233'); expect(results.companies.some((c) => c.taxId === 'VAT-112233445')).toBe(true); }); it('excludes archived companies', async () => { const port = await makePort(); const company = await makeCompany({ portId: port.id, overrides: { name: 'ArchivedCompanyXyz' }, }); await db.update(companies).set({ archivedAt: new Date() }).where(eq(companies.id, company.id)); const results = await search(port.id, 'ArchivedCompanyXyz'); expect(results.companies.some((c) => c.id === company.id)).toBe(false); }); it('is tenant-scoped', async () => { const portA = await makePort(); const portB = await makePort(); await makeCompany({ portId: portA.id, overrides: { name: 'UniqueCompanyA' } }); await makeCompany({ portId: portB.id, overrides: { name: 'UniqueCompanyB' } }); const resultsA = await search(portA.id, 'UniqueCompany'); expect(resultsA.companies.some((c) => c.name === 'UniqueCompanyA')).toBe(true); expect(resultsA.companies.some((c) => c.name === 'UniqueCompanyB')).toBe(false); }); }); // ─── Combined ──────────────────────────────────────────────────────────────── describe('search.service — combined', () => { it('returns clients, yachts, and companies for a query that matches multiple', async () => { const port = await makePort(); const client = await makeClient({ portId: port.id, overrides: { fullName: 'Alpha Person' }, }); await makeYacht({ portId: port.id, ownerType: 'client', ownerId: client.id, name: 'Alpha Yacht', }); await makeCompany({ portId: port.id, overrides: { name: 'Alpha Holdings' } }); const results = await search(port.id, 'alpha'); expect(results.clients.some((c) => c.fullName === 'Alpha Person')).toBe(true); expect(results.yachts.some((y) => y.name === 'Alpha Yacht')).toBe(true); expect(results.companies.some((c) => c.name === 'Alpha Holdings')).toBe(true); }); it('returns all result keys even when no matches exist', async () => { const port = await makePort(); const results = await search(port.id, 'zzz-nothing-matches-zzz'); expect(results).toEqual({ clients: [], interests: [], berths: [], yachts: [], companies: [], }); }); });