import { cleanup, render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { afterEach, describe, expect, it, vi } from 'vitest'; import App from '../../src/App'; import { resetSystemStatusForTests } from '../../src/systemStatus'; afterEach(() => { cleanup(); resetSystemStatusForTests(); vi.unstubAllGlobals(); window.history.replaceState({}, '', '/'); }); function json(value: unknown, status = 200): Response { return new Response(JSON.stringify(value), { status, headers: { 'Content-Type': 'application/json' } }); } const healthySource = { state: 'healthy', freshness: 'fresh' }; describe('Stitch command overview', () => { it('shows real source values while failed sources remain explicitly unavailable', async () => { window.history.replaceState({}, '', '/'); const now = new Date().toISOString(); vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url === '/api/v1/system/status') return json({ version: '1', generatedAt: now, overallState: 'healthy', components: [ { id: 'database', state: 'healthy', reason: 'ok' }, { id: 'prometheus', state: 'healthy', reason: 'ok' }, ], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [{ sourceId: 'prometheus', state: 'degraded', reason: 'source_stale', ageSeconds: 420 }], }); if (url === '/api/v1/host') return json({ identity: { name: 'tower-lab' }, cpu: { totalPercent: 37.5, perCore: [25, 50] }, memory: { utilizationPercent: 62.25 }, source: healthySource, }); if (url.startsWith('/api/v1/containers')) return json({ source: healthySource, containers: [{ id: 'running', state: 'RUNNING', health: 'healthy' }, { id: 'stopped', state: 'stopped', health: 'unknown' }], total: 2 }); if (url.startsWith('/api/v1/services')) return json({ capabilityState: 'available', configurationState: 'configured', services: [{ id: 'svc-1', name: 'API', state: 'up' }], total: 1 }); if (url.startsWith('/api/v1/incidents')) return json({ items: [] }); if (url.startsWith('/api/v1/pools')) return json({ error: 'source unavailable' }, 503); return json({ error: 'unexpected request' }, 404); })); render(); expect((await screen.findAllByText('37,5%'))[0]).toBeVisible(); expect(document.querySelector('.instrument-band')).toBeInTheDocument(); expect(document.querySelector('.data-plane')).toBeInTheDocument(); expect(document.querySelector('.focus-panel')).toBeInTheDocument(); expect(document.querySelector('.context-inspector')).toBeInTheDocument(); expect(screen.getByRole('heading', { name: 'Signaalpad' })).toBeVisible(); const unavailableStorage = screen.getByRole('button', { name: /Opslag: Niet beschikbaar/ }); expect(unavailableStorage.closest('.signal-path-stage')).toHaveAttribute('data-tone', 'unknown'); const staleSource = screen.getByRole('button', { name: /Bronnen: Aandacht.*0\/1/ }); expect(staleSource.closest('.signal-path-stage')).toHaveAttribute('data-tone', 'attention'); expect(screen.getAllByText('62,3%')[0]).toBeVisible(); expect(screen.getByText('tower-lab')).toBeVisible(); expect(screen.getByRole('button', { name: /Workloads: Kritiek.*1\/2/ })).toBeVisible(); expect(screen.queryByText('0 pools')).not.toBeInTheDocument(); const storageCard = screen.getByRole('heading', { name: 'Capaciteit en toestand' }).closest('article'); expect(storageCard).not.toBeNull(); expect(within(storageCard!).getByText(/Deze overzichtsbron kon niet worden geladen/)).toBeVisible(); expect(screen.getByText('Opslag: Niet beschikbaar')).toBeVisible(); expect(screen.getByRole('heading', { name: 'Aandacht vereist' })).toBeVisible(); }); it('suppresses current workload claims when retained container data is stale', async () => { const now = new Date().toISOString(); vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url === '/api/v1/system/status') return json({ version: '1', generatedAt: now, overallState: 'healthy', components: [], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [{ sourceId: 'unraid', state: 'healthy', reason: 'ok' }] }); if (url === '/api/v1/host') return json({ source: healthySource, cpu: { totalPercent: 30 }, memory: { utilizationPercent: 40 } }); if (url.startsWith('/api/v1/containers')) return json({ source: { state: 'unknown', freshness: 'stale' }, containers: [{ id: 'retained', state: 'running', health: 'healthy' }], total: 1 }); if (url.startsWith('/api/v1/pools')) return json({ source: healthySource, pools: [], total: 0 }); if (url.startsWith('/api/v1/services')) return json({ capabilityState: 'available', configurationState: 'not_configured', services: [], total: 0 }); if (url.startsWith('/api/v1/incidents')) return json({ items: [] }); return json({}, 404); })); render(); expect(await screen.findByRole('button', { name: /Workloads: Verouderd.*actieve containers: —/ })).toBeVisible(); const workloadCard = screen.getByRole('heading', { name: 'Containers' }).closest('article'); expect(workloadCard).not.toBeNull(); expect(within(workloadCard!).getByText(/laatst bekende waarden worden niet als actueel getoond/i)).toBeVisible(); expect(within(workloadCard!).queryByText(/1 van 1 containers zijn actief/)).not.toBeInTheDocument(); }); it('settles independent resources while a single endpoint is still pending', async () => { const now = new Date().toISOString(); vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { const url = String(input); if (url === '/api/v1/system/status') return json({ version: '1', generatedAt: now, overallState: 'healthy', components: [], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [{ sourceId: 'unraid', state: 'healthy', reason: 'ok' }] }); if (url === '/api/v1/host') return json({ source: healthySource, cpu: { totalPercent: 30 }, memory: { utilizationPercent: 40 } }); if (url.startsWith('/api/v1/containers')) return json({ source: healthySource, containers: [], total: 0 }); if (url.startsWith('/api/v1/pools')) return json({ source: healthySource, pools: [{ id: 'cache', name: 'Cache', state: 'healthy', capacitySeverity: 'normal', utilizationPercent: 50 }], total: 1 }); if (url.startsWith('/api/v1/services')) return json({ capabilityState: 'available', configurationState: 'configured', services: [{ id: 'api', name: 'API', state: 'up' }], total: 1 }); if (url.startsWith('/api/v1/incidents')) return new Promise((_resolve, reject) => { init?.signal?.addEventListener('abort', () => reject(new DOMException('aborted', 'AbortError')), { once: true }); }); return json({}, 404); })); render(); expect(await screen.findByRole('button', { name: /Host: Gezond.*30%.*40%/ })).toBeVisible(); expect(screen.getByRole('button', { name: /Incidenten: Wordt geladen/ })).toBeVisible(); expect(screen.getByRole('heading', { level: 1, name: 'Status nog niet bevestigd' })).toBeVisible(); const incidentCard = screen.getByRole('heading', { name: 'Incidenten' }).closest('article'); expect(incidentCard).not.toBeNull(); expect(within(incidentCard!).getByText(/wordt geladen; er wordt nog geen toestand verondersteld/)).toBeVisible(); }); it('does not present an unavailable incident feed as an empty healthy feed', async () => { const now = new Date().toISOString(); vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url === '/api/v1/system/status') return json({ version: '1', generatedAt: now, overallState: 'healthy', components: [], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [{ sourceId: 'unraid', state: 'healthy', reason: 'ok' }] }); if (url === '/api/v1/host') return json({ source: healthySource, cpu: { totalPercent: 30 }, memory: { utilizationPercent: 40 } }); if (url.startsWith('/api/v1/containers')) return json({ source: healthySource, containers: [], total: 0 }); if (url.startsWith('/api/v1/pools')) return json({ source: healthySource, pools: [], total: 0 }); if (url.startsWith('/api/v1/services')) return json({ capabilityState: 'available', configurationState: 'not_configured', services: [], total: 0 }); if (url.startsWith('/api/v1/incidents')) return json({ code: 'UNAVAILABLE' }, 503); return json({}, 404); })); render(); expect(await screen.findByRole('button', { name: /Incidenten: Niet beschikbaar/ })).toBeVisible(); const incidentCard = screen.getByRole('heading', { name: 'Incidenten' }).closest('article'); expect(incidentCard).not.toBeNull(); expect(within(incidentCard!).getByText(/Deze overzichtsbron kon niet worden geladen/)).toBeVisible(); expect(within(incidentCard!).queryByText('Er zijn geen open incidenten geregistreerd.')).not.toBeInTheDocument(); }); it('distinguishes forbidden resources from an expired session', async () => { const now = new Date().toISOString(); vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url === '/api/v1/system/status') return json({ code: 'FORBIDDEN' }, 403); if (url === '/api/v1/host') return json({ source: healthySource, cpu: { totalPercent: 30 }, memory: { utilizationPercent: 40 } }); if (url.startsWith('/api/v1/containers')) return json({ source: healthySource, containers: [], total: 0 }); if (url.startsWith('/api/v1/pools')) return json({ code: 'FORBIDDEN' }, 403); if (url.startsWith('/api/v1/services')) return json({ capabilityState: 'available', configurationState: 'not_configured', services: [], total: 0 }); if (url.startsWith('/api/v1/incidents')) return json({ items: [] }); return json({}, 404); })); render(); expect(await screen.findByRole('button', { name: /Bronnen: Geen toegang/ })).toBeVisible(); expect(await screen.findByRole('button', { name: /Opslag: Geen toegang/ })).toBeVisible(); expect(screen.getByText('Opslag: Geen toegang')).toBeVisible(); expect(screen.getAllByText(/account heeft geen toegang tot deze overzichtsbron/).length).toBeGreaterThan(0); expect(screen.queryByRole('link', { name: /Aanmelden/ })).not.toBeInTheDocument(); }); it('prioriteert kritieke poolcapaciteit boven gezonde device-health', async () => { const now = new Date().toISOString(); vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url === '/api/v1/system/status') return json({ version: '1', generatedAt: now, overallState: 'healthy', components: [], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [] }); if (url.startsWith('/api/v1/pools')) return json({ source: healthySource, pools: [{ id: 'ssd', name: 'ssd', state: 'healthy', capacitySeverity: 'critical', utilizationPercent: 98.8 }], total: 1 }); if (url.startsWith('/api/v1/containers')) return json({ source: healthySource, containers: [], total: 0 }); if (url.startsWith('/api/v1/services')) return json({ capabilityState: 'available', configurationState: 'not_configured', services: [], total: 0 }); if (url.startsWith('/api/v1/incidents')) return json({ items: [] }); if (url === '/api/v1/host') return json({ source: healthySource }); return json({}, 404); })); render(); expect(await screen.findByText('ssd: Kritiek')).toBeVisible(); expect(screen.getByText(/kritieke capaciteitsgrens is overschreden/)).toBeVisible(); expect(screen.getByText('Kritiek · device-health gezond')).toBeVisible(); expect(await screen.findByRole('heading', { name: 'Aandacht vereist' })).toBeVisible(); await waitFor(() => expect(screen.getByRole('button', { name: /Opslag: Kritiek/ })).toHaveAttribute('aria-pressed', 'true')); }); it('retries every overview resource from the shared retry action', async () => { const user = userEvent.setup(); const now = new Date().toISOString(); let poolCalls = 0; vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => { const url = String(input); if (url === '/api/v1/system/status') return json({ version: '1', generatedAt: now, overallState: 'healthy', components: [{ id: 'database', state: 'healthy', reason: 'ok' }], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [] }); if (url === '/api/v1/host') return json({ source: healthySource, cpu: { totalPercent: 30 }, memory: { utilizationPercent: 40 } }); if (url.startsWith('/api/v1/containers')) return json({ source: healthySource, containers: [], total: 0 }); if (url.startsWith('/api/v1/services')) return json({ capabilityState: 'available', configurationState: 'not_configured', services: [], total: 0 }); if (url.startsWith('/api/v1/incidents')) return json({ items: [] }); if (url.startsWith('/api/v1/pools')) { poolCalls += 1; if (poolCalls === 1) return json({ code: 'POOLS_UNAVAILABLE' }, 503); return json({ source: healthySource, pools: [{ id: 'cache', name: 'Cache', state: 'healthy', capacitySeverity: 'normal', utilizationPercent: 50 }], total: 1 }); } return json({}, 404); })); render(); expect(await screen.findByText('Opslag: Niet beschikbaar')).toBeVisible(); await user.click(screen.getByRole('button', { name: 'Opnieuw laden' })); expect(await screen.findByRole('button', { name: /Opslag: Gezond/ })).toBeVisible(); expect(poolCalls).toBe(2); expect(screen.queryByText('Opslag: Niet beschikbaar')).not.toBeInTheDocument(); }); it('reads the bounded container target scale and marks truncated services partial', async () => { const now = new Date().toISOString(); const containers = Array.from({ length: 150 }, (_, index) => ({ id: `container-${String(index).padStart(3, '0')}`, state: 'running', health: 'healthy' })); const services = Array.from({ length: 100 }, (_, index) => ({ id: `service-${String(index).padStart(3, '0')}`, name: `Service ${index}`, state: 'up' })); let containerCalls = 0; vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => { const raw = String(input); const url = new URL(raw, 'http://pulse.test'); if (raw === '/api/v1/system/status') return json({ version: '1', generatedAt: now, overallState: 'healthy', components: [{ id: 'database', state: 'healthy', reason: 'ok' }], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [] }); if (url.pathname === '/api/v1/host') return json({ source: healthySource, cpu: { totalPercent: 30 }, memory: { utilizationPercent: 40 } }); if (url.pathname === '/api/v1/containers') { containerCalls += 1; const offset = Number(url.searchParams.get('after') ?? 0); const page = containers.slice(offset, offset + 100); return json({ source: healthySource, containers: page, total: containers.length, nextCursor: offset + page.length < containers.length ? String(offset + page.length) : undefined }); } if (url.pathname === '/api/v1/pools') return json({ source: healthySource, pools: [{ id: 'cache', name: 'Cache', state: 'healthy', capacitySeverity: 'normal', utilizationPercent: 50 }], total: 1 }); if (url.pathname === '/api/v1/services') return json({ capabilityState: 'available', configurationState: 'configured', services, total: 150 }); if (url.pathname === '/api/v1/incidents') return json({ items: [] }); return json({}, 404); })); render(); expect(await screen.findByRole('button', { name: /Workloads: Gezond.*150\/150/ })).toBeVisible(); const serviceStage = screen.getByRole('button', { name: /Services: Gedeeltelijke data.*≥100\/150/ }); expect(serviceStage.closest('.signal-path-stage')).toHaveAttribute('data-tone', 'unknown'); expect(screen.getByText('Services: Gedeeltelijke data')).toBeVisible(); expect(containerCalls).toBe(2); }); it('bounds container paging and does not inflate totals with overlapping rows', async () => { const now = new Date().toISOString(); let containerCalls = 0; vi.stubGlobal('fetch', vi.fn(async (input: RequestInfo | URL) => { const raw = String(input); const url = new URL(raw, 'http://pulse.test'); if (raw === '/api/v1/system/status') return json({ version: '1', generatedAt: now, overallState: 'healthy', components: [], backup: { state: 'disabled', reason: 'not_configured' }, sourceLag: [{ sourceId: 'unraid', state: 'healthy', reason: 'ok' }] }); if (url.pathname === '/api/v1/host') return json({ source: healthySource, cpu: { totalPercent: 30 }, memory: { utilizationPercent: 40 } }); if (url.pathname === '/api/v1/containers') { containerCalls += 1; const start = url.searchParams.get('after') === '100' ? 99 : url.searchParams.get('after') === '200' ? 199 : 0; const items = Array.from({ length: 100 }, (_, index) => ({ id: `container-${start + index}`, state: 'running', health: 'healthy' })); const nextCursor = containerCalls === 1 ? '100' : containerCalls === 2 ? '200' : '300'; return json({ source: healthySource, containers: items, total: 400, nextCursor }); } if (url.pathname === '/api/v1/pools') return json({ source: healthySource, pools: [], total: 0 }); if (url.pathname === '/api/v1/services') return json({ capabilityState: 'available', configurationState: 'not_configured', services: [], total: 0 }); if (url.pathname === '/api/v1/incidents') return json({ items: [] }); return json({}, 404); })); render(); const workloadStage = await screen.findByRole('button', { name: /Workloads: Gedeeltelijke data.*≥299\/400/ }); expect(workloadStage.closest('.signal-path-stage')).toHaveAttribute('data-tone', 'unknown'); expect(screen.getByText('Workloads: Gedeeltelijke data')).toBeVisible(); expect(containerCalls).toBe(3); }); });