Pulse/internal/ai/intelligence_test.go
rcourtman 417a523d85 feat(ai): add unified Intelligence orchestrator
- Create Intelligence struct that aggregates all AI subsystems
- Add /api/ai/intelligence endpoint for system-wide and per-resource insights
- Wire Intelligence into PatrolService as a facade (not replacement)
- Add TypeScript types and API client for frontend
- Add unit tests for Intelligence orchestrator
- Fix pre-existing test failures using diagnostic commands instead of actionable ones

The Intelligence orchestrator provides:
- System-wide health scoring (A-F grades)
- Aggregated findings, predictions, correlations
- Per-resource context generation for AI prompts
- Learning progress tracking

This unifies access to AI subsystems without replacing existing code paths.
2025-12-21 10:32:02 +00:00

170 lines
4.5 KiB
Go

package ai
import (
"testing"
"time"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/patterns"
)
func TestNewIntelligence(t *testing.T) {
intel := NewIntelligence(IntelligenceConfig{DataDir: "/tmp/test"})
if intel == nil {
t.Fatal("Expected non-nil Intelligence")
}
}
func TestIntelligence_GetSummary_NoSubsystems(t *testing.T) {
intel := NewIntelligence(IntelligenceConfig{})
summary := intel.GetSummary()
if summary == nil {
t.Fatal("Expected non-nil summary")
}
// Should have default healthy state
if summary.OverallHealth.Score != 100 {
t.Errorf("Expected health score 100, got %f", summary.OverallHealth.Score)
}
if summary.OverallHealth.Grade != HealthGradeA {
t.Errorf("Expected grade A, got %s", summary.OverallHealth.Grade)
}
if summary.Timestamp.IsZero() {
t.Error("Expected non-zero timestamp")
}
}
func TestIntelligence_GetResourceIntelligence_NoSubsystems(t *testing.T) {
intel := NewIntelligence(IntelligenceConfig{})
resourceIntel := intel.GetResourceIntelligence("test-resource")
if resourceIntel == nil {
t.Fatal("Expected non-nil resource intelligence")
}
if resourceIntel.ResourceID != "test-resource" {
t.Errorf("Expected resource ID 'test-resource', got %s", resourceIntel.ResourceID)
}
// Should have default healthy state
if resourceIntel.Health.Score != 100 {
t.Errorf("Expected health score 100, got %f", resourceIntel.Health.Score)
}
}
func TestIntelligence_FormatContext_NoSubsystems(t *testing.T) {
intel := NewIntelligence(IntelligenceConfig{})
// With no subsystems, context should be empty
ctx := intel.FormatContext("test-resource")
if ctx != "" {
t.Errorf("Expected empty context with no subsystems, got: %s", ctx)
}
}
func TestIntelligence_CreatePredictionFinding(t *testing.T) {
intel := NewIntelligence(IntelligenceConfig{})
pred := patterns.FailurePrediction{
ResourceID: "vm-100",
EventType: patterns.EventHighCPU, // Use the constant instead of cast
DaysUntil: 0.5, // Less than 1 day
Confidence: 0.85, // High confidence
Basis: "Pattern detected",
}
finding := intel.CreatePredictionFinding(pred)
if finding == nil {
t.Fatal("Expected non-nil finding")
}
// High confidence + < 1 day should be critical
if finding.Severity != FindingSeverityCritical {
t.Errorf("Expected critical severity for imminent high-confidence prediction, got %s", finding.Severity)
}
if finding.ResourceID != "vm-100" {
t.Errorf("Expected resource ID 'vm-100', got %s", finding.ResourceID)
}
}
func TestIntelligence_SetSubsystems(t *testing.T) {
intel := NewIntelligence(IntelligenceConfig{})
// Create a findings store
findings := NewFindingsStore()
// Add a finding
findings.Add(&Finding{
ID: "test-finding",
Key: "test:finding",
Severity: FindingSeverityWarning,
Category: FindingCategoryPerformance,
ResourceID: "vm-100",
ResourceName: "test-vm",
ResourceType: "vm",
Title: "Test Finding",
DetectedAt: time.Now(),
LastSeenAt: time.Now(),
Source: "test",
})
// Set subsystems with just findings
intel.SetSubsystems(findings, nil, nil, nil, nil, nil, nil, nil)
// Get summary
summary := intel.GetSummary()
// Should have 1 warning
if summary.FindingsCount.Warning != 1 {
t.Errorf("Expected 1 warning, got %d", summary.FindingsCount.Warning)
}
if summary.FindingsCount.Total != 1 {
t.Errorf("Expected 1 total finding, got %d", summary.FindingsCount.Total)
}
// Health should be reduced due to warning
if summary.OverallHealth.Score >= 100 {
t.Error("Expected health score < 100 due to warning finding")
}
}
func TestIntelligence_HealthGrades(t *testing.T) {
tests := []struct {
score float64
grade HealthGrade
}{
{100, HealthGradeA},
{95, HealthGradeA},
{90, HealthGradeA},
{85, HealthGradeB},
{75, HealthGradeB},
{70, HealthGradeC},
{60, HealthGradeC},
{55, HealthGradeD},
{40, HealthGradeD},
{30, HealthGradeF},
{0, HealthGradeF},
}
for _, tt := range tests {
grade := scoreToGrade(tt.score)
if grade != tt.grade {
t.Errorf("scoreToGrade(%f) = %s, expected %s", tt.score, grade, tt.grade)
}
}
}
func TestIntelligence_CheckBaselinesForResource(t *testing.T) {
intel := NewIntelligence(IntelligenceConfig{})
// With no baseline store, should return nil
anomalies := intel.CheckBaselinesForResource("vm-100", map[string]float64{
"cpu": 85.0,
"memory": 90.0,
})
if anomalies != nil {
t.Error("Expected nil anomalies when baseline store not configured")
}
}