Files
pulse/internal/ai/eval/eval_test.go
T
rcourtman a04d41ce2c Add end-to-end evaluation framework for AI assistant testing
Implement comprehensive eval framework for testing Pulse Assistant:

Core components:
- Runner: Executes scenarios against live API with SSE stream parsing
- Assertions: Reusable checks (tool usage, content, duration, errors)
- Scenarios: Multi-step test workflows with configurable assertions

Basic scenarios:
- QuickSmokeTest: Minimal functionality verification
- ReadOnlyInfrastructure: List, logs, status operations
- RoutingValidation: Command routing to correct targets
- LogTailing: Bounded log commands complete properly
- Discovery: Infrastructure discovery capabilities

Advanced scenarios:
- TroubleshootingScenario: Multi-step investigation workflow
- DeepDiveScenario: Thorough single-service investigation
- ConfigInspectionScenario: Reading configuration files
- ResourceAnalysisScenario: Cross-container resource comparison
- MultiNodeScenario: Operations across Proxmox nodes
- DockerInDockerScenario: Docker containers inside LXCs
- ContextChainScenario: Context retention across turns

Usage: go test ./internal/ai/eval -live -run TestQuickSmokeTest
2026-01-28 16:49:24 +00:00

140 lines
3.5 KiB
Go

package eval
import (
"flag"
"os"
"testing"
)
var runLiveEval = flag.Bool("live", false, "Run live eval against Pulse API (requires running Pulse)")
func TestMain(m *testing.M) {
flag.Parse()
os.Exit(m.Run())
}
// TestQuickSmokeTest runs a minimal smoke test against the live API
// Run with: go test -v ./internal/ai/eval -run TestQuickSmokeTest -live
func TestQuickSmokeTest(t *testing.T) {
if !*runLiveEval {
t.Skip("Skipping live eval test. Use -live flag to run against live Pulse API")
}
runner := NewRunner(DefaultConfig())
scenario := QuickSmokeTest()
result := runner.RunScenario(scenario)
runner.PrintSummary(result)
if !result.Passed {
t.Fatalf("Scenario '%s' failed", scenario.Name)
}
}
// TestReadOnlyInfrastructure runs the full read-only infrastructure scenario
// Run with: go test -v ./internal/ai/eval -run TestReadOnlyInfrastructure -live
func TestReadOnlyInfrastructure(t *testing.T) {
if !*runLiveEval {
t.Skip("Skipping live eval test. Use -live flag to run against live Pulse API")
}
runner := NewRunner(DefaultConfig())
scenario := ReadOnlyInfrastructureScenario()
result := runner.RunScenario(scenario)
runner.PrintSummary(result)
if !result.Passed {
t.Fatalf("Scenario '%s' failed", scenario.Name)
}
}
// TestRoutingValidation runs the routing validation scenario
// Run with: go test -v ./internal/ai/eval -run TestRoutingValidation -live
func TestRoutingValidation(t *testing.T) {
if !*runLiveEval {
t.Skip("Skipping live eval test. Use -live flag to run against live Pulse API")
}
runner := NewRunner(DefaultConfig())
scenario := RoutingValidationScenario()
result := runner.RunScenario(scenario)
runner.PrintSummary(result)
if !result.Passed {
t.Fatalf("Scenario '%s' failed", scenario.Name)
}
}
// TestLogTailing runs the log tailing scenario
// Run with: go test -v ./internal/ai/eval -run TestLogTailing -live
func TestLogTailing(t *testing.T) {
if !*runLiveEval {
t.Skip("Skipping live eval test. Use -live flag to run against live Pulse API")
}
runner := NewRunner(DefaultConfig())
scenario := LogTailingScenario()
result := runner.RunScenario(scenario)
runner.PrintSummary(result)
if !result.Passed {
t.Fatalf("Scenario '%s' failed", scenario.Name)
}
}
// TestDiscovery runs the infrastructure discovery scenario
// Run with: go test -v ./internal/ai/eval -run TestDiscovery -live
func TestDiscovery(t *testing.T) {
if !*runLiveEval {
t.Skip("Skipping live eval test. Use -live flag to run against live Pulse API")
}
runner := NewRunner(DefaultConfig())
scenario := DiscoveryScenario()
result := runner.RunScenario(scenario)
runner.PrintSummary(result)
if !result.Passed {
t.Fatalf("Scenario '%s' failed", scenario.Name)
}
}
// TestAllScenarios runs all defined scenarios
// Run with: go test -v ./internal/ai/eval -run TestAllScenarios -live
func TestAllScenarios(t *testing.T) {
if !*runLiveEval {
t.Skip("Skipping live eval test. Use -live flag to run against live Pulse API")
}
runner := NewRunner(DefaultConfig())
scenarios := []Scenario{
QuickSmokeTest(),
ReadOnlyInfrastructureScenario(),
RoutingValidationScenario(),
LogTailingScenario(),
DiscoveryScenario(),
}
allPassed := true
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
result := runner.RunScenario(scenario)
runner.PrintSummary(result)
if !result.Passed {
allPassed = false
t.Errorf("Scenario '%s' failed", scenario.Name)
}
})
}
if !allPassed {
t.Fatal("One or more scenarios failed")
}
}