Files
tilbudgivern/backend/__tests__/smartPackageExcelValidationService.test.js
T

147 lines
4.7 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 AI findings', async () => {
const completion = {
model: 'gpt-4o-mini',
usage: { prompt_tokens: 100, completion_tokens: 20, total_tokens: 120 },
choices: [{ message: { content: JSON.stringify({
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 openaiService = {
openai: { chat: { completions: { create: jest.fn().mockResolvedValue(completion) } } },
trackTokenUsage: jest.fn()
};
const service = new SmartPackageExcelValidationService(openaiService, { provider: 'openai' });
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(openaiService.openai.chat.completions.create).toHaveBeenCalledWith(
expect.objectContaining({ response_format: { type: 'json_object' } })
);
expect(openaiService.trackTokenUsage).toHaveBeenCalled();
});
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'
})]);
});
});