import { createElement } from 'react'; import { renderToStaticMarkup } from 'react-dom/server'; import { describe, expect, it } 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 }))).toContain('overlapping counts are not added'); expect(operatingSystemInventory(snapshot(os('Windows 11', 8, 1)))[1].total).toBe(8); }); 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 }); }); });