155 lines
9.4 KiB
TypeScript
155 lines
9.4 KiB
TypeScript
import { createElement } from 'react';
|
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
import { describe, expect, it } from 'vitest';
|
|
import type { UnifiedResultProjection } from '../../src/domain/unified-result';
|
|
import { appendPolicyObservationsToDataset } from '../../src/features/project-import/policy-observations-to-dataset';
|
|
import { inlineResultTableSheets } from '../../src/features/linked-results/inline-result-tables';
|
|
import { buildPolicyConflictPresentation, policyConflictResultSheets, POLICY_CONFLICT_DETAILS } from '../../src/profiles/group-policy/conflict-presentation';
|
|
import { buildPolicyWorkResults } from '../../src/evaluation/policy-work-results';
|
|
import { LinkedResults } from '../../src/features/linked-results/LinkedResults';
|
|
import { FindingReader, selectFindingFields } from '../../src/features/finding-reader/FindingReader';
|
|
import { showResultSheetTitlesFor } from '../../src/features/linked-results/result-message';
|
|
|
|
function source(id: string, value: string, typeCode = 1): UnifiedResultProjection {
|
|
return { sourceKind: 'policy-observation', sourceResultId: id, projectId: 'synthetic', assessmentId: 'slot', title: 'Mode',
|
|
importedAt: '2026-09-06T00:00:00Z', policyScope: 'user', policyDeliveryMechanism: 'registry-policy',
|
|
policyIdentityStatus: 'resolved', policyIdentityKey: 'mode', policyBackupInstanceId: `backup-${id}`,
|
|
sourceLocator: { objectPath: `C:/Private/GPO_Backup/${id}/{1667E9C3-865B-4ABA-A288-71C8A18AD5E0}/DomainSysvol/GPO/User/registry.pol#records=${id}` },
|
|
policyValues: [{ kind: 'registry-policy', key: 'Software\\Policies\\Synthetic', valueName: 'Mode', operation: 'set-value',
|
|
typeCode, data: { kind: 'string', value } }] } as unknown as UnifiedResultProjection;
|
|
}
|
|
function fixture(sources = [source('a', 'Alpha'), source('b', 'Beta'), source('c', 'Beta')]) {
|
|
const dataset = appendPolicyObservationsToDataset(null, 'Synthetic', sources)!;
|
|
const record = dataset.sheets[0].analysis.records[0];
|
|
return { record, sheets: inlineResultTableSheets(record) };
|
|
}
|
|
|
|
describe('GPO conflict Results', () => {
|
|
it('groups identical values and maps every source to its variant without choosing a winner', () => {
|
|
const { record, sheets } = fixture();
|
|
expect(sheets).toHaveLength(2);
|
|
expect(sheets[0].rows).toEqual([
|
|
['Variant', 'Value', 'Type', 'Sources'],
|
|
['Variant 1', 'Alpha', 'REG_SZ (1)', '1'],
|
|
['Variant 2', 'Beta', 'REG_SZ (1)', '2'],
|
|
]);
|
|
expect(sheets[1].analysis.records).toHaveLength(3);
|
|
expect(sheets[0].resultEmphasisColumns).toEqual(['Value']);
|
|
expect(record.fields.find(field => field.label === 'Conflict')?.value).toBe('2 variants · 3 sources. Values differ.');
|
|
expect(inlineResultTableSheets(record)).toBe(sheets);
|
|
expect(showResultSheetTitlesFor('group-policy')).toBe(true);
|
|
});
|
|
|
|
it('emphasizes type differences when displayed values are equal', () => {
|
|
const { sheets } = fixture([source('a', '1', 1), source('b', '1', 2)]);
|
|
expect(sheets[0].resultEmphasisColumns).toEqual(['Type']);
|
|
expect(sheets[0].rows[1][1]).toBe(sheets[0].rows[2][1]);
|
|
expect(sheets[0].rows[1][2]).not.toBe(sheets[0].rows[2][2]);
|
|
});
|
|
|
|
it('keeps the report concise and enables the existing Results button', () => {
|
|
const { record, sheets } = fixture();
|
|
const fields = selectFindingFields(record, record.title, true, 'group-policy');
|
|
expect(fields.policyComparisonFields).toEqual([]);
|
|
expect(fields.summary.some(field => field.label === 'Conflict')).toBe(true);
|
|
expect([...fields.summary, ...fields.remainingFields].map(field => field.label)).not.toEqual(expect.arrayContaining(['Sources']));
|
|
expect([...fields.summary, ...fields.remainingFields].some(field => field.label === POLICY_CONFLICT_DETAILS)).toBe(false);
|
|
const html = renderToStaticMarkup(createElement(FindingReader, { record, title: record.title, linkedSheets: sheets, resultReferences: [], search: '',
|
|
position: 0, recordCount: 1, toolId: 'group-policy', onPrevious() {}, onNext() {}, onReview() {}, onResults() {} }));
|
|
expect(html).toContain('2 variants');
|
|
expect(html).toMatch(/<button[^>]*class="[^"]*results-detail-btn[^>]*>Results<\/button>/);
|
|
expect(html.match(/<button[^>]*results-detail-btn[^>]*>/)?.[0]).not.toContain('disabled');
|
|
expect(html).not.toContain('Configured customer value');
|
|
expect(html).toContain('backup-a');
|
|
expect(html).toContain('3 GPO backup sources');
|
|
});
|
|
|
|
it('renders an explanatory inert comparison without red/green winner claims', () => {
|
|
const { sheets } = fixture([source('a', '<script>alert(1)</script>'), source('b', '[link](https://example.invalid)')]);
|
|
const html = renderToStaticMarkup(<LinkedResults sheets={sheets} resizableTables/>);
|
|
expect(html).toContain('Conflict detected');
|
|
expect(html).toContain('ASSESSOR shows the conflicting evidence but does not determine which value ultimately applies.');
|
|
expect(html).toContain('Variant A');
|
|
expect(html).toContain('Variant B');
|
|
expect(html).toContain('Technical source details');
|
|
expect(html).toContain('Observed variants');
|
|
expect(html).toContain('<table class="variant-comparison__table">');
|
|
expect(html).toContain('<script>');
|
|
expect(html).not.toContain('<script>');
|
|
expect(html).not.toContain('href="https://example.invalid');
|
|
expect(html).not.toContain('row--violation');
|
|
});
|
|
|
|
it('names empty and whitespace-only values explicitly and keeps exact evidence', () => {
|
|
const { sheets } = fixture([source('a', ''), source('b', ' '), source('c', 'Beta')]);
|
|
const html = renderToStaticMarkup(<LinkedResults sheets={sheets} />);
|
|
expect(html).toContain('""');
|
|
expect(html).toContain('Empty string');
|
|
expect(html).toContain('" "');
|
|
expect(html).toContain('1 whitespace character');
|
|
expect(sheets[0].rows[1][1]).toBe('');
|
|
expect(sheets[0].rows[2][1]).toBe(' ');
|
|
});
|
|
|
|
it('promotes readable setting, type and source labels while retaining technical IDs', () => {
|
|
const { sheets } = fixture();
|
|
const html = renderToStaticMarkup(<LinkedResults sheets={sheets} heading="Repeated technical title" />);
|
|
expect(html).toContain('<h3>Mode</h3>');
|
|
expect(html).not.toContain('Repeated technical title');
|
|
expect(html).toContain('String (REG_SZ)');
|
|
expect(html).toContain('{1667E9C3-865B-4ABA-A288-71C8A18AD5E0}/DomainSysvol/GPO/User/registry.pol#records=a');
|
|
expect(html).not.toMatch(/C:|Private|GPO_Backup/);
|
|
expect(html).not.toContain('Show source');
|
|
expect(html).toMatch(/1 source<\/span><div class="variant-comparison__sources"[^>]*><code class="technical-path">/);
|
|
expect(html).toContain('backup-a');
|
|
expect(html).toContain('Record ID');
|
|
});
|
|
|
|
it('follows conflict, setting, variants and technical-details reading order', () => {
|
|
const { sheets } = fixture();
|
|
const html = renderToStaticMarkup(<LinkedResults sheets={sheets} />);
|
|
const conflict = html.indexOf('Conflict detected');
|
|
const setting = html.indexOf('Setting details');
|
|
const variants = html.indexOf('Observed variants');
|
|
const technical = html.indexOf('Technical source details');
|
|
expect(conflict).toBeGreaterThan(-1);
|
|
expect(setting).toBeGreaterThan(conflict);
|
|
expect(variants).toBeGreaterThan(setting);
|
|
expect(technical).toBeGreaterThan(variants);
|
|
});
|
|
|
|
it('preserves source values and provenance including whitespace in the projected table', () => {
|
|
const sources = [source('a', 'A B'), source('b', 'B')];
|
|
const before = JSON.stringify(sources);
|
|
const { sheets } = fixture(sources);
|
|
expect(sheets[0].analysis.records[0].fields.find(field => field.label === 'Value')?.value).toBe('A B');
|
|
expect(sheets[1].rows[1]).toContain('{1667E9C3-865B-4ABA-A288-71C8A18AD5E0}/DomainSysvol/GPO/User/registry.pol#records=a');
|
|
expect(sheets[1].rows[1]).toContain('backup-a');
|
|
expect(JSON.stringify(sources)).toBe(before);
|
|
});
|
|
|
|
it('retains all 2300 sources while displaying just two value variants', () => {
|
|
const { sheets } = fixture(Array.from({ length: 2300 }, (_, i) => source(String(i), i % 2 ? 'Alpha' : 'Beta')));
|
|
expect(sheets[0].analysis.records).toHaveLength(2);
|
|
expect(sheets[1].analysis.records).toHaveLength(2300);
|
|
expect(sheets[1].rows.at(-1)?.[0]).toBe('Source 2300');
|
|
});
|
|
|
|
it('falls back to existing evidence if the details envelope is malformed or oversized', () => {
|
|
for (const value of ['{', JSON.stringify({ version: 2, tables: [] }), 'x'.repeat(10 * 1024 * 1024 + 1), JSON.stringify({ version: 1, tables: [{ rows: null }, {}] })]) {
|
|
const { record } = fixture();
|
|
const changed = { ...record, fields: record.fields.map(field => field.label === POLICY_CONFLICT_DETAILS ? { ...field, value } : field) };
|
|
expect(policyConflictResultSheets(changed)).toEqual([]);
|
|
expect(selectFindingFields(changed, changed.title, false, 'group-policy').policyComparisonFields).toHaveLength(0);
|
|
expect(changed.policySourceEvidence).toHaveLength(3);
|
|
expect(selectFindingFields({ ...changed, policySourceEvidence: undefined }, changed.title, false, 'group-policy').policyComparisonFields).toHaveLength(1);
|
|
}
|
|
});
|
|
|
|
it('does not invent a customer conflict table for duplicates or missing conflict sources', () => {
|
|
const duplicates = buildPolicyWorkResults([source('a', 'same'), source('b', 'same')], [])[0];
|
|
expect(buildPolicyConflictPresentation(duplicates)).toBeUndefined();
|
|
expect(buildPolicyConflictPresentation({ ...duplicates, status: 'Conflicts', sources: [] })).toBeUndefined();
|
|
});
|
|
});
|