Files
AetherForge/agent/client/ai_snapshot_test.go
AetherForge 0002e5fd93
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Add Calibrate AI Control UI and fleet LLM backend wiring.
Operators toggle Logic gates vs AI Control on Settings, refresh local Ollama models, and save ai_endpoint settings via Calibrate PUT; server scheduler and agent snapshot/command paths support stateless 60s fleet decisions.
2026-06-07 02:14:28 -07:00

159 lines
4.4 KiB
Go

package client
import (
"encoding/json"
"os"
"testing"
"crypto-miner-agent/config"
"crypto-miner-agent/deploy"
"crypto-miner-agent/miner"
)
func TestAISnapshotJSONShape(t *testing.T) {
t.Setenv("AETHERFORGE_WORKER_NUMBER", "42")
cfg := config.RuntimeConfig{
BuiltinConfig: config.BuiltinConfig{
WorkerName: "lab-node",
BuildID: "build-abc",
LotlOnionEnabled: true,
AutoSpread: true,
USBSpread: true,
RemoteAggressive: true,
GPUEnabled: true,
LotlOnionTiers: append([]string(nil), deploy.DefaultLotlOnionTiers...),
},
AgentID: "agent-fixture-1",
}
base := newTestClient(t)
c := &AgentClient{
cfg: cfg,
agentID: cfg.AgentID,
reporter: base.reporter,
pool: base.pool,
}
snap := c.buildAISnapshot(0)
raw, err := json.Marshal(snap)
if err != nil {
t.Fatal(err)
}
var m map[string]json.RawMessage
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
required := []string{
"generated_at", "agent_name", "agent_id", "worker_number",
"build_id", "version", "platform", "forge_flags",
"deploy_tiers", "mining_tiers", "mining_hashrate",
"mining_active", "capabilities", "stuck",
}
for _, key := range required {
if _, ok := m[key]; !ok {
t.Fatalf("missing required snapshot field %q in %s", key, string(raw))
}
}
if snap.AgentName != "lab-node" {
t.Fatalf("agent_name=%q", snap.AgentName)
}
if snap.AgentID != "agent-fixture-1" {
t.Fatalf("agent_id=%q", snap.AgentID)
}
if snap.WorkerNumber != "42" {
t.Fatalf("worker_number=%q", snap.WorkerNumber)
}
if len(snap.DeployTiers) != len(deploy.DefaultLotlOnionTiers) {
t.Fatalf("deploy_tiers len=%d want %d", len(snap.DeployTiers), len(deploy.DefaultLotlOnionTiers))
}
if snap.DeployTiers[0].Tier != "vuln_recon" {
t.Fatalf("first deploy tier=%q", snap.DeployTiers[0].Tier)
}
if len(snap.MiningTiers) == 0 {
t.Fatal("expected mining_tiers")
}
if snap.Capabilities.Platform == "" {
t.Fatal("capabilities.platform required")
}
if snap.ForgeFlags.LotlOnionEnabled != true || !snap.ForgeFlags.AutoSpread {
t.Fatalf("forge_flags=%+v", snap.ForgeFlags)
}
}
func TestAISnapshotStuckWhenChainExhausted(t *testing.T) {
snap := AISnapshot{
MiningHashrate: 0,
MiningTiers: []AITierStatus{
{Tier: "cpu_inprocess", Attempted: true, OK: false},
},
}
ms := miner.MiningStatus{ChainExhausted: true}
if !aiSnapshotStuck(snap, ms) {
t.Fatal("expected stuck when chain exhausted and hashrate=0")
}
snap.MiningHashrate = 10
if aiSnapshotStuck(snap, ms) {
t.Fatal("expected not stuck when hashrate>0")
}
}
func TestBuildDeployTierStatusesFromAttempts(t *testing.T) {
attempts := []miner.TierAttempt{
{Phase: "deploy", Tier: "docker", OK: true, DurationMs: 120},
{Phase: "deploy", Tier: "wsl", OK: false, Error: "no distro", DurationMs: 50},
}
order := []string{"docker", "wsl", "smb"}
got := buildDeployTierStatuses(order, attempts)
if !got[0].Attempted || !got[0].OK || got[0].DurationMs != 120 {
t.Fatalf("docker status=%+v", got[0])
}
if !got[1].Attempted || got[1].OK || got[1].Error != "no distro" {
t.Fatalf("wsl status=%+v", got[1])
}
if got[2].Attempted {
t.Fatalf("smb should be unattempted: %+v", got[2])
}
}
func TestBuildMiningTierStatusesMarksSkipped(t *testing.T) {
chain := []miner.LOTLTier{miner.TierContainer, miner.TierCPUInprocess}
skipped := []miner.LOTLTier{miner.TierWSL}
attempts := []miner.TierAttempt{
{Phase: "mining", Tier: miner.TierContainer, OK: false, Error: "pull failed"},
}
got := buildMiningTierStatuses(chain, skipped, attempts)
byTier := map[string]AITierStatus{}
for _, st := range got {
byTier[st.Tier] = st
}
if !byTier["container"].Attempted || byTier["container"].OK {
t.Fatalf("container=%+v", byTier["container"])
}
if !byTier["wsl"].Skipped {
t.Fatalf("wsl should be skipped: %+v", byTier["wsl"])
}
}
func TestWorkerNumberOmitemptyWithoutConfig(t *testing.T) {
os.Unsetenv("AETHERFORGE_WORKER_NUMBER")
cfg := config.RuntimeConfig{BuiltinConfig: config.BuiltinConfig{WorkerName: "w"}}
c := &AgentClient{cfg: cfg, agentID: "a1", reporter: newTestClient(t).reporter, pool: newTestClient(t).pool}
snap := c.buildAISnapshot(0)
raw, _ := json.Marshal(snap)
if string(raw) == "" {
t.Fatal("empty json")
}
var m map[string]interface{}
_ = json.Unmarshal(raw, &m)
if _, ok := m["worker_number"]; ok {
t.Fatalf("worker_number should be omitted, got %v", m["worker_number"])
}
}