72 lines
5.0 KiB
TypeScript
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);
|
|
});
|
|
});
|