Files
AetherForge/server/internal/api/fleet_ai_handler_test.go
AetherForge 4b94776432
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Fix Fleet AI and LOTL test regressions after parallel merges.
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.
2026-06-07 02:38:27 -07:00

137 lines
3.9 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
fleetai "crypto-miner-server/internal/ai"
"crypto-miner-server/internal/db"
"crypto-miner-server/internal/models"
"crypto-miner-server/internal/strategy"
)
type stubFleetAIConfig struct {
view FleetAIConfigView
}
func (s *stubFleetAIConfig) GetFleetAIConfig() FleetAIConfigView { return s.view }
func (s *stubFleetAIConfig) UpdateFleetAIConfig(v FleetAIConfigView) error {
s.view = v
return nil
}
func TestFleetAIHandlerGetPutConfig(t *testing.T) {
cfg := &stubFleetAIConfig{view: FleetAIConfigView{
AIControlEnabled: false, AIEndpoint: "http://127.0.0.1:11434/v1", AIDecisionIntervalSec: 60,
}}
h := NewFleetAIHandler(cfg, nil)
req := httptest.NewRequest(http.MethodGet, "/api/v1/ai/config", nil)
rec := httptest.NewRecorder()
h.GetConfig(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
body := `{"ai_control_enabled":true,"ai_endpoint":"http://127.0.0.1:11434/v1","ai_model":"llama3.2","ai_no_context":true,"ai_decision_interval_sec":45}`
req = httptest.NewRequest(http.MethodPut, "/api/v1/ai/config", strings.NewReader(body))
rec = httptest.NewRecorder()
h.PutConfig(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("put status %d body %s", rec.Code, rec.Body.String())
}
if !cfg.view.AIControlEnabled || cfg.view.AIModel != "llama3.2" {
t.Fatalf("config not updated: %+v", cfg.view)
}
}
func TestFleetAIHandlerGetDecisions(t *testing.T) {
database, err := db.New(t.TempDir())
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { database.Close() })
_ = database.InsertAIDecision("agent-x", "abc", `{"commands":[]}`, "noop:ok", false, "", "", "")
h := NewFleetAIHandler(nil, database)
req := httptest.NewRequest(http.MethodGet, "/api/v1/ai/decisions?agent_id=agent-x", nil)
rec := httptest.NewRecorder()
h.GetDecisions(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d", rec.Code)
}
var rows []map[string]interface{}
if err := json.Unmarshal(rec.Body.Bytes(), &rows); err != nil {
t.Fatal(err)
}
if len(rows) != 1 {
t.Fatalf("rows: %v", rows)
}
}
func TestFleetAIHandlerGetModels(t *testing.T) {
modelSrv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/models" {
http.NotFound(w, r)
return
}
_ = json.NewEncoder(w).Encode(map[string]interface{}{
"data": []map[string]string{{"id": "llama3.2"}},
})
}))
t.Cleanup(modelSrv.Close)
cfg := &stubFleetAIConfig{view: FleetAIConfigView{AIEndpoint: modelSrv.URL + "/v1"}}
h := NewFleetAIHandler(cfg, nil)
req := httptest.NewRequest(http.MethodGet, "/api/v1/ai/models", nil)
rec := httptest.NewRecorder()
h.GetModels(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status %d body %s", rec.Code, rec.Body.String())
}
var body map[string]interface{}
if err := json.Unmarshal(rec.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
models, _ := body["models"].([]interface{})
if len(models) != 1 {
t.Fatalf("models: %v", body)
}
}
func TestFleetAISnapshotOmitsAdaptiveWhenAIControl(t *testing.T) {
database, err := db.New(t.TempDir())
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { database.Close() })
hub := NewWSHub(database)
hub.SetAdaptiveEngine(strategy.NewAdaptiveEngine(database, true))
hub.SetServerPolicy(ServerPolicy{AIControlEnabled: true})
agentID := "snap-agent-1"
_ = database.UpsertAgent(&models.Agent{
ID: agentID, Name: "node-a", Platform: "windows", Status: "online",
})
snap, ok := hub.FleetAISnapshot(agentID)
if !ok {
t.Fatal("snapshot not found")
}
if snap.Adaptive != nil {
t.Fatalf("adaptive must be nil when AI control enabled, got %+v", snap.Adaptive)
}
}
func TestFleetAIExecutorRestartMining(t *testing.T) {
hub := NewWSHub(nil)
exec := &FleetAIExecutor{Hub: hub}
_, err := exec.Execute("missing", fleetai.Command{Type: fleetai.CmdRestartMining})
if err == nil {
t.Fatal("expected error for disconnected agent")
}
}