import { describe, it, expect } from 'vitest'; import { normalizeStorage, storageHealth } from '../../lib/proxmox/storage.js'; // Fixtures mirror real PVE payload shapes from this cluster. const STORAGE = [ { storage: 'localzfs', node: 'z', status: 'available', plugintype: 'zfspool', disk: 37e9, maxdisk: 516e9 }, { storage: 'donatello-vm', node: 'z', status: 'unknown', plugintype: 'zfspool', disk: 0, maxdisk: 0 }, { storage: 'leonardo-vm', node: 'z', status: 'unknown', plugintype: 'zfspool', disk: 0, maxdisk: 0 }, { storage: 'local', node: 'z', status: 'available', plugintype: 'dir', disk: 1e9, maxdisk: 100e9 } ]; const VMS = [ { vmid: 100, name: 'mediastack', type: 'lxc', node: 'z', disk: 60e9, maxdisk: 63e9, status: 'running' }, // 95% { vmid: 311, name: 'void-app', type: 'lxc', node: 'z', disk: 4e9, maxdisk: 16e9, status: 'running' }, // 25% { vmid: 200, name: 'OpenClaw', type: 'qemu', node: 'z', disk: 0, maxdisk: 32e9, status: 'running' } // skipped (qemu/0) ]; const ZFS = { z: [{ name: 'localzfs', health: 'ONLINE', alloc: 37e9, size: 516e9, frag: 6 }] }; describe('normalizeStorage', () => { it('flags a dropped zfspool, a hot container, and rolls up worst=crit', () => { const r = normalizeStorage(STORAGE, VMS, ZFS); // dropped pools (donatello/leonardo) surface in `down` expect(r.down.map(d => d.name).sort()).toEqual(['donatello-vm', 'leonardo-vm']); expect(r.down.every(d => d.status === 'crit')).toBe(true); // imported pool present + healthy expect(r.pools).toHaveLength(1); expect(r.pools[0].name).toBe('localzfs'); // guests: qemu/0 skipped, sorted desc, CT100 at 95% is crit expect(r.guests.map(g => g.vmid)).toEqual([100, 311]); expect(r.guests[0].pct).toBe(95); expect(r.guests[0].status).toBe('crit'); expect(r.worst).toBe('crit'); expect(r.alerts.some(a => a.includes('donatello-vm'))).toBe(true); expect(r.alerts.some(a => a.includes('CT 100'))).toBe(true); }); it('all-healthy rolls up to ok', () => { const r = normalizeStorage( [{ storage: 'localzfs', node: 'z', status: 'available', plugintype: 'zfspool' }], [{ vmid: 311, name: 'void-app', type: 'lxc', node: 'z', disk: 4e9, maxdisk: 16e9 }], { z: [{ name: 'localzfs', health: 'ONLINE', alloc: 37e9, size: 516e9 }] } ); expect(r.worst).toBe('ok'); expect(r.down).toHaveLength(0); expect(r.alerts).toHaveLength(0); }); }); describe('storageHealth', () => { it('returns proxmox_not_configured without a token', async () => { const r = await storageHealth({ apiUrl: '', token: '' }); expect(r.error).toBe('proxmox_not_configured'); }); it('fetches + normalizes via injected fetch', async () => { const fetchImpl = async (url) => ({ ok: true, json: async () => { if (url.includes('type=storage')) return { data: STORAGE }; if (url.includes('type=vm')) return { data: VMS }; if (url.includes('/nodes/z/disks/zfs')) return { data: ZFS.z }; if (url.endsWith('/nodes')) return { data: [{ node: 'z', status: 'online' }] }; return { data: [] }; } }); const r = await storageHealth({ apiUrl: 'https://pve:8006', token: 'tok', fetchImpl }); expect(r.worst).toBe('crit'); expect(r.down).toHaveLength(2); expect(typeof r.at).toBe('number'); }); });