195 lines
5.7 KiB
TypeScript
195 lines
5.7 KiB
TypeScript
|
|
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);
|
||
|
|
});
|
||
|
|
});
|