feat: add catalog CSV parser with per-row validation
This commit is contained in:
45
mvp/b2c/tests/budget/csv.test.ts
Normal file
45
mvp/b2c/tests/budget/csv.test.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { parseCatalogCsv } from '@/budget/csv';
|
||||
|
||||
const HEADER = 'categoria,nombre,calidad,precio,unidad,descriptor_render,sku';
|
||||
|
||||
describe('parseCatalogCsv', () => {
|
||||
it('parsea filas válidas y convierte precio a céntimos', () => {
|
||||
const csv = [
|
||||
HEADER,
|
||||
'suelo,Cerámico gris,media,28.00,m2,suelo cerámico gris,SUE-M',
|
||||
'mobiliario,Muebles cocina,premium,550,ml,muebles laminado roble,MOB-P',
|
||||
].join('\n');
|
||||
const { rows, errors } = parseCatalogCsv(csv);
|
||||
expect(errors).toHaveLength(0);
|
||||
expect(rows).toHaveLength(2);
|
||||
expect(rows[0]).toMatchObject({
|
||||
categoria: 'suelo',
|
||||
calidad: 'media',
|
||||
precioUnit: 2800,
|
||||
unidad: 'm2',
|
||||
sku: 'SUE-M',
|
||||
});
|
||||
expect(rows[1].precioUnit).toBe(55000);
|
||||
});
|
||||
|
||||
it('reporta errores por fila sin abortar las válidas', () => {
|
||||
const csv = [
|
||||
HEADER,
|
||||
'suelo,Bueno,media,28,m2,desc,SUE-M',
|
||||
'inventada,Malo,media,10,m2,desc,X', // categoria inválida
|
||||
'pared,Sin precio,media,abc,m2,desc,PAR-M', // precio no numérico
|
||||
].join('\n');
|
||||
const { rows, errors } = parseCatalogCsv(csv);
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(errors).toHaveLength(2);
|
||||
expect(errors[0].line).toBe(3); // 1-indexed incluyendo cabecera
|
||||
expect(errors[1].line).toBe(4);
|
||||
});
|
||||
|
||||
it('devuelve error global si falta la cabecera esperada', () => {
|
||||
const { rows, errors } = parseCatalogCsv('a,b,c\n1,2,3');
|
||||
expect(rows).toHaveLength(0);
|
||||
expect(errors[0].message).toMatch(/cabecera/i);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user