import { readFile } from 'node:fs/promises'; import { resolve } from 'node:path'; import * as XLSX from 'xlsx'; import { describe, expect, it } from 'vitest'; import { nodeImportRuntimeAdapters } from '../../server/node-import-runtime-adapters'; import { importSource } from '../../src/importers/engine'; import { importProfiles } from '../../src/profiles/profile-catalog'; const fixtureRoot = resolve(import.meta.dirname, '..', '..', 'fixtures'); async function fixture(name: string) { const bytes = await readFile(resolve(fixtureRoot, name)); return { metadata: { name, sizeBytes: bytes.length, lastModified: 0 }, bytes }; } describe('Node import runtime', () => { it('parses CSV, JSON, HTML, XML and Excel through the shared server-side engine', async () => { const sources = [ await fixture('generic/findings.synthetic.csv'), await fixture('purple-knight/indicator-results.synthetic.json'), await fixture('pingcastle/healthcheck.synthetic.html'), { metadata: { name: 'assessment.xml', sizeBytes: 0, lastModified: 0 }, bytes: new TextEncoder().encode('Server XML') }, (() => { const workbook = XLSX.utils.book_new(); XLSX.utils.book_append_sheet(workbook, XLSX.utils.aoa_to_sheet([ ['Rule ID', 'Title', 'Severity'], ['XLS-1', 'Server Excel', 'High'] ]), 'Findings'); const bytes = new Uint8Array(XLSX.write(workbook, { type: 'array', bookType: 'xlsx' })); return { metadata: { name: 'assessment.xlsx', sizeBytes: bytes.length, lastModified: 0 }, bytes }; })() ]; sources[3].metadata.sizeBytes = sources[3].bytes.length; const datasets = await Promise.all(sources.map(source => importSource( source, { profiles: importProfiles, delimiter: 'auto' }, nodeImportRuntimeAdapters ))); expect(datasets.map(dataset => dataset.format)).toEqual(['CSV', 'JSON', 'HTML', 'XML', 'Excel']); expect(datasets.every(dataset => dataset.sheets.some(sheet => sheet.analysis.records.length > 0))).toBe(true); expect(datasets[2].intermediate?.discardedContent).toBeDefined(); }); it('does not execute imported HTML scripts and rejects malformed XML', async () => { const html = '
Title
Safe
'; const htmlBytes = new TextEncoder().encode(html); const htmlDataset = await importSource({ metadata: { name: 'safe.html', sizeBytes: htmlBytes.length, lastModified: 0 }, bytes: htmlBytes }, { profiles: importProfiles }, nodeImportRuntimeAdapters); expect((globalThis as Record).__assessorExecuted).toBeUndefined(); expect(htmlDataset.intermediate?.diagnostics).toContainEqual(expect.objectContaining({ code: 'html-active-content-removed' })); const xmlBytes = new TextEncoder().encode(''); await expect(importSource({ metadata: { name: 'broken.xml', sizeBytes: xmlBytes.length, lastModified: 0 }, bytes: xmlBytes }, { profiles: importProfiles }, nodeImportRuntimeAdapters)).rejects.toThrow('XML konnte nicht geparst werden'); }); it('rejects external XML entities before DOM parsing and strips active HTML attributes', async () => { const xml = ']>&secret;'; const xmlBytes = new TextEncoder().encode(xml); await expect(importSource({ metadata: { name: 'external.xml', sizeBytes: xmlBytes.length, lastModified: 0 }, bytes: xmlBytes }, { profiles: importProfiles }, nodeImportRuntimeAdapters)).rejects.toMatchObject({ diagnostics: [expect.objectContaining({ code: 'xml-doctype-rejected' })] }); // Ereignisattribute und aktive Elemente fallen weiterhin heraus; ein // Verweisziel als Web-Adresse bleibt seit der Freigabe erhalten, ein // Skriptschema nicht. const decoded = nodeImportRuntimeAdapters.markup.decodeHtml('Safe
'); expect(decoded.removedContentCount).toBeGreaterThanOrEqual(2); expect(decoded.fallbackRows.flat().join(' ')).toContain('Safe'); const linked = nodeImportRuntimeAdapters.markup.decodeHtml('
DokuBoese
'); const cells = linked.tables[0]?.rows.flat().join(' ') ?? ''; expect(cells).toContain(''); expect(cells).not.toContain('onclick'); expect(cells).not.toContain('javascript:'); }); it('parses a 5.9 MB PingCastle-style HTML report without recursive DOM failure', async () => { const html = 'PingCastle Healthcheck' + '' + '' + '' + '' + '
Rules
Rule IDTitleSeverity
PC-LARGE-1Large PingCastle reportHigh
'; const bytes = new TextEncoder().encode(html); const dataset = await importSource({ metadata: { name: 'ad_hc_example.local.html', sizeBytes: bytes.length, lastModified: 0 }, bytes }, { profiles: importProfiles }, nodeImportRuntimeAdapters); expect(dataset.format).toBe('HTML'); expect(dataset.profileId).toBe('pingcastle'); expect(dataset.scanTool).toBe('PingCastle'); expect(dataset.sheets.find(sheet => sheet.name === 'Rules')?.rows[1]).toEqual([ 'PC-LARGE-1', 'Large PingCastle report', 'High' ]); expect(dataset.intermediate?.diagnostics).toContainEqual(expect.objectContaining({ code: 'html-bounded-extraction', severity: 'info' })); }, 30_000); it('routes a 1.6 MB PingCastle-style HTML report through bounded extraction before the worker time limit', async () => { const html = 'PingCastle Healthcheck' + '' + '' + '' + '' + '
Rules
Rule IDTitleSeverity
PC-BOUNDED-1Bounded synthetic reportHigh
'; const bytes = new TextEncoder().encode(html); const dataset = await importSource({ metadata: { name: 'ad_hc_bounded.synthetic.html', sizeBytes: bytes.length, lastModified: 0 }, bytes }, { profiles: importProfiles }, nodeImportRuntimeAdapters); expect(dataset.format).toBe('HTML'); expect(dataset.profileId).toBe('pingcastle'); expect(dataset.sheets.find(sheet => sheet.name === 'Rules')?.rows[1]).toEqual([ 'PC-BOUNDED-1', 'Bounded synthetic report', 'High' ]); expect(dataset.intermediate?.diagnostics).toContainEqual(expect.objectContaining({ code: 'html-bounded-extraction', severity: 'info' })); }, 30_000); });