Assessor_2.0/tests/comparison/comparison-work-facet.test.ts

62 lines
4.2 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import type { FindingRecord } from '../../src/domain/finding';
import type { DynamicFacet } from '../../src/features/search-filter/dynamic-facets';
import type { TimeslotComparisonReport } from '../../src/comparison/timeslot-comparison-report';
import {
changesWithWorkRecords,
comparisonWorkFacetIndex,
COMPARISON_CHANGE_FACET_ID,
withComparisonFacets
} from '../../src/features/comparison/comparison-work-facets';
import type { PublishedComparison } from '../../src/features/comparison/use-timeslot-comparison';
// The Work `Change` facet maps the comparison onto Work records of timeslot B. Synthetic records only.
const gpoRecordId = (identity: string) =>
`gpo-work-setting:${encodeURIComponent(JSON.stringify(['dummy-project', 'aggregation-b', 'computer', identity]))}`;
const record = (id: string, findingOccurrenceId?: string) => ({ id, fields: [], ...(findingOccurrenceId ? { findingOccurrenceId } : {}) }) as unknown as FindingRecord;
// A rolled-up GPO setting whose Work record ID does not carry the setting identity: matched by its Raw Data fields.
const gpoFieldRecord = (scope: string, identity: string) => ({ id: 'record-synthetic', fields: [
{ label: 'Category', value: 'GPOs' }, { label: 'Scope', value: scope }, { label: 'Technical Identity', value: identity }] }) as unknown as FindingRecord;
const report = {
changes: [
{ sourceId: 'cis-cat', change: 'resolved', side: 'baseline', recordIds: ['a-1'], counterpartIds: ['b-1'], reasons: [] },
{ sourceId: 'cis-cat', change: 'unverified', side: 'baseline', recordIds: ['a-2'], counterpartIds: [], reasons: [] },
{ sourceId: 'cis-cat', change: 'regressed', side: 'target', recordIds: ['b-3'], counterpartIds: [], reasons: [] },
{ sourceId: 'group-policy', change: 'value-changed', side: 'baseline', recordIds: ['ga-1'], counterpartIds: ['gb-1'],
identityKey: JSON.stringify(['computer', 'MinimumPasswordLength']), reasons: [] },
{ sourceId: 'group-policy', change: 'new', side: 'target', recordIds: ['gb-2'], counterpartIds: [],
identityKey: JSON.stringify(['computer', 'SeEnableDelegationPrivilege']), reasons: [] }
]
} as unknown as TimeslotComparisonReport;
const timeslot = (id: string, date: string) => ({ id, date, assessmentIds: [id], scanRunIds: [] });
const published: PublishedComparison = {
projectId: 'dummy-project', targetAssessmentIds: ['b'], baseline: timeslot('a', '2026-01-10'), target: timeslot('b', '2026-02-10'), report
};
describe('Comparison Engine Work Change facet', () => {
it('assigns each timeslot B record its change and leaves unrelated records without one', () => {
const index = comparisonWorkFacetIndex(published);
expect(index.changeOf(record('work-b-1', 'b-1'))).toBe('resolved');
expect(index.changeOf(record('work-b-3', 'b-3'))).toBe('regressed');
expect(index.changeOf(record(gpoRecordId('MinimumPasswordLength')))).toBe('value-changed');
expect(index.changeOf(record(gpoRecordId('SeEnableDelegationPrivilege')))).toBe('new');
expect(index.changeOf(gpoFieldRecord('computer', 'MinimumPasswordLength'))).toBe('value-changed');
expect(index.changeOf(gpoFieldRecord('user', 'MinimumPasswordLength'))).toBeUndefined();
expect(index.changeOf(record('work-other', 'b-9'))).toBeUndefined();
expect(index.facets[0].valuesFor?.(record('work-b-1', 'b-1'))).toEqual(['resolved']);
expect(index.facets[0]).toMatchObject({ id: COMPARISON_CHANGE_FACET_ID, label: 'Change' });
});
it('offers only changes with a Work record in timeslot B', () => {
expect([...changesWithWorkRecords(report)].sort()).toEqual(['new', 'regressed', 'resolved', 'value-changed']);
});
it('places the facet directly before the permanent final Source: Scan control', () => {
const facet = (id: string) => ({ id, label: id, source: 'derived', kind: 'category', values: [] }) as unknown as DynamicFacet;
const facets = withComparisonFacets([facet('status'), facet('provenance:source-scan')], comparisonWorkFacetIndex(published));
expect(facets.map(item => item.id)).toEqual(['status', COMPARISON_CHANGE_FACET_ID, 'provenance:source-scan']);
expect(withComparisonFacets([facet('status')], undefined).map(item => item.id)).toEqual(['status']);
});
});