Pulse/internal/api/ai_handler_test.go
rcourtman a6a8efaa65 test: Add comprehensive test coverage across packages
New test files with expanded coverage:

API tests:
- ai_handler_test.go: AI handler unit tests with mocking
- agent_profiles_tools_test.go: Profile management tests
- alerts_endpoints_test.go: Alert API endpoint tests
- alerts_test.go: Updated for interface changes
- audit_handlers_test.go: Audit handler tests
- frontend_embed_test.go: Frontend embedding tests
- metadata_handlers_test.go, metadata_provider_test.go: Metadata tests
- notifications_test.go: Updated for interface changes
- profile_suggestions_test.go: Profile suggestion tests
- saml_service_test.go: SAML authentication tests
- sensor_proxy_gate_test.go: Sensor proxy tests
- updates_test.go: Updated for interface changes

Agent tests:
- dockeragent/signature_test.go: Docker agent signature tests
- hostagent/agent_metrics_test.go: Host agent metrics tests
- hostagent/commands_test.go: Command execution tests
- hostagent/network_helpers_test.go: Network helper tests
- hostagent/proxmox_setup_test.go: Updated setup tests
- kubernetesagent/*_test.go: Kubernetes agent tests

Core package tests:
- monitoring/kubernetes_agents_test.go, reload_test.go
- remoteconfig/client_test.go, signature_test.go
- sensors/collector_test.go
- updates/adapter_installsh_*_test.go: Install adapter tests
- updates/manager_*_test.go: Update manager tests
- websocket/hub_*_test.go: WebSocket hub tests

Library tests:
- pkg/audit/export_test.go: Audit export tests
- pkg/metrics/store_test.go: Metrics store tests
- pkg/proxmox/*_test.go: Proxmox client tests
- pkg/reporting/reporting_test.go: Reporting tests
- pkg/server/*_test.go: Server tests
- pkg/tlsutil/extra_test.go: TLS utility tests

Total: ~8000 lines of new test code
2026-01-19 19:26:18 +00:00

799 lines
24 KiB
Go

package api
import (
"context"
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/rcourtman/pulse-go-rewrite/internal/ai/chat"
"github.com/rcourtman/pulse-go-rewrite/internal/config"
"github.com/rcourtman/pulse-go-rewrite/internal/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
type MockAIService struct {
mock.Mock
}
func (m *MockAIService) Start(ctx context.Context) error {
args := m.Called(ctx)
return args.Error(0)
}
func (m *MockAIService) Stop(ctx context.Context) error {
args := m.Called(ctx)
return args.Error(0)
}
func (m *MockAIService) Restart(ctx context.Context, newCfg *config.AIConfig) error {
args := m.Called(ctx, newCfg)
return args.Error(0)
}
func (m *MockAIService) IsRunning() bool {
args := m.Called()
return args.Bool(0)
}
func (m *MockAIService) Execute(ctx context.Context, req chat.ExecuteRequest) (map[string]interface{}, error) {
args := m.Called(ctx, req)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(map[string]interface{}), args.Error(1)
}
func (m *MockAIService) ExecuteStream(ctx context.Context, req chat.ExecuteRequest, callback chat.StreamCallback) error {
args := m.Called(ctx, req, callback)
return args.Error(0)
}
func (m *MockAIService) ListSessions(ctx context.Context) ([]chat.Session, error) {
args := m.Called(ctx)
return args.Get(0).([]chat.Session), args.Error(1)
}
func (m *MockAIService) CreateSession(ctx context.Context) (*chat.Session, error) {
args := m.Called(ctx)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*chat.Session), args.Error(1)
}
func (m *MockAIService) DeleteSession(ctx context.Context, sessionID string) error {
args := m.Called(ctx, sessionID)
return args.Error(0)
}
func (m *MockAIService) GetMessages(ctx context.Context, sessionID string) ([]chat.Message, error) {
args := m.Called(ctx, sessionID)
return args.Get(0).([]chat.Message), args.Error(1)
}
func (m *MockAIService) AbortSession(ctx context.Context, sessionID string) error {
args := m.Called(ctx, sessionID)
return args.Error(0)
}
func (m *MockAIService) SummarizeSession(ctx context.Context, sessionID string) (map[string]interface{}, error) {
args := m.Called(ctx, sessionID)
return args.Get(0).(map[string]interface{}), args.Error(1)
}
func (m *MockAIService) GetSessionDiff(ctx context.Context, sessionID string) (map[string]interface{}, error) {
args := m.Called(ctx, sessionID)
return args.Get(0).(map[string]interface{}), args.Error(1)
}
func (m *MockAIService) ForkSession(ctx context.Context, sessionID string) (*chat.Session, error) {
args := m.Called(ctx, sessionID)
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*chat.Session), args.Error(1)
}
func (m *MockAIService) RevertSession(ctx context.Context, sessionID string) (map[string]interface{}, error) {
args := m.Called(ctx, sessionID)
return args.Get(0).(map[string]interface{}), args.Error(1)
}
func (m *MockAIService) UnrevertSession(ctx context.Context, sessionID string) (map[string]interface{}, error) {
args := m.Called(ctx, sessionID)
return args.Get(0).(map[string]interface{}), args.Error(1)
}
func (m *MockAIService) AnswerQuestion(ctx context.Context, questionID string, answers []chat.QuestionAnswer) error {
args := m.Called(ctx, questionID, answers)
return args.Error(0)
}
func (m *MockAIService) SetAlertProvider(provider chat.MCPAlertProvider) { m.Called(provider) }
func (m *MockAIService) SetFindingsProvider(provider chat.MCPFindingsProvider) { m.Called(provider) }
func (m *MockAIService) SetBaselineProvider(provider chat.MCPBaselineProvider) { m.Called(provider) }
func (m *MockAIService) SetPatternProvider(provider chat.MCPPatternProvider) { m.Called(provider) }
func (m *MockAIService) SetMetricsHistory(provider chat.MCPMetricsHistoryProvider) {
m.Called(provider)
}
func (m *MockAIService) SetBackupProvider(provider chat.MCPBackupProvider) { m.Called(provider) }
func (m *MockAIService) SetStorageProvider(provider chat.MCPStorageProvider) { m.Called(provider) }
func (m *MockAIService) SetDiskHealthProvider(provider chat.MCPDiskHealthProvider) {
m.Called(provider)
}
func (m *MockAIService) SetUpdatesProvider(provider chat.MCPUpdatesProvider) { m.Called(provider) }
func (m *MockAIService) SetAgentProfileManager(manager chat.AgentProfileManager) {
m.Called(manager)
}
func (m *MockAIService) SetFindingsManager(manager chat.FindingsManager) { m.Called(manager) }
func (m *MockAIService) SetMetadataUpdater(updater chat.MetadataUpdater) { m.Called(updater) }
func (m *MockAIService) UpdateControlSettings(cfg *config.AIConfig) { m.Called(cfg) }
func (m *MockAIService) GetBaseURL() string {
args := m.Called()
return args.String(0)
}
type MockAIPersistence struct {
mock.Mock
}
func (m *MockAIPersistence) LoadAIConfig() (*config.AIConfig, error) {
args := m.Called()
if args.Get(0) == nil {
return nil, args.Error(1)
}
return args.Get(0).(*config.AIConfig), args.Error(1)
}
type MockAIStateProvider struct {
mock.Mock
}
func (m *MockAIStateProvider) GetState() models.StateSnapshot {
args := m.Called()
return args.Get(0).(models.StateSnapshot)
}
func TestStart(t *testing.T) {
// Mock newChatService
oldNewService := newChatService
defer func() { newChatService = oldNewService }()
mockSvc := new(MockAIService)
newChatService = func(cfg chat.Config) AIService {
return mockSvc
}
mockPersist := new(MockAIPersistence)
h := NewAIHandler(&config.Config{}, mockPersist, nil)
// AI disabled in config
mockPersist.On("LoadAIConfig").Return(&config.AIConfig{Enabled: false}, nil).Once()
err := h.Start(context.Background(), nil)
assert.NoError(t, err)
assert.Nil(t, h.service)
// AI enabled
aiCfg := &config.AIConfig{Enabled: true, Model: "test"}
mockPersist.On("LoadAIConfig").Return(aiCfg, nil).Once()
mockSvc.On("Start", mock.Anything).Return(nil).Once()
err = h.Start(context.Background(), nil)
assert.NoError(t, err)
assert.Equal(t, mockSvc, h.service)
}
func TestStop(t *testing.T) {
mockSvc := new(MockAIService)
h := NewAIHandler(nil, nil, nil)
h.service = mockSvc
mockSvc.On("Stop", mock.Anything).Return(nil)
err := h.Stop(context.Background())
assert.NoError(t, err)
// Nil service
h.service = nil
err = h.Stop(context.Background())
assert.NoError(t, err)
}
func TestStart_Error(t *testing.T) {
oldNewService := newChatService
defer func() { newChatService = oldNewService }()
mockSvc := new(MockAIService)
newChatService = func(cfg chat.Config) AIService {
return mockSvc
}
mockPersist := new(MockAIPersistence)
h := NewAIHandler(&config.Config{}, mockPersist, nil)
aiCfg := &config.AIConfig{Enabled: true, Model: "test"}
mockPersist.On("LoadAIConfig").Return(aiCfg, nil)
mockSvc.On("Start", mock.Anything).Return(assert.AnError)
err := h.Start(context.Background(), nil)
assert.Error(t, err)
}
func TestRestart(t *testing.T) {
mockPersist := new(MockAIPersistence)
mockSvc := new(MockAIService)
h := NewAIHandler(nil, mockPersist, nil)
h.service = mockSvc
aiCfg := &config.AIConfig{}
mockPersist.On("LoadAIConfig").Return(aiCfg, nil)
mockSvc.On("IsRunning").Return(true)
mockSvc.On("Restart", mock.Anything, aiCfg).Return(nil)
err := h.Restart(context.Background())
assert.NoError(t, err)
// Service nil
h.service = nil
err = h.Restart(context.Background())
assert.NoError(t, err)
}
func TestGetService(t *testing.T) {
mockSvc := new(MockAIService)
h := NewAIHandler(nil, nil, nil)
h.service = mockSvc
assert.Equal(t, mockSvc, h.GetService())
}
func TestGetAIConfig(t *testing.T) {
mockPersist := new(MockAIPersistence)
h := NewAIHandler(nil, mockPersist, nil)
aiCfg := &config.AIConfig{Model: "test"}
mockPersist.On("LoadAIConfig").Return(aiCfg, nil)
result := h.GetAIConfig()
assert.Equal(t, aiCfg, result)
}
func TestLoadAIConfig_Error(t *testing.T) {
mockPersist := new(MockAIPersistence)
h := NewAIHandler(nil, mockPersist, nil)
mockPersist.On("LoadAIConfig").Return((*config.AIConfig)(nil), assert.AnError)
result := h.loadAIConfig()
assert.Nil(t, result)
}
func TestHandleStatus(t *testing.T) {
cfg := &config.Config{
APIToken: "test-token",
}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
req := httptest.NewRequest("GET", "/api/ai/status", nil)
w := httptest.NewRecorder()
h.HandleStatus(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
err := json.NewDecoder(w.Body).Decode(&resp)
assert.NoError(t, err)
assert.True(t, resp["running"].(bool))
assert.Equal(t, "direct", resp["engine"])
}
func TestHandleSessions(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
sessions := []chat.Session{{ID: "s1"}, {ID: "s2"}}
mockSvc.On("ListSessions", mock.Anything).Return(sessions, nil)
req := httptest.NewRequest("GET", "/api/ai/sessions", nil)
w := httptest.NewRecorder()
h.HandleSessions(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleCreateSession(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
session := &chat.Session{ID: "new-session"}
mockSvc.On("CreateSession", mock.Anything).Return(session, nil)
req := httptest.NewRequest("POST", "/api/ai/sessions", nil)
w := httptest.NewRecorder()
h.HandleCreateSession(w, req)
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleDeleteSession(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("DeleteSession", mock.Anything, "s1").Return(nil)
req := httptest.NewRequest("DELETE", "/api/ai/sessions/s1", nil)
w := httptest.NewRecorder()
h.HandleDeleteSession(w, req, "s1")
assert.Equal(t, http.StatusNoContent, w.Code)
}
func TestHandleMessages(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
messages := []chat.Message{{Role: "user", Content: "hello"}}
mockSvc.On("GetMessages", mock.Anything, "s1").Return(messages, nil)
req := httptest.NewRequest("GET", "/api/ai/sessions/s1/messages", nil)
w := httptest.NewRecorder()
h.HandleMessages(w, req, "s1")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleChat_NotRunning(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(false)
req := httptest.NewRequest("POST", "/api/ai/chat", nil)
w := httptest.NewRecorder()
h.HandleChat(w, req)
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
}
func TestHandleChat_InvalidJSON(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
req := httptest.NewRequest("POST", "/api/ai/chat", strings.NewReader("invalid"))
w := httptest.NewRecorder()
h.HandleChat(w, req)
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestHandleChat_Success(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
// Mock ExecuteStream to just return nil
mockSvc.On("ExecuteStream", mock.Anything, mock.Anything, mock.Anything).Return(nil).Run(func(args mock.Arguments) {
callback := args.Get(2).(chat.StreamCallback)
data, _ := json.Marshal("hello")
callback(chat.StreamEvent{Type: "content", Data: data})
})
body := `{"prompt": "hi"}`
req := httptest.NewRequest("POST", "/api/ai/chat", strings.NewReader(body))
w := httptest.NewRecorder()
h.HandleChat(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Contains(t, w.Header().Get("Content-Type"), "text/event-stream")
}
func TestHandleAnswerQuestion(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("AnswerQuestion", mock.Anything, "q1", mock.Anything).Return(nil)
body := `{"answers": [{"id": "a1", "value": "v1"}]}`
req := httptest.NewRequest("POST", "/api/ai/question/q1/answer", strings.NewReader(body))
w := httptest.NewRecorder()
h.HandleAnswerQuestion(w, req, "q1")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleSessions_NotRunning(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(false)
req := httptest.NewRequest("GET", "/api/ai/sessions", nil)
w := httptest.NewRecorder()
h.HandleSessions(w, req)
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
}
func TestHandleSessions_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("ListSessions", mock.Anything).Return(([]chat.Session)(nil), assert.AnError)
req := httptest.NewRequest("GET", "/api/ai/sessions", nil)
w := httptest.NewRecorder()
h.HandleSessions(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleCreateSession_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("CreateSession", mock.Anything).Return((*chat.Session)(nil), assert.AnError)
req := httptest.NewRequest("POST", "/api/ai/sessions", nil)
w := httptest.NewRecorder()
h.HandleCreateSession(w, req)
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleDeleteSession_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("DeleteSession", mock.Anything, "s1").Return(assert.AnError)
req := httptest.NewRequest("DELETE", "/api/ai/sessions/s1", nil)
w := httptest.NewRecorder()
h.HandleDeleteSession(w, req, "s1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleMessages_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("GetMessages", mock.Anything, "s1").Return(([]chat.Message)(nil), assert.AnError)
req := httptest.NewRequest("GET", "/api/ai/sessions/s1/messages", nil)
w := httptest.NewRecorder()
h.HandleMessages(w, req, "s1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleAbort_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("AbortSession", mock.Anything, "s1").Return(assert.AnError)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/abort", nil)
w := httptest.NewRecorder()
h.HandleAbort(w, req, "s1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleSummarize_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("SummarizeSession", mock.Anything, "s1").Return((map[string]interface{})(nil), assert.AnError)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/summarize", nil)
w := httptest.NewRecorder()
h.HandleSummarize(w, req, "s1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleAnswerQuestion_InvalidJSON(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
req := httptest.NewRequest("POST", "/api/ai/question/q1/answer", strings.NewReader("invalid"))
w := httptest.NewRecorder()
h.HandleAnswerQuestion(w, req, "q1")
assert.Equal(t, http.StatusBadRequest, w.Code)
}
func TestHandleAnswerQuestion_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("AnswerQuestion", mock.Anything, "q1", mock.Anything).Return(assert.AnError)
body := `{"answers": []}`
req := httptest.NewRequest("POST", "/api/ai/question/q1/answer", strings.NewReader(body))
w := httptest.NewRecorder()
h.HandleAnswerQuestion(w, req, "q1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleChat_Options(t *testing.T) {
h := NewAIHandler(nil, nil, nil)
req := httptest.NewRequest("OPTIONS", "/api/ai/chat", nil)
req.Header.Set("Origin", "http://example.com")
w := httptest.NewRecorder()
h.HandleChat(w, req)
assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "http://example.com", w.Header().Get("Access-Control-Allow-Origin"))
}
func TestHandleChat_MethodNotAllowed(t *testing.T) {
h := NewAIHandler(nil, nil, nil)
req := httptest.NewRequest("GET", "/api/ai/chat", nil)
w := httptest.NewRecorder()
h.HandleChat(w, req)
assert.Equal(t, http.StatusMethodNotAllowed, w.Code)
}
func TestHandleChat_Error(t *testing.T) {
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("ExecuteStream", mock.Anything, mock.Anything, mock.Anything).Return(assert.AnError)
body := `{"prompt": "hi"}`
req := httptest.NewRequest("POST", "/api/ai/chat", strings.NewReader(body))
w := httptest.NewRecorder()
h.HandleChat(w, req)
// ExecuteStream error happens after headers are sent, so w.Code might be 200
// but the error is returned.
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleDiff_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("GetSessionDiff", mock.Anything, "s1").Return((map[string]interface{})(nil), assert.AnError)
req := httptest.NewRequest("GET", "/api/ai/sessions/s1/diff", nil)
w := httptest.NewRecorder()
h.HandleDiff(w, req, "s1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleFork_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("ForkSession", mock.Anything, "s1").Return((*chat.Session)(nil), assert.AnError)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/fork", nil)
w := httptest.NewRecorder()
h.HandleFork(w, req, "s1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleRevert_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("RevertSession", mock.Anything, "s1").Return((map[string]interface{})(nil), assert.AnError)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/revert", nil)
w := httptest.NewRecorder()
h.HandleRevert(w, req, "s1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleUnrevert_Error(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("UnrevertSession", mock.Anything, "s1").Return((map[string]interface{})(nil), assert.AnError)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/unrevert", nil)
w := httptest.NewRecorder()
h.HandleUnrevert(w, req, "s1")
assert.Equal(t, http.StatusInternalServerError, w.Code)
}
func TestHandleStatus_NotRunning(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(false)
req := httptest.NewRequest("GET", "/api/ai/status", nil)
w := httptest.NewRecorder()
h.HandleStatus(w, req)
assert.Equal(t, http.StatusOK, w.Code) // HandleStatus returns 200 even if not running
var resp map[string]interface{}
json.NewDecoder(w.Body).Decode(&resp)
assert.False(t, resp["running"].(bool))
}
func TestMockUnimplemented(t *testing.T) {
mockSvc := new(MockAIService)
mockSvc.On("SetFindingsManager", mock.Anything).Return()
mockSvc.On("SetMetadataUpdater", mock.Anything).Return()
mockSvc.On("UpdateControlSettings", mock.Anything).Return()
h := NewAIHandler(nil, nil, nil)
h.service = mockSvc
h.SetFindingsManager(nil)
h.SetMetadataUpdater(nil)
h.UpdateControlSettings(nil)
mockSvc.AssertExpectations(t)
}
func TestProviders(t *testing.T) {
h := NewAIHandler(nil, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("SetAlertProvider", mock.Anything).Return()
mockSvc.On("SetFindingsProvider", mock.Anything).Return()
mockSvc.On("SetBaselineProvider", mock.Anything).Return()
mockSvc.On("SetPatternProvider", mock.Anything).Return()
mockSvc.On("SetMetricsHistory", mock.Anything).Return()
mockSvc.On("SetAgentProfileManager", mock.Anything).Return()
mockSvc.On("SetStorageProvider", mock.Anything).Return()
mockSvc.On("SetBackupProvider", mock.Anything).Return()
mockSvc.On("SetDiskHealthProvider", mock.Anything).Return()
mockSvc.On("SetUpdatesProvider", mock.Anything).Return()
h.SetAlertProvider(nil)
h.SetFindingsProvider(nil)
h.SetBaselineProvider(nil)
h.SetPatternProvider(nil)
h.SetMetricsHistory(nil)
h.SetAgentProfileManager(nil)
h.SetStorageProvider(nil)
h.SetBackupProvider(nil)
h.SetDiskHealthProvider(nil)
h.SetUpdatesProvider(nil)
mockSvc.AssertExpectations(t)
}
func TestHandleAbort_Success(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("AbortSession", mock.Anything, "s1").Return(nil)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/abort", nil)
w := httptest.NewRecorder()
h.HandleAbort(w, req, "s1")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleSummarize_Success(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("SummarizeSession", mock.Anything, "s1").Return(map[string]interface{}{"summary": "ok"}, nil)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/summarize", nil)
w := httptest.NewRecorder()
h.HandleSummarize(w, req, "s1")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleDiff_Success(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("GetSessionDiff", mock.Anything, "s1").Return(map[string]interface{}{"diff": "test"}, nil)
req := httptest.NewRequest("GET", "/api/ai/sessions/s1/diff", nil)
w := httptest.NewRecorder()
h.HandleDiff(w, req, "s1")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleFork_Success(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("ForkSession", mock.Anything, "s1").Return(&chat.Session{ID: "s2"}, nil)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/fork", nil)
w := httptest.NewRecorder()
h.HandleFork(w, req, "s1")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleRevert_Success(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("RevertSession", mock.Anything, "s1").Return(map[string]interface{}{"reverted": true}, nil)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/revert", nil)
w := httptest.NewRecorder()
h.HandleRevert(w, req, "s1")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleUnrevert_Success(t *testing.T) {
h := NewAIHandler(&config.Config{}, nil, nil)
mockSvc := new(MockAIService)
h.service = mockSvc
mockSvc.On("IsRunning").Return(true)
mockSvc.On("UnrevertSession", mock.Anything, "s1").Return(map[string]interface{}{"unreverted": true}, nil)
req := httptest.NewRequest("POST", "/api/ai/sessions/s1/unrevert", nil)
w := httptest.NewRecorder()
h.HandleUnrevert(w, req, "s1")
assert.Equal(t, http.StatusOK, w.Code)
}
func TestHandleStatus_NoService(t *testing.T) {
// HandleStatus with no service initialized should still return 200 with running=false
cfg := &config.Config{}
h := NewAIHandler(cfg, nil, nil)
req := httptest.NewRequest("GET", "/api/ai/status", nil)
w := httptest.NewRecorder()
h.HandleStatus(w, req)
assert.Equal(t, http.StatusOK, w.Code)
var resp map[string]interface{}
json.NewDecoder(w.Body).Decode(&resp)
assert.False(t, resp["running"].(bool))
}