Assessor_2.0/tests/features/project-sheet-merge-metadat...

72 lines
5.0 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { ImportedDataset } from '../../src/domain/dataset';
import type { FindingRecord } from '../../src/domain/finding';
import { POLICY_EVALUATION_ENGINE, type PolicyEvaluationResult, type StoredPolicyEvaluation } from '../../src/domain/policy-evaluation';
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
import { appendPolicyObservationsToDataset } from '../../src/features/project-import/policy-observations-to-dataset';
import { makeSheet } from '../../src/importers/sheet-factory';
/** Properties the rebuilt project sheet recomputes from the extended row layout. */
const LAYOUT_KEYS = new Set(['id', 'index', 'title', 'fields', 'filled', 'meta', 'sourceFile', 'sourceSheet', 'highlighted']);
function policyInputs() {
const observation = {
sourceKind: 'policy-observation', sourceResultId: 'synthetic-observation', projectId: 'synthetic', assessmentId: 'slot',
policyBackupInstanceId: 'backup-1', title: 'Synthetic setting', importedAt: '2026-01-01T00:00:00Z',
policyScope: 'computer', policyDeliveryMechanism: 'registry-policy', policyIdentityStatus: 'resolved',
policyIdentityKey: 'synthetic-setting', policyValues: [{ kind: 'registry-policy', operation: 'set-value',
key: 'Software\\Policies\\Synthetic', valueName: 'Mode', typeCode: 4, data: { kind: 'uint32', value: 0 } }]
} as unknown as UnifiedResultProjection;
const comparison = {
id: 'synthetic-comparison', observationIds: [observation.sourceResultId], baselineId: 'baseline', baselineLabel: 'Synthetic baseline',
identity: { id: 'setting-synthetic', scope: 'computer', target: { kind: 'registry-value', key: 'Synthetic', valueName: 'Mode' } },
baselineState: 'explicit-recommendation', customerState: 'configured', classification: 'confirmed_configured_deviation', diagnosticCodes: [],
comparator: { version: 1, comparator: { kind: 'equality' }, relation: 'different',
baseline: { kind: 'integer', value: '1' }, actual: { kind: 'integer', value: '0' }, diagnosticCodes: [] }
} as unknown as PolicyEvaluationResult;
const evaluation = { run: { id: 'evaluation', projectId: 'synthetic', aggregationRunId: 'aggregation',
completedAt: '2026-01-01T00:00:00Z', evaluator: POLICY_EVALUATION_ENGINE }, results: [comparison] } as StoredPolicyEvaluation;
return { observation, evaluation };
}
describe('project sheet merge with GPO results', () => {
// Regression 2026-09-26: the merge copied an explicit list of properties, so newly added record metadata
// (CIS section titles, recognized references) silently disappeared in timeslots with GPO results.
it('keeps every non-layout property of a scanner record, including metadata unknown to the merge', () => {
const scannerSheet = makeSheet('Synthetic · All Records', [
['Title', 'Status', 'Source: Scan'],
['Synthetic scanner finding', 'Fail', 'CIS-CAT Pro · 2026-01-01T00:00:00.000Z']
], '', 'Synthetic', { isProjectSheet: true });
const original = scannerSheet.analysis.records[0];
Object.assign(original, {
findingOccurrenceId: 'occurrence-synthetic', findingDefinitionId: 'definition-synthetic',
scanRunId: 'run-synthetic', importFileId: 'file-synthetic',
benchmarkSectionTitles: new Map([['2.2', 'Synthetic User Rights Assignment']]),
referenceRecognition: { runId: 'reference-run', references: [] },
policyFailingBackupSources: [{ id: 'backup-synthetic', label: 'Synthetic backup' }],
policySourceEvidence: [], policyBaselineLabels: ['Synthetic baseline'], policyBaselineComparisons: [],
policyReviewMembers: [{ id: 'member-synthetic', baseline: 'Synthetic baseline' }],
// Stands for any record metadata added later without touching the merge.
syntheticFutureMetadata: { marker: 'survives' }
});
const dataset = { sheets: [scannerSheet], rawText: '', profileId: 'generic', scanTool: 'Multiple Tools' } as unknown as ImportedDataset;
const { observation, evaluation } = policyInputs();
const merged = appendPolicyObservationsToDataset(dataset, 'Synthetic', [observation], [evaluation])!;
const aggregate = merged.sheets[0];
const carried = aggregate.analysis.records[0] as FindingRecord & Record<string, unknown>;
const metadataKeys = Object.keys(original).filter(key => !LAYOUT_KEYS.has(key));
expect(metadataKeys).toContain('syntheticFutureMetadata');
for (const key of metadataKeys) expect(carried[key], key).toBe((original as unknown as Record<string, unknown>)[key]);
// Layout properties come from the rebuilt sheet: the GPO columns extend the scanner row.
const rebuilt = makeSheet(aggregate.name, aggregate.rows, '', 'Synthetic', { id: aggregate.id, isProjectSheet: true }).analysis.records[0];
expect(carried.id).toBe(rebuilt.id);
expect(carried.fields.map(field => field.label)).toEqual(aggregate.rows[0]);
expect(carried.fields.map(field => field.label)).toContain('Configured Value');
expect(carried.fields.find(field => field.label === 'Status')?.value).toBe('Fail');
expect(carried).not.toBe(original);
});
});