const fs = require('fs'); const os = require('os'); const path = require('path'); const XLSX = require('xlsx'); const SmartPackageExcelValidationService = require('../src/services/smartPackageExcelValidationService'); const createWorkbook = () => { const directory = fs.mkdtempSync(path.join(os.tmpdir(), 'smartpakker-validation-')); const filePath = path.join(directory, 'smartpakker.xlsx'); const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet([{ Pakkenavn: 'Byggepladspakke', Kategori: 'Byggeplads', Enhed: 'stk.', Enhedspris: '0.00 kr.', Beskrivelse: 'Testpakke' }]), 'Byggeplads'); XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet([{ Pakkenavn: 'Tømrerpakke', Kategori: 'Tag', Enhed: 'm²', 'Pris pr. enhed': '120.00 kr.', 'Timepris (kr.)': '600.00 kr.', 'Timer pr. enhed': '0.20', Beskrivelse: 'Tømrerarbejde' }]), 'Tømrer opgaver'); XLSX.utils.book_append_sheet(workbook, XLSX.utils.json_to_sheet([{ Smartpakkenavn: 'Udlejningspakke', Kategori: 'Maskiner', Enhed: 'dag', Standardpris: '500.00 kr.', Beskrivelse: 'Leje' }]), 'Udlejning'); XLSX.writeFile(workbook, filePath); return { directory, filePath }; }; describe('SmartPackageExcelValidationService', () => { let fixture; beforeEach(() => { fixture = createWorkbook(); }); afterEach(() => { fs.rmSync(fixture.directory, { recursive: true, force: true }); }); test('returns deterministic warnings without blocking import', () => { const service = new SmartPackageExcelValidationService(null); const result = service.validateStandard(fixture.filePath); expect(result.blocksImport).toBe(false); expect(result.summary.totalPackages).toBe(2); expect(result.summary.skippedZeroPrice).toBe(1); expect(result.issues).toEqual(expect.arrayContaining([ expect.objectContaining({ code: 'zero_price_skipped', packageName: 'Byggepladspakke' }) ])); }); test('combines standard validation with structured Codex findings', async () => { const codexRunner = jest.fn().mockResolvedValue({ status: 'warnings', summary: 'En række bør kontrolleres.', issues: [{ severity: 'warning', sheet: 'Byggeplads', row: 2, packageName: 'Byggepladspakke', message: 'Navnet er meget generelt.', suggestion: 'Gør navnet mere konkret.' }] }); const service = new SmartPackageExcelValidationService(null, { codexRunner }); const result = await service.validateWithAi(fixture.filePath); expect(result.standard.summary.totalPackages).toBe(2); expect(result.ai.status).toBe('warnings'); expect(result.ai.issues).toHaveLength(1); expect(codexRunner).toHaveBeenCalled(); expect(result.ai.provider).toBe('codex-cli'); }); test('uses Codex CLI provider without requiring an API client', async () => { const codexRunner = jest.fn().mockResolvedValue({ status: 'passed', summary: 'Ingen semantiske problemer fundet.', issues: [] }); const service = new SmartPackageExcelValidationService(null, { provider: 'codex', codexRunner }); const result = await service.validateWithAi(fixture.filePath); expect(codexRunner).toHaveBeenCalledWith(expect.objectContaining({ packages: expect.any(Array) })); expect(result.ai.provider).toBe('codex-cli'); expect(result.ai.status).toBe('passed'); }); test('resolves AI findings to an existing concrete Excel row', async () => { const codexRunner = jest.fn().mockResolvedValue({ status: 'warnings', summary: 'En række kræver kontrol.', issues: [{ severity: 'warning', sheet: 'Forkert fane', row: 999, packageName: 'Tømrerpakke', field: 'Enhed', message: 'Enheden kan være forkert.', suggestion: 'Kontrollér enheden.', relatedRows: [] }] }); const service = new SmartPackageExcelValidationService(null, { provider: 'codex', codexRunner }); const result = await service.validateWithAi(fixture.filePath); expect(result.ai.issues).toEqual([expect.objectContaining({ sheet: 'Tømrer opgaver', row: 2, packageName: 'Tømrerpakke', field: 'Enhed' })]); }); });