Files
Pulse/internal/api/ai_intelligence_handlers_more_test.go
rcourtman bfa648ddd5 Test: expand api feature test coverage
Add tests for AI intelligence, Docker/K8s agents, log redaction, and general router helper functions.
2026-02-02 22:02:22 +00:00

213 lines
6.3 KiB
Go

package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/ai"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/learning"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/unified"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
)
type snapshotStateProvider struct {
state models.StateSnapshot
}
func (s snapshotStateProvider) GetState() models.StateSnapshot {
return s.state
}
func buildBaselineStore(t *testing.T) *ai.BaselineStore {
t.Helper()
store := ai.NewBaselineStore(ai.BaselineConfig{MinSamples: 1})
points := []ai.BaselineMetricPoint{{Value: 10, Timestamp: time.Now()}}
if err := store.Learn("vm-1", "vm", "cpu", points); err != nil {
t.Fatalf("baseline Learn error: %v", err)
}
return store
}
func TestHandleGetRecentChanges_WithDetector(t *testing.T) {
svc := newEnabledAIService(t)
detector := ai.NewChangeDetector(ai.ChangeDetectorConfig{MaxChanges: 10})
change := ai.Change{
ID: "change-1",
ResourceID: "vm-1",
ResourceName: "vm-one",
ResourceType: "vm",
ChangeType: ai.ChangeConfig,
Before: "old",
After: "new",
DetectedAt: time.Now().Add(-30 * time.Minute),
Description: "updated config",
}
setUnexportedField(t, detector, "changes", []ai.Change{change})
svc.SetChangeDetector(detector)
handler := &AISettingsHandler{legacyAIService: svc}
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/changes?hours=1", nil)
rec := httptest.NewRecorder()
handler.HandleGetRecentChanges(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["count"] != float64(1) {
t.Fatalf("expected count 1, got %#v", payload["count"])
}
changes, _ := payload["changes"].([]interface{})
if len(changes) != 1 {
t.Fatalf("expected 1 change, got %d", len(changes))
}
}
func TestHandleGetBaselines_WithStore(t *testing.T) {
svc := newEnabledAIService(t)
store := buildBaselineStore(t)
svc.SetBaselineStore(store)
handler := &AISettingsHandler{legacyAIService: svc}
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/baselines?resource_id=vm-1", nil)
rec := httptest.NewRecorder()
handler.HandleGetBaselines(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["count"] != float64(1) {
t.Fatalf("expected count 1, got %#v", payload["count"])
}
}
func TestHandleGetLearningStatus_WithBaselines(t *testing.T) {
svc := newEnabledAIService(t)
store := buildBaselineStore(t)
svc.SetBaselineStore(store)
handler := &AISettingsHandler{legacyAIService: svc}
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/learning", nil)
rec := httptest.NewRecorder()
handler.HandleGetLearningStatus(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["resources_baselined"] != float64(1) {
t.Fatalf("expected resources_baselined 1, got %#v", payload["resources_baselined"])
}
if payload["status"] != "learning" {
t.Fatalf("expected status learning, got %#v", payload["status"])
}
}
func TestHandleGetAnomalies_WithBaseline(t *testing.T) {
svc := newEnabledAIService(t)
store := buildBaselineStore(t)
svc.SetBaselineStore(store)
state := models.StateSnapshot{
VMs: []models.VM{{
ID: "vm-1",
Name: "vm-one",
Status: "running",
CPU: 0.8,
Memory: models.Memory{Usage: 50},
}},
}
svc.SetStateProvider(snapshotStateProvider{state: state})
handler := &AISettingsHandler{legacyAIService: svc}
req := httptest.NewRequest(http.MethodGet, "/api/ai/intelligence/anomalies?resource_id=vm-1", nil)
rec := httptest.NewRecorder()
handler.HandleGetAnomalies(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["count"] == float64(0) {
t.Fatalf("expected anomalies to be returned")
}
}
func TestHandleGetLearningPreferences_WithStore(t *testing.T) {
store := learning.NewLearningStore(learning.LearningStoreConfig{})
handler := &AISettingsHandler{}
handler.SetLearningStore(store)
req := httptest.NewRequest(http.MethodGet, "/api/ai/learning/preferences?resource_id=vm-1", nil)
rec := httptest.NewRecorder()
handler.HandleGetLearningPreferences(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["resource_id"] != "vm-1" {
t.Fatalf("expected resource_id in response, got %#v", payload["resource_id"])
}
}
func TestHandleGetUnifiedFindings_WithStore(t *testing.T) {
store := unified.NewUnifiedStore(unified.DefaultAlertToFindingConfig())
store.AddFromAI(&unified.UnifiedFinding{
ID: "finding-1",
Source: unified.SourceAIPatrol,
Severity: unified.SeverityCritical,
Category: unified.CategoryPerformance,
ResourceID: "vm-1",
ResourceName: "vm-one",
ResourceType: "vm",
Title: "CPU high",
Description: "cpu usage high",
DetectedAt: time.Now(),
LastSeenAt: time.Now(),
})
handler := &AISettingsHandler{}
handler.SetUnifiedStore(store)
req := httptest.NewRequest(http.MethodGet, "/api/ai/unified/findings?resource_id=vm-1", nil)
rec := httptest.NewRecorder()
handler.HandleGetUnifiedFindings(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want %d", rec.Code, http.StatusOK)
}
var payload map[string]interface{}
if err := json.NewDecoder(rec.Body).Decode(&payload); err != nil {
t.Fatalf("decode response: %v", err)
}
if payload["count"] == float64(0) {
t.Fatalf("expected findings in response")
}
}