68 lines
4.3 KiB
TypeScript
68 lines
4.3 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act } from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
import { afterEach, expect, it, vi } from 'vitest';
|
|
import type { ImportedDataset } from '../../src/domain/dataset';
|
|
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
|
|
import { createReferenceRecognizer } from '../../src/domain/reference-recognition';
|
|
import { bundledReferenceCatalogs } from '../../server/reference-data/reference-catalogs';
|
|
import { appendPolicyObservationsToDataset } from '../../src/features/project-import/policy-observations-to-dataset';
|
|
import { FindingReader } from '../../src/features/finding-reader/FindingReader';
|
|
import { makeSheet } from '../../src/importers/sheet-factory';
|
|
|
|
afterEach(() => { vi.unstubAllGlobals(); document.body.replaceChildren(); });
|
|
|
|
it.each([false, true])('retains recognized references when opening the scanner Reader (GPOs: %s)', async withGpos => {
|
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
const sheet = makeSheet('All Records', [
|
|
['Title', 'Status', 'Category'],
|
|
['Synthetic scanner finding', 'Fail', 'Synthetic'],
|
|
['Synthetic indexed finding without references', 'Pass', 'Synthetic'],
|
|
['Synthetic unindexed finding', 'Fail', 'Synthetic']
|
|
], '', 'Synthetic', { isProjectSheet: true });
|
|
const references = createReferenceRecognizer(bundledReferenceCatalogs)([
|
|
{ key: 'attack', label: 'MITRE ATT&CK', value: 'T1059.001' },
|
|
{ key: 'description', label: 'Description', value: 'T9999 D3-CH' }
|
|
]);
|
|
const recognition = { runId: 'synthetic-reference-run', recognizerVersion: '1.0.0',
|
|
createdAt: '2026-09-26T00:00:00Z', references };
|
|
sheet.analysis.records.forEach((record, index) => { record.findingOccurrenceId = `synthetic-occurrence-${index}`; });
|
|
sheet.analysis.records[0].referenceRecognition = recognition;
|
|
sheet.analysis.records[1].referenceRecognition = { ...recognition, references: [] };
|
|
const dataset = { sheets: [sheet], rawText: '', profileId: 'generic', scanTool: 'Synthetic' } as ImportedDataset;
|
|
const observation = { sourceKind: 'policy-observation', sourceResultId: 'synthetic-policy', projectId: 'synthetic',
|
|
assessmentId: 'slot', policyBackupInstanceId: 'synthetic-backup', title: 'Synthetic GPO setting',
|
|
importedAt: '2026-09-26T00:00:00Z', policyScope: 'computer', policyDeliveryMechanism: 'registry-policy',
|
|
policyIdentityStatus: 'resolved', policyIdentityKey: 'synthetic-setting', policyState: 'configured',
|
|
policyValues: [{ kind: 'registry-policy', operation: 'set-value', key: 'Software\\Synthetic', valueName: 'Mode',
|
|
registryType: 4, data: 0 }] } as unknown as UnifiedResultProjection;
|
|
const projected = appendPolicyObservationsToDataset(dataset, 'Synthetic', withGpos ? [observation] : [])!;
|
|
const records = projected.sheets[0].analysis.records;
|
|
expect(records).toHaveLength(withGpos ? 4 : 3);
|
|
expect(records[0].referenceRecognition).toEqual(recognition);
|
|
expect(records[1].referenceRecognition?.references).toEqual([]);
|
|
expect(records[2].referenceRecognition).toBeUndefined();
|
|
if (withGpos) expect(records[3].referenceRecognition).toBeUndefined();
|
|
const host = document.createElement('div'); document.body.append(host);
|
|
const root = createRoot(host);
|
|
try {
|
|
const record = records[0];
|
|
await act(async () => root.render(<FindingReader record={record} title={record.title} linkedSheets={[]}
|
|
resultReferences={[]} search="" position={0} recordCount={records.length}
|
|
onPrevious={() => {}} onNext={() => {}} onReview={() => {}}/>));
|
|
const section = host.querySelector('[aria-label="Catalog references"]')!;
|
|
expect(section).not.toBeNull();
|
|
expect(section.textContent).toContain('ATT&CK T1059.001');
|
|
expect(section.textContent).toContain('D3FEND D3-CH');
|
|
expect(section.textContent).toContain('Mentioned · Not found in this catalog');
|
|
expect(section.textContent).not.toContain('Not indexed yet');
|
|
const details = section.querySelector('details')!;
|
|
await act(async () => details.querySelector('summary')!.click());
|
|
expect(details.open).toBe(true);
|
|
expect(details.textContent).toContain('MITRE ATT&CK · attack');
|
|
expect(details.textContent).toContain('synthetic-reference-run');
|
|
expect(details.textContent).toContain(references[0].catalogVersion);
|
|
expect(details.textContent).toContain(references[0].catalogSha256);
|
|
} finally { await act(async () => root.unmount()); }
|
|
});
|