Assessor_2.0/tests/features/client-info-facts.test.tsx

64 lines
4.5 KiB
TypeScript

// @vitest-environment jsdom
import { act } from 'react';
import { createRoot } from 'react-dom/client';
import { afterEach, expect, it, vi } from 'vitest';
import { useApp } from '../../src/app/AppProvider';
import { AssessorApiClient } from '../../src/api/assessor-api-client';
import { DashboardPanel } from '../../src/features/dashboard/DashboardPanel';
import { clientFactsSnapshot, clientFactsState } from '../fixtures/client-info-facts';
vi.mock('../../src/app/AppProvider', () => ({ useApp: vi.fn() }));
vi.mock('../../src/features/search-filter/WorkTimeslotControl', () => ({ WorkTimeslotControl: () => <span>Synthetic timeslot</span> }));
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); document.body.replaceChildren(); });
it.each(['populated', 'unavailable', 'non-numeric count'])('shows each overview fact once in the existing tiles and preserves Results: %s', async mode => {
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
const snapshot = structuredClone(clientFactsSnapshot);
if (mode === 'unavailable') { snapshot.observations = []; snapshot.sections = []; }
if (mode === 'non-numeric count') {
const count = snapshot.observations.find(item => item.property === 'domain.controller_count')!;
count.originalValue = count.normalizedValue = 'Two reported controllers'; count.valueType = 'string';
}
vi.spyOn(AssessorApiClient.prototype, 'getEnvironmentSnapshot').mockResolvedValue(snapshot);
vi.spyOn(AssessorApiClient.prototype, 'listProjectGpoBackupStatus').mockResolvedValue({ records: [] } as never);
vi.mocked(useApp).mockReturnValue(clientFactsState() as unknown as ReturnType<typeof useApp>);
const host = document.createElement('div'); document.body.append(host); const root = createRoot(host);
try {
await act(async () => root.render(<DashboardPanel onOpenWork={vi.fn()}/>));
const domain = host.querySelector('.client-domain-information')!;
const groups = [...host.querySelectorAll('.client-env-group')];
const directory = groups.find(group => group.querySelector('h3')?.textContent === 'Directory')!;
const inventory = groups.find(group => group.querySelector('h3')?.textContent === 'Inventory')!;
const labels = (element: Element) => [...element.querySelectorAll('dt')].map(item => item.textContent);
expect(host.querySelector('.client-posture')).not.toBeNull();
expect(host.querySelector('.client-posture-connectors')).not.toBeNull();
expect(host.querySelector('.client-priority')).not.toBeNull();
expect(host.querySelectorAll('.client-data-fact')).toHaveLength(6);
// experimental keeps the Source metrics tile (2026-09-28 integration).
expect(groups).toHaveLength(6);
expect(labels(directory)).toEqual(['NetBIOS name', 'Schema version', 'Domain created', 'Entra ID']);
expect(labels(domain)).toEqual(mode === 'non-numeric count'
? ['Domain', 'Forest', 'Domain controllers', 'Domain functional level', 'Forest functional level', 'DC OS']
: ['Domain', 'Forest', 'Domain functional level', 'Forest functional level', 'DC OS']);
expect(labels(inventory)).toEqual(['Enabled / disabled', 'Password never expires', 'Admin groups', 'Trusts', 'Assessed host']);
// Equal domain/forest and functional-level values are distinct facts, not duplicates.
if (mode !== 'unavailable') {
expect([...domain.querySelectorAll('dd')].filter(item => item.textContent === 'contoso.example')).toHaveLength(2);
expect([...domain.querySelectorAll('dd')].filter(item => item.textContent === 'Windows Server 2016')).toHaveLength(2);
} else expect([...domain.querySelectorAll('dd')].every(item => item.textContent === '—')).toBe(true);
for (const [title, group, expected] of [
['Directory', directory, ['Domain', 'Forest', 'Domains', 'Domain functional level', 'Schema version']],
['Inventory', inventory, ['User accounts', 'Computer objects', 'Domain controllers', 'Assessed host']]
] as const) {
const trigger = group.querySelector<HTMLButtonElement>('button')!;
expect(trigger.getAttribute('aria-label')).toBe(`Open ${title} results`);
await act(async () => trigger.click());
const popup = document.querySelector('.floating-data-window')!;
for (const label of expected) expect(labels(popup)).toContain(label);
if (mode !== 'unavailable') expect(popup.querySelector('table')).not.toBeNull();
await act(async () => document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true })));
expect(document.querySelector('.floating-data-window')).toBeNull();
}
} finally { await act(async () => root.unmount()); }
});