// @vitest-environment jsdom import { describe, expect, it } from 'vitest'; import maesterHtmlFixture from '../../fixtures/maester/test-results.synthetic.html?raw'; import purpleKnightHtmlFixture from '../../fixtures/purple-knight/security-assessment.synthetic.html?raw'; import { assertCanonicalAssessmentModel } from '../../src/domain/canonical-validation'; import { confirmEntryMappings, previewEntryFromDataset } from '../../src/features/import-workflow/import-workflow'; import { createCanonicalImportBatch } from '../../src/importers/canonical-import'; import { importFile } from '../../src/importers/importer'; import { importProfiles } from '../../src/profiles/profile-catalog'; import { linkResultSheets } from '../../src/profiles/purple-knight/result-linker'; import { canonicalModelToDataset } from '../../src/features/project-import/canonical-model-to-dataset'; function candidateValue( candidate: NonNullable>['extraction']>['candidates'][number], label: string ): string | undefined { return candidate.fields.find(field => field.label === label)?.decodedValue; } async function canonicalCount(content: string, fileName: string, type: string): Promise { const dataset = await importFile(new File([content], fileName, { type }), { profiles: importProfiles }); const preview = previewEntryFromDataset(dataset, { sha256: fileName.padEnd(64, '0').slice(0, 64), fileName, sizeBytes: content.length, lastModified: 0 }); const entry = confirmEntryMappings(preview, importProfiles); const model = createCanonicalImportBatch(fileName, [entry], '2026-08-25T08:00:00.000Z').model; assertCanonicalAssessmentModel(model); return model.findingOccurrences.length; } describe('script-embedded scanner HTML source extractors', () => { it('extracts Maester tests from inert embedded JSON without executing report scripts', async () => { delete (window as Window & { __syntheticActiveContentExecuted?: boolean }).__syntheticActiveContentExecuted; const imported = await importFile(new File([maesterHtmlFixture], 'Maester-Synthetic-Report.html', { type: 'text/html' }), { profiles: importProfiles }); expect((window as Window & { __syntheticActiveContentExecuted?: boolean }).__syntheticActiveContentExecuted).toBeUndefined(); expect(imported.profileId).toBe('maester'); expect(imported.extraction?.extractor.id).toBe('maester-html-source-extractor'); expect(imported.extraction?.candidates).toHaveLength(2); expect(candidateValue(imported.extraction!.candidates[0], 'ID')).toBe('MT.9001'); expect(candidateValue(imported.extraction!.candidates[0], 'Result')).toBe('Failed'); expect(candidateValue(imported.extraction!.candidates[0], 'ResultDetail')).toBe('The synthetic policy needs review.'); expect(imported.extraction!.candidates[0].source.objectPath).toBe('$.Tests[0]'); expect(await canonicalCount(maesterHtmlFixture, 'Maester-Synthetic-Report.html', 'text/html')).toBe(2); }); it('extracts PurpleKnight indicators and preserves source-object provenance', async () => { delete (window as Window & { __syntheticActiveContentExecuted?: boolean }).__syntheticActiveContentExecuted; const imported = await importFile(new File( [purpleKnightHtmlFixture], 'Security_Assessment_Report_synthetic.html', { type: 'text/html' } ), { profiles: importProfiles }); expect((window as Window & { __syntheticActiveContentExecuted?: boolean }).__syntheticActiveContentExecuted).toBeUndefined(); expect(imported.profileId).toBe('purple-knight'); expect(imported.extraction?.extractor.id).toBe('purple-knight-html-source-extractor'); expect(imported.extraction?.candidates).toHaveLength(2); expect(imported.extraction?.candidates[0].fields.map(field => field.label)).toEqual([ 'ShortName', 'Name', 'SI version', 'Target', 'Status', 'Runtime', 'Score', 'Severity', 'Weight', 'Category', 'MITRE ATT&CK_1', 'MITRE ATT&CK_2', 'MITRE ATT&CK_3', 'MITRE ATT&CK_4', 'ANSSI_1', 'ANSSI_2', 'ANSSI_3', 'ANSSI_4', 'MITRE D3FEND_1', 'MITRE D3FEND_2', 'Description', 'Likelihood of compromise', 'Result message', 'Number of results', 'Result', 'Remediation' ]); expect(candidateValue(imported.extraction!.candidates[0], 'ShortName')).toBe('SI900001'); expect(candidateValue(imported.extraction!.candidates[0], 'Status')).toBe('IOE Found'); expect(candidateValue(imported.extraction!.candidates[0], 'Result')).toBe('SI900001'); expect(candidateValue(imported.extraction!.candidates[0], 'Description')).toBe('A synthetic description.'); expect(candidateValue(imported.extraction!.candidates[0], 'Category')).toBe('Synthetic Category'); expect(imported.extraction!.candidates[0].source.objectPath).toContain('$.Category_1'); expect(imported.extraction?.supportingTables).toEqual([expect.objectContaining({ name: 'SI900001', headers: ['Object', 'EventTimestamp', 'Ignored'], rows: [['Synthetic value', '2026-05-27 12:37', 'False']] })]); expect(imported.sheets.find(sheet => sheet.sourceSheet === 'SI900001')?.rows) .toEqual([['Object', 'EventTimestamp', 'Ignored'], ['Synthetic value', '2026-05-27 12:37', 'False']]); expect(await canonicalCount( purpleKnightHtmlFixture, 'Security_Assessment_Report_synthetic.html', 'text/html' )).toBe(2); }); it('links embedded PurpleKnight HTML result tables like workbook result sheets', async () => { const imported = await importFile(new File( [purpleKnightHtmlFixture], 'Security_Assessment_Report_synthetic.html', { type: 'text/html' } ), { profiles: importProfiles }); const preview = previewEntryFromDataset(imported, { sha256: 'purple-knight-html-result'.padEnd(64, '0').slice(0, 64), fileName: imported.fileName, sizeBytes: purpleKnightHtmlFixture.length, lastModified: 0 }); const model = createCanonicalImportBatch( 'purple-knight-html-result', [confirmEntryMappings(preview, importProfiles)], '2026-08-25T08:00:00.000Z' ).model; const dataset = canonicalModelToDataset(model)!; const mainSheet = dataset.sheets.find(sheet => sheet.analysis.records.some(record => record.fields.some(field => field.label === 'ShortName' && field.value === 'SI900001')))!; const record = mainSheet.analysis.records.find(item => item.fields.some(field => field.label === 'ShortName' && field.value === 'SI900001'))!; const linked = linkResultSheets(record, dataset.sheets, mainSheet.id); expect(linked).toHaveLength(1); expect(linked[0].sourceSheet).toBe('SI900001'); expect(linked[0].rows).toEqual([ ['Object', 'EventTimestamp', 'Ignored'], ['Synthetic value', '2026-05-27 12:37', 'False'] ]); }); it('does not treat an empty Maester report template as a finding export', async () => { const template = ''; await expect(importFile(new File([template], 'ReportTemplate.html', { type: 'text/html' }), { profiles: importProfiles })) .rejects.toThrow(/keine auswertbaren Tabellen/); }); it('extracts all tenants from a Maester multi-tenant HTML report', async () => { const report = `
Maester Security Tests
`; const imported = await importFile(new File([report], 'Maester-MultiTenant-Synthetic.html', { type: 'text/html' }), { profiles: importProfiles }); expect(imported.profileId).toBe('maester'); expect(imported.extraction?.candidates.map(candidate => candidate.source.objectPath)).toEqual([ '$.Tenants[0].Tests[0]', '$.Tenants[1].Tests[0]' ]); }); });