import { describe, expect, it } from 'vitest'; import * as XLSX from 'xlsx'; import { buildWorkViewExport, renderWorkViewHtml, renderWorkViewMarkdown, type WorkViewExport } from '../../src/exports/work-view-export'; import { renderWorkViewXlsx } from '../../src/exports/work-view-xlsx'; import type { ImportedDataset } from '../../src/domain/dataset'; import { makeSheet } from '../../src/importers/sheet-factory'; import { defaultReview } from '../../src/domain/review'; function fixture(tool = 'generic', fields: Record = {}) { const values = { Title: 'Synthetic setting', Status: 'Fail', ...fields }; const sheet = makeSheet('Synthetic', [Object.keys(values), Object.values(values)], '', 'synthetic', { toolId: tool }); const dataset = { fileName: 'Synthetic', profileId: tool, sheets: [sheet] } as ImportedDataset; return { dataset, sheet, record: sheet.analysis.records[0] }; } /** Text of every cell in every sheet of the Excel export. */ function xlsxText(report: WorkViewExport): string { const book = XLSX.read(renderWorkViewXlsx(report), { type: 'array' }); return book.SheetNames.flatMap(name => XLSX.utils.sheet_to_json(book.Sheets[name], { header: 1, raw: true, defval: '' }).flat()).join('\n'); } function project(f: ReturnType) { return buildWorkViewExport({ ...f, records: [f.record], generatedAt: '2026-09-18T12:00:00.000Z' }); } describe('two-column Work export contract', () => { it('keeps a rolled-up GPO once, every source operation and baseline variant; review done never changes Fail', () => { const f = fixture('group-policy', { Category: 'GPOs', 'Baseline value': JSON.stringify([ { baseline: 'Synthetic baseline A', expected: 1 }, { baseline: 'Synthetic baseline B', expected: 2 } ]), Remediation: 'Confirm the applicable baseline before changing the setting.', Scope: 'computer' }); f.record.policySourceEvidence = ['backup-a', 'backup-b'].map((id, i) => ({ id, backupKey: id, backupInstanceId: id, sourceReference: `Synthetic/${id}/registry.pol#records=${i + 1}`, sequence: i + 1, values: [{ kind: 'registry-policy', operation: i ? 'delete-value' : 'set-value', key: 'Software\\Synthetic', valueName: 'Setting', ...(i ? {} : { data: 0, typeCode: 4 }) }] })); const report = buildWorkViewExport({ ...f, records: [f.record], generatedAt: 'now', reviews: { [f.record.id]: { ...defaultReview(), status: 'Done', nextStep: 'Synthetic plan', owner: 'Synthetic owner' } } }); expect(report.entries).toHaveLength(1); const text = xlsxText(report); for (const value of ['backup-a', 'backup-b', 'delete-value', 'Data: 0', 'Type code: 4', 'Source sequence: 2', 'Synthetic baseline A', 'Synthetic baseline B', 'Expected: 1', 'Expected: 2', 'Status: Done','kein technischer Behebungsnachweis', 'Synthetic plan']) expect(text).toContain(value); expect(report.entries[0].status).toBe('Fail'); }); it.each(['Not configured', 'No baseline rule', 'Not comparable', 'Review required'])('does not invent a deviation or obligation for %s', status => { const f = fixture('group-policy', { Category: 'GPOs', Status: status }); const report = project(f); expect(report.entries[0].status).toBe(status); expect(xlsxText(report)).toContain('Score-neutral:'); expect(xlsxText(report)).toContain('kein Nachweis der wirksamen Endpoint-Konfiguration'); }); it('includes complete inline Maester results and embedded remediation in the appropriate column', () => { const f = fixture('maester', { Description: 'Synthetic rule.\n\n#### Remediation action\n\n1. Change the synthetic setting.\n2. Rescan.', Result: '| Account | State |\n| --- | --- |\n| Synthetic A | Fail |\n| Synthetic B | Pass |' }); const report = project(f); const [entry] = report.entries; expect(entry.expected.some(block => block.value.includes('Change the synthetic setting'))).toBe(true); expect(entry.observed.some(block => block.label === 'Results · Test result')).toBe(true); for (const text of [xlsxText(report), renderWorkViewHtml(report), renderWorkViewMarkdown(report)]) { expect(text).toContain('Synthetic A'); expect(text).toContain('Synthetic B'); expect(text).toContain('State: Pass'); expect(text).toContain('Change the synthetic setting'); } }); it('retains every linked row, zero, false, empty cells and XML, without active imported markup', () => { const f = fixture('cis-cat', { Results: 'source-table:42 "Evidence"', 'Rule Result XML': '0', Description: '', Remediation: '=HYPERLINK("bad")' }); f.record.sourceFile = 'synthetic.html'; const evidence = makeSheet('Evidence', [['Asset', 'Value', 'Flag', 'Empty'], ...Array.from({ length: 160 }, (_, index) => [`Synthetic ${index}`, '0', 'false', ''])], '', 'synthetic.html', { sourceFile: 'synthetic.html', sourceTableIndex: 42, toolId: 'cis-cat' }); f.dataset.sheets.push(evidence); const report = project(f); expect(report.entries[0].resultRowCount).toBe(160); const html = renderWorkViewHtml(report); expect(html).toContain('Synthetic 159'); expect(html).toContain('Flag: false'); expect(html).toContain('Empty: '); expect(html).toContain('<result>'); expect(html).not.toMatch(/<(?:img|script|iframe)\b/i); expect(html).toContain("default-src 'none'"); expect(xlsxText(report)).toContain('Synthetic 159'); expect(renderWorkViewMarkdown(report)).not.toContain(' { const f = fixture('purple-knight', { Result: 'missing-synthetic-evidence.csv' }); const text = xlsxText(project(f)); expect(text).toContain('Referenzierte Results nicht geladen'); expect(text).toContain('missing-synthetic-evidence.csv'); }); it('is frozen and does not mutate source or select additional rows', () => { const f = fixture('generic', { Expected: '1', Actual: '0' }); const original = JSON.stringify(f); const report = project(f); const before = xlsxText(report); expect(JSON.stringify(f)).toBe(original); f.record.fields.find(field => field.label === 'Actual')!.value = 'changed later'; expect(xlsxText(report)).toBe(before); expect(buildWorkViewExport({ ...f, records: [], generatedAt: 'now' }).entries).toEqual([]); }); it('reports a missing source table even when another linked table exists', () => { const f = fixture('cis-cat', { Results: 'source-table:42 "Available"\nsource-table:43 "Missing"' }); f.record.sourceFile = 'synthetic.html'; f.dataset.sheets.push(makeSheet('Available', [['Asset'], ['Synthetic']], '', 'synthetic.html', { sourceFile: 'synthetic.html', sourceTableIndex: 42, toolId: 'cis-cat' })); const report = project(f); expect(report.entries[0].resultRowCount).toBe(1); const warning = report.entries[0].observed.find(block => block.label === 'Results · fehlende Belege'); expect(warning?.value).toContain('source-table:43'); expect(warning?.value).not.toContain('source-table:42'); }); it('marks only source fields backed by retained deviation evidence, not the source section', () => { const f = fixture('group-policy', { Category: 'GPOs' }); f.record.policySourceEvidence = [{ id: 'synthetic-observation', backupKey: 'synthetic-backup', sourceReference: 'Synthetic/registry.pol', sequence: 1, values: [{ kind: 'registry-policy', operation: 'set-value', key: 'Software\\Synthetic', valueName: 'Value', data: 0, typeCode: 4 }], deviationFields: [['data']] }]; const html = renderWorkViewHtml(project(f)); expect(html).toContain('Data: 0'); expect(html).not.toContain('Operation'); expect(html).not.toContain('