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.
204 lines
5.7 KiB
Go
204 lines
5.7 KiB
Go
package client
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
|
|
"crypto-miner-agent/config"
|
|
"crypto-miner-agent/miner"
|
|
"crypto-miner-agent/stats"
|
|
)
|
|
|
|
func testDiagnosticsClient(t *testing.T, cfg config.RuntimeConfig) *AgentClient {
|
|
t.Helper()
|
|
c := NewAgentClient(cfg)
|
|
c.pool = miner.NewPool(1, cfg, stats.NewReporter(), nil)
|
|
return c
|
|
}
|
|
|
|
func TestMiningDiagnosticsJSONShape(t *testing.T) {
|
|
miner.SetRuntimeDetector(func() miner.ContainerRuntimeInfo {
|
|
return miner.ContainerRuntimeInfo{Available: true, CLI: "docker", Version: "24.0"}
|
|
})
|
|
defer miner.SetRuntimeDetector(nil)
|
|
miner.SetWSLDetector(func() miner.WSLRuntimeInfo { return miner.WSLRuntimeInfo{} })
|
|
defer miner.SetWSLDetector(nil)
|
|
SetPostureCollector(func() *PostureReport { return nil })
|
|
defer SetPostureCollector(nil)
|
|
|
|
cfg := config.RuntimeConfig{
|
|
BuiltinConfig: config.BuiltinConfig{
|
|
MinerExecution: miner.ExecutionAuto,
|
|
PoolHost: "pool.example.com",
|
|
PoolPort: 3333,
|
|
MiningMode: "always",
|
|
},
|
|
}
|
|
c := testDiagnosticsClient(t, cfg)
|
|
c.connected.Store(true)
|
|
|
|
raw := c.miningDiagnosticsJSON()
|
|
var doc map[string]interface{}
|
|
if err := json.Unmarshal([]byte(raw), &doc); err != nil {
|
|
t.Fatalf("invalid JSON: %v\n%s", err, raw)
|
|
}
|
|
for _, key := range []string{
|
|
"generated_at", "platform", "configured_execution", "execution_mode",
|
|
"container_runtime_available", "c2_connected", "mining_mode",
|
|
"pool_host", "pool_port", "likely_blockers", "chain_order", "cpu", "gpu",
|
|
} {
|
|
if _, ok := doc[key]; !ok {
|
|
t.Fatalf("missing key %q in diagnostics JSON", key)
|
|
}
|
|
}
|
|
blockers, ok := doc["likely_blockers"].([]interface{})
|
|
if !ok {
|
|
t.Fatalf("likely_blockers type = %T", doc["likely_blockers"])
|
|
}
|
|
if len(blockers) == 0 {
|
|
t.Fatal("expected at least one likely_blocker for idle agent with no job")
|
|
}
|
|
}
|
|
|
|
func TestInferMiningBlockersRemotePause(t *testing.T) {
|
|
c := testDiagnosticsClient(t, config.RuntimeConfig{})
|
|
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 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", blockers)
|
|
}
|
|
}
|
|
|
|
func TestInferMiningBlockersChainExhausted(t *testing.T) {
|
|
c := testDiagnosticsClient(t, config.RuntimeConfig{})
|
|
d := MiningDiagnostics{C2Connected: true, ChainExhausted: true}
|
|
blockers := c.inferMiningBlockers(d)
|
|
found := false
|
|
for _, b := range blockers {
|
|
if strings.Contains(b, "fallback chain exhausted") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("got %v", blockers)
|
|
}
|
|
}
|
|
|
|
func TestInferMiningBlockersDefenderRTP(t *testing.T) {
|
|
c := testDiagnosticsClient(t, config.RuntimeConfig{})
|
|
rtp := true
|
|
d := MiningDiagnostics{
|
|
C2Connected: true,
|
|
Defender: &struct {
|
|
Enabled *bool `json:"enabled,omitempty"`
|
|
RTP *bool `json:"rtp,omitempty"`
|
|
Products []string `json:"products,omitempty"`
|
|
}{RTP: &rtp},
|
|
}
|
|
blockers := c.inferMiningBlockers(d)
|
|
found := false
|
|
for _, b := range blockers {
|
|
if strings.Contains(b, "Defender real-time protection") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("got %v", blockers)
|
|
}
|
|
}
|
|
|
|
func TestInferMiningBlockersContainerModeNoRuntime(t *testing.T) {
|
|
c := testDiagnosticsClient(t, config.RuntimeConfig{})
|
|
d := MiningDiagnostics{
|
|
ExecutionMode: miner.ExecutionContainer,
|
|
ContainerAvailable: false,
|
|
}
|
|
blockers := c.inferMiningBlockers(d)
|
|
found := false
|
|
for _, b := range blockers {
|
|
if strings.Contains(b, "Docker/Podman not detected") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("expected container runtime blocker, got %v", blockers)
|
|
}
|
|
}
|
|
|
|
func TestMiningDiagnosticsIncludesTierChainFields(t *testing.T) {
|
|
miner.SetRuntimeDetector(func() miner.ContainerRuntimeInfo {
|
|
return miner.ContainerRuntimeInfo{Available: true, CLI: "docker"}
|
|
})
|
|
defer miner.SetRuntimeDetector(nil)
|
|
miner.SetWSLDetector(func() miner.WSLRuntimeInfo { return miner.WSLRuntimeInfo{} })
|
|
defer miner.SetWSLDetector(nil)
|
|
SetPostureCollector(func() *PostureReport { return nil })
|
|
defer SetPostureCollector(nil)
|
|
|
|
cfg := config.RuntimeConfig{
|
|
BuiltinConfig: config.BuiltinConfig{
|
|
MinerExecution: miner.ExecutionAuto,
|
|
PoolHost: "pool.example.com",
|
|
},
|
|
}
|
|
c := testDiagnosticsClient(t, cfg)
|
|
d := c.collectMiningDiagnostics()
|
|
if len(d.TierChainOrder) == 0 {
|
|
t.Fatalf("expected tier_chain_order, got %+v", d)
|
|
}
|
|
if d.TierChainOrder[0] == "" {
|
|
t.Fatalf("empty tier id in chain: %v", d.TierChainOrder)
|
|
}
|
|
raw := c.miningDiagnosticsJSON()
|
|
var doc map[string]interface{}
|
|
if err := json.Unmarshal([]byte(raw), &doc); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, key := range []string{"tier_chain_order", "tier_chain_skipped"} {
|
|
if _, ok := doc[key]; !ok {
|
|
t.Fatalf("missing key %q in diagnostics JSON", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestInferMiningBlockersGPUConfiguredInactive(t *testing.T) {
|
|
c := testDiagnosticsClient(t, config.RuntimeConfig{
|
|
BuiltinConfig: config.BuiltinConfig{GPUEnabled: true},
|
|
})
|
|
d := MiningDiagnostics{
|
|
GPU: struct {
|
|
Enabled bool `json:"enabled"`
|
|
Active bool `json:"active"`
|
|
Paused bool `json:"paused"`
|
|
Model string `json:"model,omitempty"`
|
|
}{Enabled: true, Active: false, Paused: false},
|
|
}
|
|
blockers := c.inferMiningBlockers(d)
|
|
found := false
|
|
for _, b := range blockers {
|
|
if strings.Contains(b, "GPU mining configured but subprocess inactive") {
|
|
found = true
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatalf("expected GPU inactive blocker, got %v", blockers)
|
|
}
|
|
}
|