/** * Tests for the Storage Details UI component. * * Tests cover: * - Rendering of storage details accordion * - Accordion toggle behavior * - Filesystem table rendering with warning thresholds * - ZFS and LVM section rendering * - Inspector mode drawer * - Feature flag badges (ZFS, LVM) */ import { describe, it, expect, beforeEach, vi } from 'vitest'; // Mock storage_details data fixtures const mockLinuxDebianStorageDetails = { os_type: 'linux-debian', status: 'ok', collected_at: '2025-12-19T10:00:00Z', commands_run: [ { cmd: 'lsblk', status: 'ok' }, { cmd: 'findmnt', status: 'ok' }, { cmd: 'df', status: 'ok' } ], partial_failures: [], summary: { total_bytes: 500107862016, used_bytes: 125026965504, free_bytes: 349600112640, used_pct: 25.0 }, filesystems: [ { device: '/dev/sda2', fstype: 'ext4', size_bytes: 499570991104, used_bytes: 125026965504, avail_bytes: 349600112640, use_pct: 26, mountpoint: '/' } ], block_devices: [ { name: 'sda', type: 'disk', size: 500107862016, model: 'Samsung SSD 860' } ], lvm: { pvs: [], vgs: [], lvs: [] }, zfs: { pools: [], datasets: [] }, feature_flags: { has_lvm: false, has_zfs: false, has_lsblk: true, has_findmnt: true } }; const mockTrueNASStorageDetails = { os_type: 'truenas-core', status: 'ok', collected_at: '2025-12-19T10:00:00Z', commands_run: [ { cmd: 'df', status: 'ok' }, { cmd: 'zpool list', status: 'ok' }, { cmd: 'zfs list', status: 'ok' } ], partial_failures: [], summary: { total_bytes: 4000787030016, used_bytes: 2800550921011, free_bytes: 1200236109005, used_pct: 70.0 }, filesystems: [], block_devices: [], lvm: { pvs: [], vgs: [], lvs: [] }, zfs: { pools: [ { name: 'tank', size_bytes: 4000787030016, alloc_bytes: 2800550921011, free_bytes: 1200236109005, cap_pct: 70, health: 'ONLINE' } ], datasets: [ { name: 'tank/apps', used_bytes: 107374182400, avail_bytes: 1200236109005, mountpoint: '/mnt/tank/apps', type: 'filesystem' } ] }, feature_flags: { has_lvm: false, has_zfs: true, has_lsblk: false, has_findmnt: false } }; const mockHighUsageStorageDetails = { os_type: 'linux-debian', status: 'ok', collected_at: '2025-12-19T10:00:00Z', commands_run: [{ cmd: 'df', status: 'ok' }], partial_failures: [], summary: { total_bytes: 100000000000, used_bytes: 92000000000, free_bytes: 8000000000, used_pct: 92.0 }, filesystems: [ { device: '/dev/sda1', fstype: 'ext4', size_bytes: 100000000000, used_bytes: 92000000000, avail_bytes: 8000000000, use_pct: 92, mountpoint: '/' } ], block_devices: [], lvm: { pvs: [], vgs: [], lvs: [] }, zfs: { pools: [], datasets: [] }, feature_flags: { has_lvm: false, has_zfs: false, has_lsblk: true, has_findmnt: true } }; describe('Storage Details UI Component', () => { describe('renderStorageDetailsSection', () => { it('should return empty string when storage_details is null', () => { const metrics = { storage_details: null }; const result = renderStorageDetailsSection(metrics, 'host-1'); expect(result).toBe(''); }); it('should return empty string when storage_details is undefined', () => { const metrics = {}; const result = renderStorageDetailsSection(metrics, 'host-1'); expect(result).toBe(''); }); it('should render accordion header with correct title', () => { const metrics = { storage_details: mockLinuxDebianStorageDetails }; const result = renderStorageDetailsSection(metrics, 'host-1'); expect(result).toContain('Stockage détaillé'); expect(result).toContain('fa-database'); }); it('should show status badge', () => { const metrics = { storage_details: mockLinuxDebianStorageDetails }; const result = renderStorageDetailsSection(metrics, 'host-1'); expect(result).toContain('OK'); }); it('should show feature chips for ZFS', () => { const metrics = { storage_details: mockTrueNASStorageDetails }; const result = renderStorageDetailsSection(metrics, 'host-1'); expect(result).toContain('ZFS'); }); it('should show usage percentage in summary', () => { const metrics = { storage_details: mockLinuxDebianStorageDetails }; const result = renderStorageDetailsSection(metrics, 'host-1'); expect(result).toContain('25%'); }); }); describe('Filesystem Table', () => { it('should render filesystem table with columns', () => { const html = renderFilesystemsTable(mockLinuxDebianStorageDetails.filesystems); expect(html).toContain('Mount'); expect(html).toContain('Device'); expect(html).toContain('FS'); expect(html).toContain('Taille'); }); it('should show mountpoint in table', () => { const html = renderFilesystemsTable(mockLinuxDebianStorageDetails.filesystems); expect(html).toContain('/'); }); it('should highlight high usage filesystems', () => { const html = renderFilesystemsTable(mockHighUsageStorageDetails.filesystems); expect(html).toContain('text-red-400'); expect(html).toContain('92%'); }); it('should filter out virtual filesystems', () => { const filesystems = [ { device: '/dev/sda1', mountpoint: '/', use_pct: 50 }, { device: 'tmpfs', mountpoint: '/run', use_pct: 10 }, { device: 'devtmpfs', mountpoint: '/dev', use_pct: 0 } ]; const html = renderFilesystemsTable(filesystems); expect(html).not.toContain('/run'); expect(html).not.toContain('/dev'); }); }); describe('ZFS Section', () => { it('should render ZFS pools', () => { const html = renderZfsSection(mockTrueNASStorageDetails.zfs); expect(html).toContain('tank'); expect(html).toContain('fa-water'); }); it('should show pool health status', () => { const html = renderZfsSection(mockTrueNASStorageDetails.zfs); expect(html).toContain('ONLINE'); expect(html).toContain('text-green-400'); }); it('should show pool usage percentage', () => { const html = renderZfsSection(mockTrueNASStorageDetails.zfs); expect(html).toContain('70%'); }); it('should render datasets list', () => { const html = renderZfsSection(mockTrueNASStorageDetails.zfs); expect(html).toContain('tank/apps'); }); it('should return empty string when no ZFS', () => { const html = renderZfsSection({ pools: [], datasets: [] }); expect(html).toBe(''); }); }); describe('Inspector Mode', () => { it('should show inspector button', () => { const metrics = { storage_details: mockLinuxDebianStorageDetails }; const result = renderStorageDetailsSection(metrics, 'host-1'); expect(result).toContain('fa-info-circle'); expect(result).toContain('toggleStorageInspector'); }); it('should show OS type in inspector', () => { const html = renderInspectorDrawer(mockLinuxDebianStorageDetails, 'host-1', true); expect(html).toContain('linux-debian'); expect(html).toContain('OS détecté'); }); it('should show commands run in inspector', () => { const html = renderInspectorDrawer(mockLinuxDebianStorageDetails, 'host-1', true); expect(html).toContain('lsblk'); expect(html).toContain('findmnt'); expect(html).toContain('df'); }); it('should show collection timestamp', () => { const html = renderInspectorDrawer(mockLinuxDebianStorageDetails, 'host-1', true); expect(html).toContain('Collecté le'); }); }); describe('Warning Thresholds', () => { it('should apply warning class for usage >= 75%', () => { const fs = { use_pct: 80, mountpoint: '/var' }; const colorClass = getPctColor(fs.use_pct); expect(colorClass).toBe('bg-yellow-500'); }); it('should apply critical class for usage >= 90%', () => { const fs = { use_pct: 95, mountpoint: '/' }; const colorClass = getPctColor(fs.use_pct); expect(colorClass).toBe('bg-red-500'); }); it('should apply normal class for usage < 75%', () => { const fs = { use_pct: 50, mountpoint: '/home' }; const colorClass = getPctColor(fs.use_pct); expect(colorClass).toBe('bg-green-500'); }); }); describe('Byte Formatting', () => { it('should format bytes to GB', () => { const result = formatBytes(107374182400); // 100 GB expect(result).toBe('100 GB'); }); it('should format bytes to TB for large values', () => { const result = formatBytes(1099511627776); // 1 TB expect(result).toBe('1.0 TB'); }); it('should return empty string for zero', () => { const result = formatBytes(0); expect(result).toBe(''); }); it('should handle small values', () => { const result = formatBytes(1073741824); // 1 GB expect(result).toBe('1.0 GB'); }); }); }); // Helper functions to test (would be extracted from main.js in real implementation) function renderStorageDetailsSection(metrics, hostId) { const storageDetails = metrics?.storage_details; if (!storageDetails) return ''; const status = storageDetails.status || 'unknown'; const flags = storageDetails.feature_flags || {}; const summary = storageDetails.summary || {}; const chips = []; if (flags.has_zfs) chips.push('ZFS'); if (flags.has_lvm) chips.push('LVM'); const usedPct = summary.used_pct ? Number(summary.used_pct).toFixed(0) : null; const statusBadge = status === 'ok' ? 'bg-green-500/20 text-green-400' : status === 'partial' ? 'bg-yellow-500/20 text-yellow-400' : 'bg-red-500/20 text-red-400'; const statusText = status === 'ok' ? 'OK' : status === 'partial' ? 'Partiel' : 'Erreur'; return `
Stockage détaillé ${statusText} ${chips.map(c => `${c}`).join('')}
${usedPct}% utilisé
`; } function renderFilesystemsTable(filesystems) { if (!filesystems.length) return ''; const filtered = filesystems.filter(fs => { const mp = (fs.mountpoint || '').toLowerCase(); const dev = (fs.device || '').toLowerCase(); return !mp.startsWith('/run') && !mp.startsWith('/sys') && !mp.startsWith('/proc') && !dev.includes('tmpfs') && !dev.includes('devtmpfs'); }); const sanitizeDevice = (device) => { const d = device || ''; return (typeof d === 'string' && d.startsWith('/dev/')) ? d.slice('/dev/'.length) : d; }; const rows = filtered.map(fs => { const pct = fs.use_pct || 0; const device = sanitizeDevice(fs.device || ''); const pctClass = pct >= 90 ? 'text-red-400' : pct >= 75 ? 'text-yellow-400' : 'text-green-400'; return `${fs.mountpoint}${device}${fs.fstype || ''}${pct}%`; }).join(''); return ` ${rows}
MountDeviceFSTaille
`; } function renderZfsSection(zfs) { if (!zfs.pools.length && !zfs.datasets.length) return ''; const poolCards = zfs.pools.map(pool => { const healthColor = pool.health === 'ONLINE' ? 'text-green-400' : 'text-red-400'; return `
${pool.name} ${pool.health} ${pool.cap_pct}%
`; }).join(''); const datasetsList = zfs.datasets.map(ds => `
${ds.name}
`).join(''); return `
${poolCards}${datasetsList}
`; } function renderInspectorDrawer(storageDetails, hostId, isOpen) { if (!isOpen) return ''; const cmdList = storageDetails.commands_run.map(cmd => `
${cmd.cmd} - ${cmd.status}
` ).join(''); return `
OS détecté: ${storageDetails.os_type}
Collecté le: ${storageDetails.collected_at}
Commandes: ${cmdList}
`; } function getPctColor(pct) { if (pct === null || pct === undefined) return 'bg-gray-600'; return pct >= 90 ? 'bg-red-500' : pct >= 75 ? 'bg-yellow-500' : 'bg-green-500'; } function formatBytes(bytes) { if (!bytes || bytes <= 0) return ''; const gb = bytes / (1024 * 1024 * 1024); if (gb >= 1024) return `${(gb / 1024).toFixed(1)} TB`; if (gb >= 100) return `${gb.toFixed(0)} GB`; return `${gb.toFixed(1)} GB`; }