62 lines
4.2 KiB
TypeScript
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']);
|
|
});
|
|
});
|