Files
tilbudgivern/backend/__tests__/smartPackageExcelValidationService.test.js
Alex d5f65f74d4 feat: mobil tømrerflow og Codex CLI-generering (#8)
Flyt brugerrettet indholdsgenerering til Codex CLI, forbedr mobilflowet og ret CI-miljøets afhængigheder og testkonfiguration.
2026-08-13 11:47:14 +02:00

137 lines
4.3 KiB
JavaScript

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'
})]);
});
});