// @vitest-environment jsdom import { act, createElement } from 'react'; import { createRoot } from 'react-dom/client'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it, vi } from 'vitest'; import type { EnvironmentObservation, EnvironmentSnapshot } from '../../src/domain/environment-context'; import { operatingSystemGroup, operatingSystemInventory } from '../../src/features/dashboard/operating-system-inventory'; import { OperatingSystemInventory } from '../../src/features/dashboard/OperatingSystemInventory'; function os(name: string, count: unknown, row: number, run = 'run'): EnvironmentObservation[] { return [['operating_system.name', name], ['operating_system.count', count]].map(([property, raw], column) => ({ version: 1, id: `${run}:${row}:${column}`, scanRunId: run, importFileId: `${run}:file`, tool: { id: 'pingcastle', name: 'PingCastle' }, subject: { type: 'computer-information.operating-systems', identity: name }, property: String(property), label: column ? 'Nb OS' : 'Operating system', originalValue: raw, normalizedValue: raw, valueType: 'string', state: 'observed', sectionKey: 'sectionComputerInformation', source: { rowIndex: row, columnIndex: column }, attributes: { tableName: 'Operating systems', sourceTableIndex: 0 }, mappingVersion: '1' })) as EnvironmentObservation[]; } function osStates(row: number, values: Record<'Active' | 'Inactive' | 'Enabled' | 'Disabled', unknown>, run = 'run'): EnvironmentObservation[] { return Object.entries(values).map(([label, raw], column) => ({ version: 1, id: `${run}:${row}:state:${column}`, scanRunId: run, importFileId: `${run}:file`, tool: { id: 'pingcastle', name: 'PingCastle' }, subject: { type: 'computer-information.operating-systems', identity: `row-${row}` }, property: `pingcastle.sectioncomputerinformation.operating.system.list.nb.${label.toLowerCase()}`, label: `Nb ${label}`, originalValue: raw, normalizedValue: raw, valueType: 'string', state: 'observed', sectionKey: 'sectionComputerInformation', source: { rowIndex: row, columnIndex: column + 2 }, attributes: { tableName: 'Operating systems', sourceTableIndex: 0 }, mappingVersion: '1' })) as EnvironmentObservation[]; } function snapshot(observations: EnvironmentObservation[]): EnvironmentSnapshot { return { version: 1, projectId: 'synthetic', scanRunIds: ['run'], generatedAt: '', sections: [], observations, coverage: { observed: observations.length, explicitlyAbsent: 0, empty: 0, restricted: 0, incomplete: 0 } }; } describe('operating system inventory', () => { it('splits server/client products, retains other systems and pairs exact native counts including zero', () => { const source = snapshot([...os('Windows Server 2022', 12, 1), ...os('Windows 11 24H2', '1,200', 2), ...os('Windows 10 22H2', 0, 3), ...os('Debian', 4, 4), ...os('OperatingSystem not set', 3, 5)]); const before = JSON.stringify(source); const groups = operatingSystemInventory(source); expect(groups.map(group => [group.label, group.total])).toEqual([['Servers', 12], ['Clients', 1200], ['Other / unclassified', 7]]); expect(groups[1].entries.map(entry => entry.count)).toEqual([1200, 0]); expect(renderToStaticMarkup(createElement(OperatingSystemInventory, { groups }))).toContain('1,200'); expect(JSON.stringify(source)).toBe(before); }); it('does not infer Linux roles, supports explicit roles and preserves conflicting classifications', () => { expect(operatingSystemGroup('Debian')).toBe('Other / unclassified'); expect(operatingSystemGroup('Debian', ['Server'])).toBe('Servers'); expect(operatingSystemGroup('Windows 11', ['Server', 'Workstation'])).toBe('Other / unclassified'); expect(operatingSystemGroup('Windows 2019')).toBe('Servers'); expect(operatingSystemGroup('Windows 2000')).toBe('Other / unclassified'); }); it('keeps missing, null, negative, boolean and malformed counts unknown rather than zero', () => { for (const raw of [undefined, null, '', -1, true, '1.5', '1,2', 'unknown']) { const group = operatingSystemInventory(snapshot(os('Windows 11', raw, 1)))[1]; expect(group.entries[0].count).toBeUndefined(); expect(group.total).toBeUndefined(); } expect(operatingSystemInventory().every(group => group.total === undefined)).toBe(true); }); it('never borrows counts from another report, table or source row', () => { const fields = os('Windows 11', 2, 1); fields[1].source.rowIndex = 2; const foreign = os('Windows Server 2022', 99, 1, 'foreign'); const groups = operatingSystemInventory(snapshot([...fields, ...foreign])); expect(groups[1].entries[0].count).toBeUndefined(); expect(groups[0].total).toBe(99); fields[1].source.rowIndex = 1; fields[1].attributes.sourceTableIndex = 2; expect(operatingSystemInventory(snapshot(fields))[1].total).toBeUndefined(); }); it('does not sum duplicate OS rows or overlapping inventories and invalidates on changed inputs', () => { const rows = [...os('Windows 11', 4, 1), ...os('Windows 11', 4, 2)]; expect(operatingSystemInventory(snapshot(rows))[1].total).toBeUndefined(); const groups = operatingSystemInventory(snapshot([...os('Windows 11', 4, 1), ...os('Windows 11', 7, 1, 'other')])); expect(groups[1].total).toBeUndefined(); expect(groups[1].entries.map(entry => entry.count)).toEqual([7, 4]); expect(renderToStaticMarkup(createElement(OperatingSystemInventory, { groups }))).not.toContain('overlapping counts are not added'); expect(operatingSystemInventory(snapshot(os('Windows 11', 8, 1)))[1].total).toBe(8); }); it('shows the complete quantified inventory total when another inventory reports names without counts', () => { const groups = operatingSystemInventory(snapshot([ ...os('Windows Server 2022', 40, 1), ...os('Windows Server 2025', 19, 2), ...os('Windows Server 2016', 10, 3), ...os('Windows Server 2019', 3, 4), ...os('Windows 2022', undefined, 1, 'other') ])); expect(groups[0].total).toBe(72); expect(groups[0].entries.find(entry => entry.name === 'Windows 2022')?.count).toBeUndefined(); }); it('shows PingCastle active, inactive, enabled and disabled counts from the exact OS source row', () => { const groups = operatingSystemInventory(snapshot([ ...os('Windows Server 2022', 12, 1), ...osStates(1, { Active: 10, Inactive: 2, Enabled: 9, Disabled: 3 }), ...os('Windows 11', 8, 2) ])); expect(groups[0].entries[0]).toMatchObject({ count: 12, active: 10, inactive: 2, enabled: 9, disabled: 3 }); expect(groups[1].entries[0]).toMatchObject({ count: 8, active: undefined, inactive: undefined, enabled: undefined, disabled: undefined }); const markup = renderToStaticMarkup(createElement(OperatingSystemInventory, { groups })); ['Active', 'Inactive', 'Enabled', 'Disabled'].forEach(label => expect(markup).toContain(`>${label}<`)); expect(markup).toContain('>10<'); expect(markup).toContain('>3<'); expect(markup).toContain('>—<'); }); it('does not borrow or guess PingCastle state counts across source rows', () => { const groups = operatingSystemInventory(snapshot([ ...os('Windows Server 2022', 12, 1), ...osStates(2, { Active: 10, Inactive: 2, Enabled: 9, Disabled: 3 }) ])); expect(groups[0].entries[0]).toMatchObject({ count: 12, active: undefined, inactive: undefined, enabled: undefined, disabled: undefined }); }); it('opens the table in a movable window, maximizes it and restores focus after closing', () => { (globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }).IS_REACT_ACT_ENVIRONMENT = true; vi.stubGlobal('requestAnimationFrame', (callback: FrameRequestCallback) => { callback(0); return 1; }); const container = document.createElement('div'); document.body.append(container); const root = createRoot(container); const groups = operatingSystemInventory(snapshot([ ...os('Windows Server 2022', 12, 1), ...os('Windows Server 2025', 4, 1, 'other') ])); act(() => root.render(createElement(OperatingSystemInventory, { groups }))); const trigger = container.querySelector('[aria-label="Open Operating systems results"]'); expect(trigger).not.toBeNull(); expect(trigger?.textContent).toBe('Results'); expect(trigger?.classList.contains('results-btn')).toBe(true); act(() => trigger?.click()); expect(trigger?.classList.contains('is-open')).toBe(true); const floating = document.body.querySelector('.floating-data-window'); expect(floating?.getAttribute('aria-modal')).toBe('false'); expect(floating?.textContent).toContain('Windows Server 2022'); expect(floating?.querySelector('.client-os-source')?.textContent).toBe('Inventory'); act(() => floating?.querySelector('.floating-data-window-head')?.dispatchEvent(new MouseEvent('dblclick', { bubbles: true }))); expect(floating?.classList.contains('is-maximized')).toBe(true); expect(floating?.querySelector('[aria-label="Restore Operating systems window"]')).not.toBeNull(); act(() => floating?.querySelector('[aria-label="Close Operating systems window"]')?.click()); expect(document.body.querySelector('.floating-data-window')).toBeNull(); expect(document.activeElement).toBe(trigger); act(() => root.unmount()); container.remove(); vi.unstubAllGlobals(); }); });