/** * @vitest-environment happy-dom */ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { cleanup, render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { MemoryRouter } from 'react-router-dom'; import { mockAgent } from '../test/fixtures'; import { useWebSocket } from '../hooks/useWebSocket'; import { api } from '../api/client'; import CruciblePage, { agentColor, patchLabel, pendingBadge, portsBadge, postureBadge, postureTooltip, contingencyDepthBadge, sshBadge, thermalBadge, } from './CruciblePage'; // ── Mocks ───────────────────────────────────────────────────────────────── vi.mock('../hooks/useWebSocket', () => ({ useWebSocket: vi.fn() })); vi.mock('../context/MatrixRainContext', () => ({ useMatrixRain: () => ({ setCrucibleFocus: vi.fn() }), })); vi.mock('../hooks/useFleetGroups', () => ({ useFleetGroups: () => ({ groups: [], addGroup: vi.fn(), removeGroup: vi.fn() }), })); vi.mock('../api/client', () => ({ api: { sendAgentCommand: vi.fn().mockResolvedValue({ success: true }), }, })); vi.mock('../components/Fleet/CrucibleExpandedOps', () => ({ default: () =>
, })); vi.mock('../components/Fleet/FleetHeatMiniMap', () => ({ default: () =>
, })); vi.mock('../components/Fleet/LatencyBadge', () => ({ default: () => null, })); vi.mock('../components/Fleet/CreateGroupModal', () => ({ default: () => null, })); vi.mock('../components/Fleet/FleetGroupsStrip', () => ({ default: () => null, })); vi.mock('../hooks/useFleetBulkActions', () => ({ useFleetBulkActions: () => ({ bulkBusy: false, handleBulkAction: vi.fn() }), })); vi.mock('../components/Fleet/CrucibleAgentMeta', () => ({ default: () => null, })); vi.mock('../components/Presence/AlsoHere', () => ({ default: () => null, })); vi.mock('../components/Fleet/FullSysCheckPanel', () => ({ default: () => null, })); vi.mock('../components/HelpTip', () => ({ HelpTip: () => null, })); vi.mock('../components/NeonCard/NeonCard', () => ({ default: ({ children, className }: { children: React.ReactNode; className?: string }) => (
{children}
), })); const useWebSocketMock = vi.mocked(useWebSocket); function makeWsValue(overrides: Partial>) { return { isConnected: true, agents: [], recentShares: [], fleetAlerts: [], poolStatus: [], aiActivity: [], agentLogs: {}, commandResults: [], policyAcks: [], latestMessage: null, sendDashboardMessage: vi.fn(), ...overrides, }; } function renderCrucible(wsValue: ReturnType) { useWebSocketMock.mockReturnValue(wsValue as ReturnType); return render( ); } // ── Terminal rendering tests ────────────────────────────────────────────── describe('CruciblePage terminal — command_result processing', () => { beforeEach(() => { vi.clearAllMocks(); }); afterEach(() => { cleanup(); }); it('displays exec result in terminal when agent is selected', async () => { const agent = mockAgent({ id: 'agent-aaa-001', name: 'TestNode', status: 'online' }); const commandResults = [ { agent_id: 'agent-aaa-001', action: 'exec', success: true, message: 'hello world', _seq: 1 }, ]; // Render with the agent already "selected" by providing a pre-selected state. // The component reads commandResults from context and renders terminal lines. renderCrucible(makeWsValue({ agents: [agent], commandResults })); // The terminal should display the result message from the command. await waitFor(() => { expect(screen.getByText('hello world')).toBeInTheDocument(); }); }); it('displays multiple exec result lines split on newline', async () => { const agent = mockAgent({ id: 'agent-bbb-002', name: 'MultiNode', status: 'online' }); const commandResults = [ { agent_id: 'agent-bbb-002', action: 'exec', success: true, message: 'line one\nline two\nline three', _seq: 1, }, ]; renderCrucible(makeWsValue({ agents: [agent], commandResults })); await waitFor(() => { expect(screen.getByText('line one')).toBeInTheDocument(); expect(screen.getByText('line two')).toBeInTheDocument(); expect(screen.getByText('line three')).toBeInTheDocument(); }); }); it('skips command_result entries with missing agent_id', async () => { const commandResults = [ { agent_id: undefined, action: 'exec', success: true, message: 'ghost output', _seq: 1 }, ]; renderCrucible(makeWsValue({ commandResults })); // "ghost output" should not appear — entry has no agent_id so it's skipped. await new Promise((r) => setTimeout(r, 50)); expect(screen.queryByText('ghost output')).not.toBeInTheDocument(); }); it('skips already-seen entries when new commandResults arrive', async () => { const agent = mockAgent({ id: 'agent-ccc-003', name: 'SeqNode', status: 'online' }); // Initial render: seq=1 const ws1 = makeWsValue({ agents: [agent], commandResults: [{ agent_id: 'agent-ccc-003', action: 'exec', success: true, message: 'first', _seq: 1 }], }); const { rerender } = renderCrucible(ws1); await waitFor(() => expect(screen.getByText('first')).toBeInTheDocument()); // Update: add seq=2, keep seq=1 — only 'second' should be added (not 'first' again) const ws2 = makeWsValue({ agents: [agent], commandResults: [ { agent_id: 'agent-ccc-003', action: 'exec', success: true, message: 'first', _seq: 1 }, { agent_id: 'agent-ccc-003', action: 'exec', success: true, message: 'second', _seq: 2 }, ], }); useWebSocketMock.mockReturnValue(ws2 as ReturnType); rerender( ); await waitFor(() => expect(screen.getByText('second')).toBeInTheDocument()); // 'first' should appear exactly once (not twice from a replay) expect(screen.getAllByText('first')).toHaveLength(1); }); it('renders fleet filter toolbar when agents are registered', async () => { const agent = mockAgent({ name: 'FilterNode', status: 'online' }); renderCrucible(makeWsValue({ agents: [agent] })); await waitFor(() => { expect(screen.getByPlaceholderText('Search name, IP, notes, tags…')).toBeInTheDocument(); }); expect(screen.getByText('FilterNode')).toBeInTheDocument(); }); it('shows filter empty hint when no nodes match', async () => { const agent = mockAgent({ name: 'HiddenNode', tags: ['prod'] }); renderCrucible(makeWsValue({ agents: [agent] })); await waitFor(() => expect(screen.getByPlaceholderText('Search name, IP, notes, tags…')).toBeInTheDocument()); await userEvent.setup().type(screen.getByPlaceholderText('Search name, IP, notes, tags…'), 'nomatchxyz'); expect(screen.getByText('No nodes match filters.')).toBeInTheDocument(); }); it('shows fallback line when command result message is empty', async () => { const agent = mockAgent({ id: 'agent-empty-001', name: 'EmptyNode', status: 'online' }); renderCrucible( makeWsValue({ agents: [agent], commandResults: [ { agent_id: 'agent-empty-001', action: 'pause', success: true, message: '', _seq: 1 }, ], }), ); await waitFor(() => { expect(screen.getByText('[pause] OK')).toBeInTheDocument(); }); }); it('renders mining diagnostics JSON in terminal', async () => { const agent = mockAgent({ id: 'agent-diag-001', name: 'DiagNode', status: 'online' }); const diagnostics = { generated_at: '2026-06-06T12:00:00.000Z', platform: 'windows', configured_execution: 'auto', execution_mode: 'inprocess', active_method: 'inprocess', lotl_tier: 'inprocess', mining_hashrate: 512, lotl_attempts: [ { tier: 'container', ok: false, error: 'docker not found', duration_ms: 600 }, { tier: 'inprocess', ok: true, duration_ms: 1800 }, ], likely_blockers: [ 'mining paused by remote command or healthy container delegation', 'Windows Defender real-time protection is ON — use Calibrate exclusion script or allowlist install path', ], cpu: { remote_paused: true, has_job: false, hashrate_hps: 0 }, gpu: { enabled: false, active: false, paused: false }, }; const commandResults = [ { agent_id: 'agent-diag-001', action: 'mining_diagnostics', success: true, message: JSON.stringify(diagnostics, null, 2), _seq: 1, }, ]; renderCrucible(makeWsValue({ agents: [agent], commandResults })); await waitFor(() => { expect(screen.getByText('MINING DIAGNOSTICS')).toBeInTheDocument(); expect(screen.getByText('LIKELY BLOCKERS')).toBeInTheDocument(); expect( screen.getByText('mining paused by remote command or healthy container delegation'), ).toBeInTheDocument(); expect( screen.getByText( 'Windows Defender real-time protection is ON — use Calibrate exclusion script or allowlist install path', ), ).toBeInTheDocument(); expect(screen.getByText('Execution')).toBeInTheDocument(); expect(screen.getAllByText('inprocess').length).toBeGreaterThanOrEqual(1); expect(screen.getByText('LOTL TIER CHAIN')).toBeInTheDocument(); expect(screen.getByText('docker not found')).toBeInTheDocument(); expect(screen.getByText('600ms')).toBeInTheDocument(); }); }); it('shows LOTL tier badge on agent card when lotl_tier is set', async () => { const agent = mockAgent({ id: 'agent-lotl-001', name: 'LotlNode', status: 'online', lotl_tier: 'wsl', }); renderCrucible(makeWsValue({ agents: [agent] })); await waitFor(() => { expect(screen.getByText('LOTL WSL')).toBeInTheDocument(); }); }); }); // ── Helper function tests ───────────────────────────────────────────────── describe('CruciblePage helpers', () => { it('agentColor cycles palette by agent order', () => { const ids = ['a', 'b', 'c']; expect(agentColor('a', ids)).toBe('#00e8f5'); expect(agentColor('b', ids)).toBe('#39ff14'); expect(agentColor('missing', ids)).toBe('#00e8f5'); }); it('sshBadge reflects ssh_available tri-state', () => { expect(sshBadge(mockAgent({ ssh_available: true })).label).toBe('SSH ON'); expect(sshBadge(mockAgent({ ssh_available: false })).cls).toBe('ssh-off'); expect(sshBadge(mockAgent({ ssh_available: undefined })).label).toBe('SSH ?'); }); it('postureBadge buckets score thresholds', () => { expect(postureBadge(undefined).cls).toBe('posture-unk'); expect(postureBadge(90).cls).toBe('posture-good'); expect(postureBadge(50).cls).toBe('posture-warn'); expect(postureBadge(10).cls).toBe('posture-bad'); }); it('patchLabel marks stale patches beyond 30 days', () => { expect(patchLabel(10)?.cls).toBe('patch-ok'); expect(patchLabel(45)?.cls).toBe('patch-stale'); expect(patchLabel(undefined)).toBeNull(); }); it('portsBadge flags high listener counts', () => { expect(portsBadge(5)?.cls).toBe('ports-ok'); expect(portsBadge(25)?.cls).toBe('ports-many'); }); it('pendingBadge encodes update counts', () => { expect(pendingBadge(mockAgent({ pending_updates: 0 }))?.label).toBe('UP TO DATE'); expect(pendingBadge(mockAgent({ pending_updates: 3 }))?.cls).toBe('upd-warn'); expect(pendingBadge(mockAgent({ pending_updates: 12 }))?.cls).toBe('upd-bad'); expect(pendingBadge(mockAgent({ pending_updates: -1 }))?.label).toBe('UPD ?'); }); it('thermalBadge shows hot and warm thresholds', () => { expect(thermalBadge(mockAgent({ cpu_temp_c: 60 }))).toBeNull(); expect(thermalBadge(mockAgent({ cpu_temp_c: 70 }))?.cls).toBe('therm-warm'); expect(thermalBadge(mockAgent({ gpu_temp_c: 85 }))?.cls).toBe('therm-hot'); }); it('contingencyDepthBadge shows ONION depth when present', () => { expect(contingencyDepthBadge(mockAgent({ contingency_depth: 0 }))).toBeNull(); expect(contingencyDepthBadge(mockAgent({ contingency_depth: 3 }))?.label).toBe('ONION 3'); expect(contingencyDepthBadge(mockAgent({ contingency_depth: 10 }))?.cls).toBe('cn-contingency-deep'); }); it('postureTooltip includes defender, DNS drift, and services', () => { const agent = mockAgent({ defender_enabled: true, defender_rtp: false, dns_servers: ['8.8.8.8'], dns_drifted: true, services: [{ name: 'sshd', display_name: 'OpenSSH', status: 'running', start_type: 'auto' }], }); const tip = postureTooltip(agent); expect(tip).toContain('Defender'); expect(tip).toContain('DNS changed since last heartbeat'); expect(tip).toContain('OpenSSH'); }); });