/**
* @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');
});
});