import { reportToCsv, sanitizeBaseName } from './pipeline.service'; import { RunReport } from './types'; describe('sanitizeBaseName', () => { it('retire l\'extension et les caracteres non surs', () => { expect(sanitizeBaseName('mon nuage (2026) v2.las')).toBe('mon_nuage_2026_v2'); }); it('gere le double-extension .copc.laz (ne retire que le dernier ".laz")', () => { expect(sanitizeBaseName('zone_chantier.copc.laz')).toBe('zone_chantier_copc'); }); it('retombe sur un nom par defaut si le nom de base est vide apres extraction de l\'extension', () => { expect(sanitizeBaseName('.las')).toBe('nuage'); }); it('tronque a 80 caracteres', () => { const long = 'a'.repeat(200) + '.las'; expect(sanitizeBaseName(long).length).toBe(80); }); }); describe('reportToCsv', () => { const constHeightReport: RunReport = { runId: 'run-1', mode: 'const_height', originalFilename: 'test.las', originalFilenameBottom: null, createdAt: '2026-01-01T00:00:00.000Z', params: { zref: 100, zrefSource: 'auto', initialStep: 0.02, initialStepSource: 'auto', gridStepMultiplier: 2, gridStepMultiplierSource: 'auto', maxEdgeLengthMultiplier: 3, maxEdgeLengthSource: 'auto', emptyCellStrategy: 'interpolate', customHeightValue: null, krigingKnn: null, projectionType: 'avg', vertDir: 2, factor: 2, steps: 1, densityMatch: null, sampleStats: { sampleCount: 100, meanNn: 0.02, medianNn: 0.02, bboxApprox: { xmin: 0, xmax: 1, ymin: 0, ymax: 1, zmin: 100, zmax: 101 } }, }, levels: [ { level: 0, gridStep: 0.04, maxEdgeLength: 0.12, clouds: [{ role: 'unique', spatialStep: null, pointCount: 1000, pointRatio: 1, binFile: 'levels/L0_full.bin' }], volume: 12.5, surface: 100, addedVolume: 12.5, removedVolume: 0, matchingCellsPct: 99, groundNonMatchingPct: 1, ceilNonMatchingPct: 0, diffMapImage: 'images/L0_diffmap.png', warn: false, status: 'ok', computeTimeMs: 500, }, { level: 1, gridStep: 0.08, maxEdgeLength: 0.24, clouds: [{ role: 'unique', spatialStep: 0.04, pointCount: 0, pointRatio: 0, binFile: null }], volume: null, surface: null, addedVolume: null, removedVolume: null, matchingCellsPct: null, groundNonMatchingPct: null, ceilNonMatchingPct: null, diffMapImage: null, warn: true, status: 'skipped', message: 'decimation, pas assez de points\navec une virgule, ici', computeTimeMs: 0, }, ], mergedBinFile: 'test_all_levels.bin', }; it('genere un en-tete et une ligne par (niveau, role)', () => { const csv = reportToCsv(constHeightReport); const lines = csv.trim().split('\n'); expect(lines).toHaveLength(3); // header + 2 niveaux (1 nuage chacun en mode const_height) expect(lines[0]).toBe( 'level,role,spatialStep,gridStep,maxEdgeLength,pointCount,pointRatio,volume,surface,addedVolume,removedVolume,matchingCellsPct,groundNonMatchingPct,ceilNonMatchingPct,status,warn,computeTimeMs,message', ); }); it('genere une ligne par nuage (top ET bottom) en mode cloud_compare', () => { const compareReport: RunReport = { ...constHeightReport, mode: 'cloud_compare', levels: [ { level: 0, gridStep: 0.04, maxEdgeLength: 0.12, clouds: [ { role: 'top', spatialStep: 0.02, pointCount: 500, pointRatio: 1, binFile: 'levels/L0_top_full.bin' }, { role: 'bottom', spatialStep: 0.02, pointCount: 480, pointRatio: 1, binFile: 'levels/L0_bottom_full.bin' }, ], volume: 5, surface: 50, addedVolume: 5, removedVolume: 0, matchingCellsPct: 97, groundNonMatchingPct: 3, ceilNonMatchingPct: 0, diffMapImage: 'images/L0_diffmap.png', warn: false, status: 'ok', computeTimeMs: 300, }, ], }; const csv = reportToCsv(compareReport); const lines = csv.trim().split('\n'); expect(lines).toHaveLength(3); // header + 2 lignes (top, bottom) pour le seul niveau expect(lines[1]).toMatch(/^0,top,/); expect(lines[2]).toMatch(/^0,bottom,/); }); it('remplace les sauts de ligne et virgules dans les messages pour ne pas casser le CSV', () => { const csv = reportToCsv(constHeightReport); const lines = csv.trim().split('\n'); expect(lines[2]).not.toContain('\n'); const messageField = lines[2].split(',').slice(17).join(','); expect(messageField).not.toMatch(/,/); }); it('laisse les champs numeriques null en cellule vide plutot que "null"', () => { const csv = reportToCsv(constHeightReport); const lines = csv.trim().split('\n'); expect(lines[2]).not.toContain('null'); }); });