import { describe, it, expect } from 'vitest'; import { db } from '@/lib/db'; import { clientAddresses, companyAddresses } from '@/lib/db/schema'; import { eq } from 'drizzle-orm'; import { listClientAddresses, addClientAddress, updateClientAddress, removeClientAddress, } from '@/lib/services/clients.service'; import { listCompanyAddresses, addCompanyAddress, updateCompanyAddress, removeCompanyAddress, } from '@/lib/services/companies.service'; import { NotFoundError } from '@/lib/errors'; import { makePort, makeClient, makeCompany } from '../helpers/factories'; const META = (portId: string) => ({ userId: 'test-user', portId, ipAddress: '127.0.0.1', userAgent: 'vitest', }); describe('client addresses service', () => { it('adds, lists, updates, and removes a client address', async () => { const port = await makePort(); const client = await makeClient({ portId: port.id }); // Initially empty. const empty = await listClientAddresses(client.id, port.id); expect(empty).toHaveLength(0); const added = await addClientAddress( client.id, port.id, { label: 'Home', streetAddress: '1 Pier Rd', city: 'Marbella', countryIso: 'ES', subdivisionIso: 'ES-MA', postalCode: '29602', isPrimary: true, }, META(port.id), ); expect(added.label).toBe('Home'); expect(added.countryIso).toBe('ES'); expect(added.isPrimary).toBe(true); const list = await listClientAddresses(client.id, port.id); expect(list).toHaveLength(1); const updated = await updateClientAddress( added.id, client.id, port.id, { city: 'Málaga' }, META(port.id), ); expect(updated.city).toBe('Málaga'); await removeClientAddress(added.id, client.id, port.id, META(port.id)); const after = await listClientAddresses(client.id, port.id); expect(after).toHaveLength(0); }); it('demotes an existing primary when adding a new primary', async () => { const port = await makePort(); const client = await makeClient({ portId: port.id }); const first = await addClientAddress( client.id, port.id, { label: 'Home', isPrimary: true }, META(port.id), ); const second = await addClientAddress( client.id, port.id, { label: 'Office', isPrimary: true }, META(port.id), ); const rows = await db.query.clientAddresses.findMany({ where: eq(clientAddresses.clientId, client.id), }); const primaries = rows.filter((r) => r.isPrimary); expect(primaries).toHaveLength(1); expect(primaries[0]!.id).toBe(second.id); // The previously-primary row is now demoted, not deleted. const firstAfter = rows.find((r) => r.id === first.id); expect(firstAfter?.isPrimary).toBe(false); }); it('demotes other primaries when patching to primary=true', async () => { const port = await makePort(); const client = await makeClient({ portId: port.id }); const first = await addClientAddress( client.id, port.id, { label: 'Home', isPrimary: true }, META(port.id), ); const second = await addClientAddress( client.id, port.id, { label: 'Office', isPrimary: false }, META(port.id), ); await updateClientAddress(second.id, client.id, port.id, { isPrimary: true }, META(port.id)); const rows = await db.query.clientAddresses.findMany({ where: eq(clientAddresses.clientId, client.id), }); const primary = rows.find((r) => r.isPrimary); expect(primary?.id).toBe(second.id); expect(rows.find((r) => r.id === first.id)?.isPrimary).toBe(false); }); it('is tenant-scoped (cross-port access throws NotFoundError)', async () => { const portA = await makePort(); const portB = await makePort(); const client = await makeClient({ portId: portA.id }); await expect(listClientAddresses(client.id, portB.id)).rejects.toThrow(NotFoundError); await expect( addClientAddress(client.id, portB.id, { label: 'X' }, META(portB.id)), ).rejects.toThrow(NotFoundError); }); }); describe('company addresses service', () => { it('adds, lists, updates, and removes a company address', async () => { const port = await makePort(); const company = await makeCompany({ portId: port.id }); const added = await addCompanyAddress( company.id, port.id, { label: 'HQ', countryIso: 'GB', isPrimary: true }, META(port.id), ); expect(added.countryIso).toBe('GB'); const list = await listCompanyAddresses(company.id, port.id); expect(list).toHaveLength(1); const updated = await updateCompanyAddress( added.id, company.id, port.id, { city: 'London' }, META(port.id), ); expect(updated.city).toBe('London'); await removeCompanyAddress(added.id, company.id, port.id, META(port.id)); const after = await db.query.companyAddresses.findMany({ where: eq(companyAddresses.companyId, company.id), }); expect(after).toHaveLength(0); }); it('demotes an existing primary when adding a new primary', async () => { const port = await makePort(); const company = await makeCompany({ portId: port.id }); await addCompanyAddress(company.id, port.id, { label: 'HQ', isPrimary: true }, META(port.id)); const second = await addCompanyAddress( company.id, port.id, { label: 'Branch', isPrimary: true }, META(port.id), ); const rows = await db.query.companyAddresses.findMany({ where: eq(companyAddresses.companyId, company.id), }); const primaries = rows.filter((r) => r.isPrimary); expect(primaries).toHaveLength(1); expect(primaries[0]!.id).toBe(second.id); }); });