// @vitest-environment jsdom import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it } from 'vitest'; import type { DatasetSheet } from '../../src/domain/dataset'; import type { FindingRecord } from '../../src/domain/finding'; import { FindingReader } from '../../src/features/finding-reader/FindingReader'; import { makeSheet } from '../../src/importers/sheet-factory'; /** Synthetic scanner finding with the given retained fields. */ function record(fields: Array<[string, string]>): FindingRecord { const values = fields.map(([label, value], index) => ({ label, value, index })); return { id: 'synthetic', index: 0, title: 'Synthetic finding', fields: values, filled: values, meta: [] }; } function sheet(rows: string[][]): DatasetSheet { return makeSheet('Synthetic results', rows, '', 'synthetic.html'); } function render(item: FindingRecord, toolId: string, linkedSheets: DatasetSheet[] = []) { const container = document.createElement('div'); container.innerHTML = renderToStaticMarkup( {}} onNext={() => {}} onReview={() => {}}/>); const values = (selector: string) => Object.fromEntries([...container.querySelectorAll(`${selector} .policy-problem__values > div`)] .map(row => [row.querySelector('dt')!.textContent, row.querySelector('dd')!.textContent])); return { container, values }; } const headings = (container: HTMLElement) => [...container.querySelectorAll('.reader-summary > div > h4')].map(item => item.textContent); describe('scanner first read', () => { it('PingCastle: evidence and result rows as the problem, the advised solution as the next step, background below', () => { const item = record([['Rule ID', 'S-Synthetic'], ['Title', 'Synthetic finding'], ['Status', 'Matched'], ['Evidence', 'Synthetic accounts found: 3'], ['Description', 'Synthetic description.'], ['Technical explanation', 'Synthetic explanation.'], ['Advised solution', 'Change the synthetic setting.\n\n$Synthetic = Get-Synthetic'], ['Documentation', 'Synthetic documentation']]); const { container, values } = render(item, 'pingcastle', [sheet([['Account'], ['synthetic-a'], ['synthetic-b'], ['synthetic-c']])]); const problem = container.querySelector('.finding-first-read.policy-problem')!; expect(problem.getAttribute('data-outcome')).toBe(container.querySelector('.outcome-mark')?.getAttribute('data-outcome')); expect(values('.finding-first-read')).toEqual({ Result: 'Synthetic accounts found: 3', Results: '3 rows in 1 table' }); const next = problem.nextElementSibling!; expect(next.matches('section.policy-next-step')).toBe(true); expect(next.textContent).toContain('Change the synthetic setting.'); // The remaining remediation text is complete, only behind a disclosure. expect(next.querySelector('details summary')?.textContent).toBe('Full remediation'); expect(next.querySelector('details')?.textContent).toContain('$Synthetic = Get-Synthetic'); expect(headings(container)).toEqual(['Description', 'Technical explanation', 'Documentation']); }); it('keeps a PingCastle PowerShell command complete and readable outside the prose', () => { const command = 'Get-ADComputer -Filter {(Enabled -eq $true) -and (OperatingSystem -Like "*Windows 7*")} -Property Name,OperatingSystem | format-table Name,OperatingSystem -Wrap -Auto'; const advice = `Upgrade the workstations. You can check the OS with the following PowerShell command: ${command}`; const item = record([['Advised solution', `${advice}\n\nConfirm the resulting inventory.`]]); const { container } = render(item, 'pingcastle'); const step = container.querySelector('.finding-first-read.policy-next-step')!; expect(step.querySelector('p')?.textContent).toBe('Upgrade the workstations. You can check the OS with the following PowerShell command:'); expect(step.querySelector('pre.finding-first-read__command code')?.textContent).toBe(command); expect(step.querySelector('details')?.textContent).toContain('Confirm the resulting inventory.'); expect(item.fields[0].value).toBe(`${advice}\n\nConfirm the resulting inventory.`); }); it('Purple Knight: result message, affected count and likelihood as the problem, remediation as the next step', () => { const item = record([['Name', 'Synthetic finding'], ['Status', 'IOE Found'], ['Description', 'Synthetic description.'], ['Likelihood of compromise', 'Synthetic likelihood.'], ['Result message', 'Found 2 synthetic objects.'], ['Number of results', '2'], ['Remediation', 'Remove the synthetic permission.']]); const { container, values } = render(item, 'purple-knight'); expect(values('.finding-first-read')).toEqual({ Result: 'Found 2 synthetic objects.', Affected: '2', Impact: 'Synthetic likelihood.' }); expect(container.querySelector('.policy-next-step')?.textContent).toContain('Remove the synthetic permission.'); expect(container.querySelector('.policy-next-step details')).toBeNull(); expect(headings(container)).toEqual(['Description']); }); it('CIS-CAT: the report criterion and evaluated row as expected and found, colored by the finding outcome', () => { const item = record([['Benchmark Item', 'Synthetic finding'], ['Result', 'Fail'], ['Description', 'Synthetic description.'], ['Rationale', 'Synthetic rationale.'], ['Impact', 'Synthetic impact.'], ['Remediation', 'Set the synthetic value to 1.']]); const evidence = sheet([['Item', 'Value'], ['Criterion: synthetic value', 'equals 1'], ['Synthetic value', '0']]); evidence.analysis.records[1].highlighted = true; const { container, values } = render(item, 'cis-cat', [evidence]); expect(values('.finding-first-read')).toEqual({ Expected: 'Criterion: synthetic value · equals 1', Found: 'Synthetic value · 0', Impact: 'Synthetic impact.' }); expect(container.querySelector('.finding-first-read [data-state="deviating"] .policy-value-mark.is-deviating')?.textContent).toBe('Synthetic value · 0'); expect(headings(container)).toEqual(['Description', 'Rationale']); }); it('Maester: counts only rows the source marks, keeps the test result out of the reader and links https documentation', () => { const item = record([['ID', 'MT.9001'], ['Title', 'Synthetic finding'], ['Result', 'Failed'], ['ResultDetail', 'Synthetic native test result'], ['Description', 'Synthetic description.'], ['HelpUrl', 'https://maester.dev/docs/tests/MT.9001']]); const results = sheet([['Policy', 'Status'], ['Synthetic A', '❌ Disabled'], ['Synthetic B', '✅ Enabled'], ['Synthetic C', 'Report-only']]); const { container, values } = render(item, 'maester', [results]); expect(values('.finding-first-read')).toEqual({ Results: '1 of 3 rows marked failed · 1 marked passed' }); expect(container.querySelector('.reader-summary')?.textContent).not.toContain('Synthetic native test result'); const link = container.querySelector('.policy-next-step a')!; expect(link.getAttribute('href')).toBe('https://maester.dev/docs/tests/MT.9001'); expect(link.getAttribute('rel')).toBe('noopener noreferrer'); }); it('never offers a non-https documentation link from imported content', () => { const item = record([['ID', 'MT.9002'], ['Title', 'Synthetic finding'], ['Result', 'Failed'], ['HelpUrl', 'javascript:alert(1)']]); expect(render(item, 'maester').container.querySelector('.policy-next-step')).toBeNull(); }); });