92 lines
3.6 KiB
TypeScript
92 lines
3.6 KiB
TypeScript
|
|
/**
|
||
|
|
* Unit tests for the importer engine internals (no DB): fuzzy column mapping,
|
||
|
|
* row validation (per-field + cross-field), natural-key derivation, and CSV
|
||
|
|
* parsing.
|
||
|
|
*/
|
||
|
|
import { describe, expect, it } from 'vitest';
|
||
|
|
|
||
|
|
import { applyMapping, suggestMapping } from '@/lib/import/mapping';
|
||
|
|
import { validateRow } from '@/lib/import/classify';
|
||
|
|
import { parseCsv } from '@/lib/import/engine';
|
||
|
|
import { companiesAdapter } from '@/lib/import/adapters/companies';
|
||
|
|
import { clientsAdapter } from '@/lib/import/adapters/clients';
|
||
|
|
import { berthsAdapter } from '@/lib/import/adapters/berths';
|
||
|
|
|
||
|
|
describe('suggestMapping', () => {
|
||
|
|
it('maps headers to fields by exact, alias, and fuzzy match', () => {
|
||
|
|
const m = suggestMapping(
|
||
|
|
['Company Name', 'VAT', 'Billing Email', 'Unrelated'],
|
||
|
|
companiesAdapter.targetFields,
|
||
|
|
);
|
||
|
|
expect(m.name).toBe('Company Name'); // alias "companyname"
|
||
|
|
expect(m.taxId).toBe('VAT'); // alias "vat"
|
||
|
|
expect(m.billingEmail).toBe('Billing Email');
|
||
|
|
// A header claimed by one field isn't reused by another.
|
||
|
|
expect(Object.values(m).filter((h) => h === 'Company Name')).toHaveLength(1);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('leaves unmatched fields out of the mapping', () => {
|
||
|
|
const m = suggestMapping(['xyz', 'qqq'], companiesAdapter.targetFields);
|
||
|
|
expect(m.name).toBeUndefined();
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('applyMapping', () => {
|
||
|
|
it('produces fieldKey→cell and drops empty cells', () => {
|
||
|
|
const mapped = applyMapping(
|
||
|
|
{ 'Company Name': ' Acme ', VAT: '', Email: 'a@b.com' },
|
||
|
|
{ name: 'Company Name', taxId: 'VAT', billingEmail: 'Email' },
|
||
|
|
);
|
||
|
|
expect(mapped).toEqual({ name: 'Acme', billingEmail: 'a@b.com' });
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('validateRow', () => {
|
||
|
|
it('flags a missing required field', () => {
|
||
|
|
const errs = validateRow(companiesAdapter, { legalName: 'X Ltd' });
|
||
|
|
expect(errs).toEqual([{ field: 'name', message: 'Name is required' }]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('flags an invalid email', () => {
|
||
|
|
const errs = validateRow(companiesAdapter, { name: 'Acme', billingEmail: 'not-an-email' });
|
||
|
|
expect(errs.some((e) => e.field === 'billingEmail')).toBe(true);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('clients require an email or a phone (cross-field)', () => {
|
||
|
|
expect(validateRow(clientsAdapter, { fullName: 'Jane Doe' })).toEqual([
|
||
|
|
{ field: 'email', message: 'An email or phone is required' },
|
||
|
|
]);
|
||
|
|
expect(validateRow(clientsAdapter, { fullName: 'Jane Doe', email: 'j@d.com' })).toEqual([]);
|
||
|
|
});
|
||
|
|
|
||
|
|
it('berths reject a malformed mooring number', () => {
|
||
|
|
const errs = validateRow(berthsAdapter, { mooringNumber: 'not-a-mooring', area: 'A' });
|
||
|
|
expect(errs.some((e) => e.field === 'mooringNumber')).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('matchKey', () => {
|
||
|
|
it('companies: case-insensitive name', () => {
|
||
|
|
expect(companiesAdapter.matchKey({ name: ' AcMe ' })).toBe('acme');
|
||
|
|
});
|
||
|
|
it('berths: canonicalizes mooring (D-032 → D32)', () => {
|
||
|
|
expect(berthsAdapter.matchKey({ mooringNumber: 'd-032', area: 'D' })).toBe('D32');
|
||
|
|
});
|
||
|
|
it('clients: email key, phone fallback', () => {
|
||
|
|
expect(clientsAdapter.matchKey({ fullName: 'X', email: 'A@B.com' })).toBe('email:a@b.com');
|
||
|
|
const k = clientsAdapter.matchKey({ fullName: 'X', phone: '+1 574 274 0548' });
|
||
|
|
expect(k?.startsWith('phone:')).toBe(true);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
|
||
|
|
describe('parseCsv', () => {
|
||
|
|
it('parses headers + rows, trims, skips blank lines', () => {
|
||
|
|
const { headers, rows } = parseCsv('Name, Email\nAcme, a@b.com\n\nBeta, b@c.com\n');
|
||
|
|
expect(headers).toEqual(['Name', 'Email']);
|
||
|
|
expect(rows).toEqual([
|
||
|
|
{ Name: 'Acme', Email: 'a@b.com' },
|
||
|
|
{ Name: 'Beta', Email: 'b@c.com' },
|
||
|
|
]);
|
||
|
|
});
|
||
|
|
});
|