Assessor_2.0/tests/compliance/client-compliance-preload.t...

91 lines
5.5 KiB
TypeScript

// @vitest-environment jsdom
import { act } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, beforeEach, expect, it, vi } from 'vitest';
import { AssessorApiClient } from '../../src/api/assessor-api-client';
import { useApp } from '../../src/app/AppProvider';
import { resetEnginePreferencesForTests, setEngineEnabled } from '../../src/app/engine-preferences';
import type { RegulatoryControlOverviewResponse } from '../../src/compliance/regulatory-mapping';
import { resetClientComplianceCacheForTests } from '../../src/compliance/ui/ClientIsoControls';
import type { ImportedDataset } from '../../src/domain/dataset';
import { DashboardPanel } from '../../src/features/dashboard/DashboardPanel';
import { resetAssessmentContextRequestsForTests } from '../../src/features/context-layer/use-assessment-context';
import { makeSheet } from '../../src/importers/sheet-factory';
import { genericProfile } from '../../src/profiles/generic/generic-profile';
import { emptyHeatmapContext } from '../assessment-context/context-heatmap-fixture';
vi.mock('../../src/app/AppProvider', () => ({ useApp: vi.fn() }));
vi.mock('../../src/features/search-filter/WorkTimeslotControl', () => ({ WorkTimeslotControl: () => <span>Synthetic assessment</span> }));
const overview: RegulatoryControlOverviewResponse = {
version: 1, projectId: 'synthetic-client', scope: { assessmentIds: ['synthetic-timeslot'], scanRunIds: ['synthetic-run'] }, findingCount: 1,
controls: [{ controlId: '8.20', annexAControlId: 'A.8.20', title: 'Networks security', theme: 'Technological', findings: [
{ resultProjectionId: 'scanner-finding:synthetic', sourceKind: 'scanner-finding', sourceResultId: 'synthetic' }] }]
};
beforeEach(() => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
vi.stubGlobal('ResizeObserver', class { observe() {} disconnect() {} });
localStorage.clear();
resetEnginePreferencesForTests();
resetAssessmentContextRequestsForTests();
resetClientComplianceCacheForTests();
const sheet = makeSheet('Synthetic', [['Name', 'Status'], ['One', 'Failed']], '', 'synthetic.csv', { isProjectSheet: true });
const dataset = { fileName: 'Synthetic client', sheets: [sheet], scanTool: 'Generic' } as ImportedDataset;
vi.mocked(useApp).mockReturnValue({ dataset, activeSheet: sheet, activeProfile: genericProfile, reviews: {}, backendStatus: 'connected',
targetProjectId: 'synthetic-client', activeAssessmentIds: ['synthetic-timeslot'], activeScanRunIds: ['synthetic-run'], facetFilters: {}, search: '',
statusFilter: 'all', actionOnly: false, setStatusFilter: vi.fn(), setFacetFilter: vi.fn(), setPathFindingIds: vi.fn(), resetFilters: vi.fn(),
setView: vi.fn(), setActionOnly: vi.fn(), setActiveSheet: vi.fn()
} as unknown as ReturnType<typeof useApp>);
vi.spyOn(AssessorApiClient.prototype, 'getEnvironmentSnapshot').mockResolvedValue({ observations: [], sections: [] } as never);
vi.spyOn(AssessorApiClient.prototype, 'listProjectGpoBackupStatus').mockResolvedValue({ records: [] } as never);
vi.spyOn(AssessorApiClient.prototype, 'getAssessmentContext').mockResolvedValue(emptyHeatmapContext());
vi.spyOn(AssessorApiClient.prototype, 'getAiOverview').mockResolvedValue({ version: 1, projectId: 'synthetic-client',
scope: { assessmentIds: [], scanRunIds: [] }, generationEnabled: false, state: 'empty', stale: false });
});
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); resetEnginePreferencesForTests(); });
async function renderClientInfo() {
const host = document.createElement('div');
document.body.append(host);
const root = createRoot(host);
await act(async () => root.render(<DashboardPanel onOpenWork={vi.fn()}/>));
await act(async () => { await new Promise(resolve => setTimeout(resolve, 0)); });
return { host, unmount: async () => { await act(async () => root.unmount()); host.remove(); } };
}
it('loads the Compliance overview with Client Info, so the first switch to the tab shows it at once', async () => {
let resolveFresh!: (value: RegulatoryControlOverviewResponse) => void;
const request = vi.spyOn(AssessorApiClient.prototype, 'getRegulatoryControlOverview')
.mockResolvedValueOnce(overview)
.mockReturnValueOnce(new Promise(resolve => { resolveFresh = resolve; }));
const { host, unmount } = await renderClientInfo();
try {
expect(host.querySelector('.client-compliance')).toBeNull();
expect(request).toHaveBeenCalledTimes(1);
expect(request).toHaveBeenCalledWith('synthetic-client', { assessmentIds: ['synthetic-timeslot'], scanRunIds: ['synthetic-run'] });
const tab = [...host.querySelectorAll<HTMLButtonElement>('button')].find(button => button.textContent === 'Compliance')!;
await act(async () => tab.click());
// The preloaded values show while the tab's own fresh request is still pending.
const findings = [...host.querySelectorAll('[data-framework="iso-27001-27002"] .client-env-facts > div')]
.find(fact => fact.querySelector('dt')?.textContent === 'Findings')?.querySelector('dd')?.textContent;
expect(findings).toBe('1');
expect(request).toHaveBeenCalledTimes(2);
await act(async () => { resolveFresh(overview); await Promise.resolve(); });
} finally {
await unmount();
}
});
it('preloads nothing while the Compliance Engine is off', async () => {
setEngineEnabled('compliance', false);
const request = vi.spyOn(AssessorApiClient.prototype, 'getRegulatoryControlOverview').mockResolvedValue(overview);
const { unmount } = await renderClientInfo();
try {
expect(request).not.toHaveBeenCalled();
} finally {
await unmount();
}
});