Add tiered LOTL mining onion and fleet recon so agents can fallback across execution tiers while operators see spread and vuln posture in Crucible. Includes triple-onion chain, spread cred graph, and full Go/TS/E2E test validation.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
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';
|
||||
@@ -56,6 +57,14 @@ 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,
|
||||
}));
|
||||
@@ -194,6 +203,23 @@ describe('CruciblePage terminal — command_result processing', () => {
|
||||
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(
|
||||
@@ -209,6 +235,71 @@ describe('CruciblePage terminal — command_result processing', () => {
|
||||
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 ─────────────────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user