Assessor_2.0/tests/features/gpo-source-navigation.test.tsx

176 lines
10 KiB
TypeScript

// @vitest-environment jsdom
import { act, useState } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { AppProvider, useApp } from '../../src/app/AppProvider';
import { AssessorApiClient } from '../../src/api/assessor-api-client';
import { loadCanonicalProjectWorkspace } from '../../src/features/project-import/load-canonical-project-workspace';
import { DashboardPanel } from '../../src/features/dashboard/DashboardPanel';
import { SearchFilterBar, SheetAndSearchControls } from '../../src/features/search-filter/SearchFilterBar';
import { useFilteredRecords } from '../../src/features/search-filter/use-filtered-records';
import { makeSheet } from '../../src/importers/sheet-factory';
import type { ImportedDataset } from '../../src/domain/dataset';
import type { CanonicalAssessmentModel } from '../../src/domain/canonical-model';
vi.mock('../../src/features/project-import/load-canonical-project-workspace', () => ({ loadCanonicalProjectWorkspace: vi.fn() }));
vi.mock('../../src/features/search-filter/WorkTimeslotControl', () => ({ WorkTimeslotControl: () => <span>Synthetic timeslot</span> }));
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); localStorage.clear(); });
function fixture() {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
vi.spyOn(AssessorApiClient.prototype, 'health').mockResolvedValue({ status: 'ok' } as never);
vi.spyOn(AssessorApiClient.prototype, 'getEnvironmentSnapshot').mockRejectedValue(new Error('Synthetic context unavailable'));
vi.spyOn(AssessorApiClient.prototype, 'listProjectGpoBackupStatus').mockResolvedValue({ records: [] } as never);
const rows = [['Title', 'Category', 'Status', 'Source: Scan', 'Diagnostics'],
['Synthetic review one', 'GPOs', 'Review required', 'GPOs', 'policy-evaluation-baseline-incomplete'],
['Synthetic review two', 'GPOs', 'Review required', 'GPOs', 'policy-evaluation-customer-absence-unproven'],
['Synthetic duplicate', 'GPOs', 'Duplicates', 'GPOs', '']];
const gpo = makeSheet('GPOs', rows, '', 'Synthetic', { toolId: 'group-policy' });
const aggregate = makeSheet('All Records', [...rows, ['Synthetic scanner', 'Scanner', 'Fail', 'Maester', '']], '', 'Synthetic', { isProjectSheet: true });
const dataset = { fileName: 'Synthetic', profileId: 'generic', scanTool: 'Multiple Tools', fileSize: 0,
lastModified: 0, format: 'Project', rawText: '', sheets: [aggregate, gpo] } as ImportedDataset;
vi.mocked(loadCanonicalProjectWorkspace).mockResolvedValue({ dataset, assessmentIds: ['slot'], models: [{
project: { id: 'synthetic', name: 'Synthetic' }, assessment: { id: 'slot' }, reviews: []
} as unknown as CanonicalAssessmentModel] });
return { dataset, gpo };
}
function Harness() {
const app = useApp();
currentApp = app;
const [area, setArea] = useState('dashboard');
const records = useFilteredRecords();
return <>
<button onClick={() => void app.openProject('synthetic', ['run'], ['slot'])}>Open synthetic client</button>
<button onClick={() => setArea('work')}>Work</button>
<button onClick={() => {
app.setSearch('no matching source row');
app.setStatusFilter('passed');
app.setFacetFilter('category', { values: ['GPOs'] });
app.setPathFindingIds(['stale-record']);
app.setActionOnly(true);
}}>Apply stale Work filters</button>
<output data-testid="count">{records.length}</output>
{area === 'dashboard' ? <DashboardPanel onOpenWork={() => setArea('work')}/>
: <><SheetAndSearchControls/><SearchFilterBar/><ul data-testid="records">{records.map(record => <li key={record.id}>{record.title}</li>)}</ul></>}
</>;
}
let currentApp: ReturnType<typeof useApp>;
describe('GPO source navigation with the real application state provider', () => {
it('resets stale Work filters when another Source: Scan is selected and shows its default results', async () => {
fixture();
const container = document.createElement('div');
const root = createRoot(container);
const click = (label: string) => act(async () => {
[...container.querySelectorAll('button')].find(button => button.textContent === label)!.click();
});
const selectSource = (value: string) => act(async () => {
const control = container.querySelector<HTMLSelectElement>('select[aria-label="Source: Scan"]')!;
control.value = value;
control.dispatchEvent(new Event('change', { bubbles: true }));
});
try {
await act(async () => root.render(<AppProvider><Harness/></AppProvider>));
await click('Open synthetic client');
await click('Work');
await selectSource('gpos');
await click('Apply stale Work filters');
expect(container.querySelector('[data-testid="count"]')?.textContent).toBe('0');
const source = container.querySelector<HTMLSelectElement>('select[aria-label="Source: Scan"]')!;
const scannerOption = [...source.options].find(option => option.textContent?.startsWith('Maester'))!;
await selectSource(scannerOption.value);
expect(currentApp.search).toBe('');
expect(currentApp.statusFilter).toBe('deviation');
expect(currentApp.actionOnly).toBe(false);
expect(currentApp.pathFindingIds).toEqual([]);
expect(currentApp.facetFilters).toEqual({ 'provenance:source-scan': { values: [scannerOption.value] } });
expect(container.querySelector('[data-testid="count"]')?.textContent).toBe('1');
expect(container.querySelector('[data-testid="records"]')?.textContent).toContain('Synthetic scanner');
await click('Apply stale Work filters');
await selectSource('');
expect(currentApp.search).toBe('');
expect(currentApp.statusFilter).toBe('deviation');
expect(currentApp.actionOnly).toBe(false);
expect(currentApp.pathFindingIds).toEqual([]);
expect(currentApp.facetFilters).toEqual({});
expect(container.querySelector('[data-testid="records"]')?.textContent).toContain('Synthetic scanner');
} finally { await act(async () => root.unmount()); container.remove(); }
});
it('shows the full GPO filters only while Source: Scan is set to GPOs', async () => {
fixture();
const container = document.createElement('div');
const root = createRoot(container);
const labels = () => [...container.querySelectorAll<HTMLSelectElement>('.dynamic-facet-select')]
.map(control => control.getAttribute('aria-label'));
const selectSource = (value: string) => act(async () => {
const control = container.querySelector<HTMLSelectElement>('select[aria-label="Source: Scan"]')!;
control.value = value;
control.dispatchEvent(new Event('change', { bubbles: true }));
});
try {
await act(async () => root.render(<AppProvider><Harness/></AppProvider>));
await act(async () => [...container.querySelectorAll('button')]
.find(button => button.textContent === 'Open synthetic client')!.click());
await act(async () => [...container.querySelectorAll('button')]
.find(button => button.textContent === 'Work')!.click());
await selectSource('gpos');
expect(labels()).toContain('Diagnostics');
expect(labels()).toContain('Category');
expect(labels()).toContain('Security Domain');
await selectSource('');
expect(container.querySelector<HTMLSelectElement>('select[aria-label="Source: Scan"]')?.value).toBe('');
expect(labels()).toContain('Category');
expect(labels()).not.toContain('Diagnostics');
} finally { await act(async () => root.unmount()); container.remove(); }
});
it.each(['dashboard source then Work', 'Work source', 'GPO sheet', 'GPO review domain'] as const)('uses Deviation by default through %s while preserving explicit incoming drilldowns', async route => {
const { gpo } = fixture();
const container = document.createElement('div');
const root = createRoot(container);
const click = (label: string) => act(async () => {
[...container.querySelectorAll('button')].find(button => button.textContent === label)!.click();
});
const select = (selector: string, value: string) => act(async () => {
const control = container.querySelector<HTMLSelectElement>(selector)!;
control.value = value;
control.dispatchEvent(new Event('change', { bubbles: true }));
});
try {
await act(async () => root.render(<AppProvider><Harness/></AppProvider>));
await click('Open synthetic client');
if (route === 'dashboard source then Work' || route === 'GPO review domain') {
await select('.client-overview-source select', 'gpos');
expect(container.querySelector('.client-domains')?.textContent).toContain('System Hardening2');
if (route === 'GPO review domain') await act(async () => container.querySelector<HTMLButtonElement>('.client-domains button')!.click());
else await click('Work');
} else {
await click('Work');
if (route === 'Work source') await select('select[aria-label="Source: Scan"]', 'gpos');
else {
const sheet = container.querySelector<HTMLSelectElement>('.sheet-select')!;
await act(async () => sheet.dispatchEvent(new Event('focusin', { bubbles: true })));
await select('.sheet-select', gpo.id);
}
}
const explicitReviewDrilldown = route === 'GPO review domain';
expect(container.querySelector<HTMLSelectElement>('select[aria-label="Result Status"]')?.value)
.toBe(explicitReviewDrilldown ? 'all' : 'deviation');
expect(container.querySelector('[data-testid="count"]')?.textContent).toBe(explicitReviewDrilldown ? '2' : '0');
if (!explicitReviewDrilldown) await select('select[aria-label="Result Status"]', 'all');
expect(container.querySelector('[data-testid="records"]')?.textContent).toContain('Synthetic review one');
expect(container.querySelector('[data-testid="records"]')?.textContent).not.toContain('Synthetic scanner');
await select('select[aria-label="Result Status"]', 'passed');
expect(container.querySelector<HTMLSelectElement>('select[aria-label="Result Status"]')?.value).toBe('passed');
expect(container.querySelector('[data-testid="count"]')?.textContent).toBe('0');
await select('select[aria-label="Result Status"]', 'all');
expect(container.querySelector('[data-testid="count"]')?.textContent).toBe(explicitReviewDrilldown ? '2' : '3');
} finally { await act(async () => root.unmount()); container.remove(); }
});
});