diff --git a/server/internal/api/fleet_ai_handler_test.go b/server/internal/api/fleet_ai_handler_test.go new file mode 100644 index 0000000..face8f0 --- /dev/null +++ b/server/internal/api/fleet_ai_handler_test.go @@ -0,0 +1,80 @@ +package api + +import ( + "encoding/json" + "net/http" + "net/http/httptest" + "strings" + "testing" + + fleetai "crypto-miner-server/internal/ai" + "crypto-miner-server/internal/db" +) + +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") + + 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 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") + } +}