mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 11:30:15 +00:00
191 lines
5.9 KiB
Go
191 lines
5.9 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai/chat"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai/circuit"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai/forecast"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai/learning"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai/proxmox"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/ai/remediation"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
)
|
|
|
|
type stubStateProvider struct{}
|
|
|
|
func (s *stubStateProvider) GetState() models.StateSnapshot {
|
|
return models.StateSnapshot{}
|
|
}
|
|
|
|
type fakeChatWrapper struct {
|
|
*chat.Service
|
|
}
|
|
|
|
func newTestAISettingsHandlerLite() *AISettingsHandler {
|
|
return &AISettingsHandler{
|
|
legacyAIService: ai.NewService(nil, nil),
|
|
aiServices: make(map[string]*ai.Service),
|
|
}
|
|
}
|
|
|
|
func TestIsMCPToolCall(t *testing.T) {
|
|
handler := &AISettingsHandler{}
|
|
if !handler.isMCPToolCall("pulse_control_guest(guest_id='102')") {
|
|
t.Fatalf("expected MCP tool call to be detected")
|
|
}
|
|
if !handler.isMCPToolCall("default_api:pulse_get_resource(id='1')") {
|
|
t.Fatalf("expected MCP tool call with default_api prefix")
|
|
}
|
|
if handler.isMCPToolCall("echo hello") {
|
|
t.Fatalf("expected non-tool command to be false")
|
|
}
|
|
}
|
|
|
|
func TestCleanTargetHost(t *testing.T) {
|
|
handler := &AISettingsHandler{}
|
|
if got := handler.cleanTargetHost("delly (The container's host is 'delly')"); got != "delly" {
|
|
t.Fatalf("expected cleaned host, got %q", got)
|
|
}
|
|
if got := handler.cleanTargetHost("delly extra"); got != "delly" {
|
|
t.Fatalf("expected first token, got %q", got)
|
|
}
|
|
if got := handler.cleanTargetHost(" delly "); got != "delly" {
|
|
t.Fatalf("expected trimmed host, got %q", got)
|
|
}
|
|
if got := handler.cleanTargetHost(""); got != "" {
|
|
t.Fatalf("expected empty host")
|
|
}
|
|
}
|
|
|
|
func TestSplitToolArgs(t *testing.T) {
|
|
handler := &AISettingsHandler{}
|
|
args := "action='start', guest_id=\"102\", note='hello, world', path=\"/tmp/a,b\", escaped=\"\\\"quote\\\"\""
|
|
parts := handler.splitToolArgs(args)
|
|
expected := []string{
|
|
"action='start'",
|
|
"guest_id=\"102\"",
|
|
"note='hello, world'",
|
|
"path=\"/tmp/a,b\"",
|
|
"escaped=\"\\\"quote\\\"\"",
|
|
}
|
|
if len(parts) != len(expected) {
|
|
t.Fatalf("expected %d parts, got %d", len(expected), len(parts))
|
|
}
|
|
for i := range expected {
|
|
if strings.TrimSpace(parts[i]) != expected[i] {
|
|
t.Fatalf("expected part %q, got %q", expected[i], parts[i])
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestParseMCPToolCall(t *testing.T) {
|
|
handler := &AISettingsHandler{}
|
|
tool, args, err := handler.parseMCPToolCall("default_api:pulse_control_guest(guest_id=\"102\", action='start')")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if tool != "pulse_control_guest" {
|
|
t.Fatalf("expected tool name pulse_control_guest, got %q", tool)
|
|
}
|
|
if args["guest_id"] != "102" || args["action"] != "start" {
|
|
t.Fatalf("unexpected args: %#v", args)
|
|
}
|
|
|
|
tool, args, err = handler.parseMCPToolCall("pulse_run_command()")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error for empty args: %v", err)
|
|
}
|
|
if tool != "pulse_run_command" || len(args) != 0 {
|
|
t.Fatalf("expected empty args, got %#v", args)
|
|
}
|
|
|
|
if _, _, err = handler.parseMCPToolCall("pulse_control_guest"); err == nil {
|
|
t.Fatalf("expected error for missing parenthesis")
|
|
}
|
|
if _, _, err = handler.parseMCPToolCall("pulse_control_guest("); err == nil {
|
|
t.Fatalf("expected error for missing closing parenthesis")
|
|
}
|
|
}
|
|
|
|
func TestExecuteMCPToolFix_Errors(t *testing.T) {
|
|
handler := &AISettingsHandler{}
|
|
if _, _, err := handler.executeMCPToolFix(context.Background(), "pulse_control_guest()", ""); err == nil {
|
|
t.Fatalf("expected error when chat handler is missing")
|
|
}
|
|
|
|
handler.chatHandler = &AIHandler{}
|
|
if _, _, err := handler.executeMCPToolFix(context.Background(), "pulse_control_guest()", ""); err == nil {
|
|
t.Fatalf("expected error when chat service is missing")
|
|
}
|
|
|
|
handler.chatHandler.legacyService = &fakeChatWrapper{}
|
|
if _, _, err := handler.executeMCPToolFix(context.Background(), "pulse_control_guest()", ""); err == nil {
|
|
t.Fatalf("expected error for chat service type mismatch")
|
|
}
|
|
}
|
|
|
|
func TestAISettingsHandler_Setters(t *testing.T) {
|
|
handler := newTestAISettingsHandlerLite()
|
|
stateProvider := &stubStateProvider{}
|
|
handler.SetStateProvider(stateProvider)
|
|
if handler.GetStateProvider() != stateProvider {
|
|
t.Fatalf("expected state provider to be set")
|
|
}
|
|
|
|
breaker := &circuit.Breaker{}
|
|
handler.SetCircuitBreaker(breaker)
|
|
if handler.GetCircuitBreaker() != breaker {
|
|
t.Fatalf("expected circuit breaker to be set")
|
|
}
|
|
|
|
learningStore := &learning.LearningStore{}
|
|
handler.SetLearningStore(learningStore)
|
|
if handler.GetLearningStore() != learningStore {
|
|
t.Fatalf("expected learning store to be set")
|
|
}
|
|
|
|
forecastSvc := &forecast.Service{}
|
|
handler.SetForecastService(forecastSvc)
|
|
if handler.GetForecastService() != forecastSvc {
|
|
t.Fatalf("expected forecast service to be set")
|
|
}
|
|
|
|
correlator := &proxmox.EventCorrelator{}
|
|
handler.SetProxmoxCorrelator(correlator)
|
|
if handler.GetProxmoxCorrelator() != correlator {
|
|
t.Fatalf("expected correlator to be set")
|
|
}
|
|
|
|
engine := &remediation.Engine{}
|
|
handler.SetRemediationEngine(engine)
|
|
if handler.GetRemediationEngine() != engine {
|
|
t.Fatalf("expected remediation engine to be set")
|
|
}
|
|
}
|
|
|
|
func TestAISettingsHandler_RemoveTenantService(t *testing.T) {
|
|
handler := newTestAISettingsHandlerLite()
|
|
handler.aiServices["org-1"] = ai.NewService(nil, nil)
|
|
handler.aiServices["default"] = ai.NewService(nil, nil)
|
|
|
|
handler.RemoveTenantService("org-1")
|
|
if _, ok := handler.aiServices["org-1"]; ok {
|
|
t.Fatalf("expected org-1 to be removed")
|
|
}
|
|
|
|
handler.RemoveTenantService("default")
|
|
if _, ok := handler.aiServices["default"]; !ok {
|
|
t.Fatalf("expected default to remain")
|
|
}
|
|
}
|
|
|
|
func TestAISettingsHandler_IsAIEnabled(t *testing.T) {
|
|
handler := newTestAISettingsHandlerLite()
|
|
if handler.IsAIEnabled(context.Background()) {
|
|
t.Fatalf("expected AI to be disabled by default")
|
|
}
|
|
}
|