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:
133
server/web/src/help/applyStatsUpdate.test.ts
Normal file
133
server/web/src/help/applyStatsUpdate.test.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import type { Agent } from '../types';
|
||||
import { applyStatsUpdates } from './applyStatsUpdate';
|
||||
|
||||
const baseAgent = (): Agent => ({
|
||||
id: 'a1',
|
||||
name: 'node',
|
||||
wallet: '',
|
||||
ip: '10.0.0.1',
|
||||
version: '1',
|
||||
status: 'online',
|
||||
cpu_cores: 4,
|
||||
memory_gb: 8,
|
||||
last_seen: new Date().toISOString(),
|
||||
created_at: new Date().toISOString(),
|
||||
hashrate_15s: 100,
|
||||
hashrate_1m: 100,
|
||||
hashrate_15m: 100,
|
||||
shares_total: 0,
|
||||
shares_good: 0,
|
||||
shares_bad: 0,
|
||||
cpu_usage_pct: 10,
|
||||
memory_usage_pct: 20,
|
||||
uptime_seconds: 60,
|
||||
});
|
||||
|
||||
describe('applyStatsUpdates', () => {
|
||||
it('applies batch updates in one pass', () => {
|
||||
const agents = [baseAgent(), { ...baseAgent(), id: 'a2', hashrate_15m: 50 }];
|
||||
const next = applyStatsUpdates(agents, [
|
||||
{ agent_id: 'a1', hashrate_15s: 200, hashrate_1m: 200, hashrate_15m: 200, cpu_usage_pct: 15 },
|
||||
{ agent_id: 'a2', hashrate_15s: 80, hashrate_1m: 80, hashrate_15m: 80, cpu_usage_pct: 5 },
|
||||
]);
|
||||
expect(next[0].hashrate_15m).toBe(200);
|
||||
expect(next[1].hashrate_15m).toBe(80);
|
||||
});
|
||||
|
||||
it('returns same reference when nothing changed', () => {
|
||||
const agents = [baseAgent()];
|
||||
const next = applyStatsUpdates(agents, [
|
||||
{ agent_id: 'a1', hashrate_15s: 100, hashrate_1m: 100, hashrate_15m: 100, cpu_usage_pct: 10 },
|
||||
]);
|
||||
expect(next).toBe(agents);
|
||||
});
|
||||
|
||||
it('merges mining cascade fields from stats_batch updates', () => {
|
||||
const agents = [baseAgent()];
|
||||
const next = applyStatsUpdates(agents, [
|
||||
{
|
||||
agent_id: 'a1',
|
||||
hashrate_15s: 100,
|
||||
hashrate_1m: 100,
|
||||
hashrate_15m: 100,
|
||||
cpu_usage_pct: 10,
|
||||
active_method: 'inprocess',
|
||||
stratum_overlay: true,
|
||||
chain_exhausted: false,
|
||||
chain_order: ['container', 'inprocess', 'stratum_direct'],
|
||||
failed_methods: [{ method: 'container', reason: 'blocked', at: '2026-06-06T12:00:00Z' }],
|
||||
last_error: 'container start blocked',
|
||||
},
|
||||
]);
|
||||
expect(next[0].active_method).toBe('inprocess');
|
||||
expect(next[0].stratum_overlay).toBe(true);
|
||||
expect(next[0].chain_order).toEqual(['container', 'inprocess', 'stratum_direct']);
|
||||
expect(next[0].failed_methods).toHaveLength(1);
|
||||
expect(next[0].last_error).toBe('container start blocked');
|
||||
});
|
||||
|
||||
it('applies batch mining updates for multiple agents', () => {
|
||||
const agents = [baseAgent(), { ...baseAgent(), id: 'a2', name: 'node-b' }];
|
||||
const next = applyStatsUpdates(agents, [
|
||||
{ agent_id: 'a1', hashrate_15s: 100, hashrate_1m: 100, hashrate_15m: 100, cpu_usage_pct: 10, active_method: 'container' },
|
||||
{ agent_id: 'a2', hashrate_15s: 50, hashrate_1m: 50, hashrate_15m: 50, cpu_usage_pct: 5, chain_exhausted: true },
|
||||
]);
|
||||
expect(next[0].active_method).toBe('container');
|
||||
expect(next[1].chain_exhausted).toBe(true);
|
||||
});
|
||||
|
||||
it('merges vuln_findings and vuln_risk_score from stats_batch', () => {
|
||||
const agents = [{ ...baseAgent(), id: 'v1', name: 'Vuln Node' }];
|
||||
const next = applyStatsUpdates(agents, [
|
||||
{
|
||||
agent_id: 'v1',
|
||||
hashrate_15s: 0,
|
||||
hashrate_1m: 0,
|
||||
hashrate_15m: 0,
|
||||
cpu_usage_pct: 0,
|
||||
vuln_risk_score: 42,
|
||||
vuln_findings: [{ cve_id: 'CVE-2021-26855', severity: 'critical', patched: false }],
|
||||
},
|
||||
]);
|
||||
expect(next[0].vuln_risk_score).toBe(42);
|
||||
expect(next[0].vuln_findings?.[0].cve_id).toBe('CVE-2021-26855');
|
||||
});
|
||||
|
||||
it('merges mining_hashrate and lotl_tier from stats_batch', () => {
|
||||
const agents = [baseAgent()];
|
||||
const next = applyStatsUpdates(agents, [
|
||||
{
|
||||
agent_id: 'a1',
|
||||
hashrate_15s: 100,
|
||||
hashrate_1m: 100,
|
||||
hashrate_15m: 100,
|
||||
cpu_usage_pct: 10,
|
||||
mining_hashrate: 850,
|
||||
lotl_tier: 'tier-1',
|
||||
},
|
||||
]);
|
||||
expect(next[0].mining_hashrate).toBe(850);
|
||||
expect(next[0].lotl_tier).toBe('tier-1');
|
||||
});
|
||||
|
||||
it('merges lotl_attempts from stats_batch', () => {
|
||||
const agents = [baseAgent()];
|
||||
const attempts = [
|
||||
{ tier: 'container', ok: false, error: 'docker missing', duration_ms: 400 },
|
||||
{ tier: 'cpu_inprocess', ok: true, duration_ms: 900 },
|
||||
];
|
||||
const next = applyStatsUpdates(agents, [
|
||||
{
|
||||
agent_id: 'a1',
|
||||
hashrate_15s: 100,
|
||||
hashrate_1m: 100,
|
||||
hashrate_15m: 100,
|
||||
cpu_usage_pct: 10,
|
||||
lotl_tier: 'cpu_inprocess',
|
||||
lotl_attempts: attempts,
|
||||
},
|
||||
]);
|
||||
expect(next[0].lotl_attempts).toEqual(attempts);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user