package main import ( "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "git.gobha.me/xcaliber/chat-switchboard/config" "git.gobha.me/xcaliber/chat-switchboard/handlers" "git.gobha.me/xcaliber/chat-switchboard/middleware" "git.gobha.me/xcaliber/chat-switchboard/providers" ) func init() { gin.SetMode(gin.TestMode) providers.Init() } func TestHealthEndpoint(t *testing.T) { r := gin.New() r.GET("/health", func(c *gin.Context) { c.JSON(200, gin.H{"status": "ok", "version": "test", "database": false}) }) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/health", nil) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected %d, got %d", http.StatusOK, w.Code) } } func TestCORSHeaders(t *testing.T) { r := gin.New() r.Use(middleware.CORS()) r.GET("/test", func(c *gin.Context) { c.Status(200) }) w := httptest.NewRecorder() req, _ := http.NewRequest("OPTIONS", "/test", nil) r.ServeHTTP(w, req) if w.Code != http.StatusNoContent { t.Errorf("Expected %d for OPTIONS, got %d", http.StatusNoContent, w.Code) } if w.Header().Get("Access-Control-Allow-Origin") != "*" { t.Error("Missing CORS header") } } func TestAllRoutesRegistered(t *testing.T) { cfg := &config.Config{JWTSecret: "test"} auth := handlers.NewAuthHandler(cfg) chats := handlers.NewChatHandler() msgs := handlers.NewMessageHandler() comp := handlers.NewCompletionHandler() apiCfg := handlers.NewAPIConfigHandler() r := gin.New() api := r.Group("/api/v1") authGroup := api.Group("/auth") { authGroup.POST("/register", auth.Register) authGroup.POST("/login", auth.Login) authGroup.POST("/refresh", auth.Refresh) authGroup.POST("/logout", auth.Logout) } protected := api.Group("") { // Chats protected.GET("/chats", chats.ListChats) protected.POST("/chats", chats.CreateChat) protected.GET("/chats/:id", chats.GetChat) protected.PUT("/chats/:id", chats.UpdateChat) protected.DELETE("/chats/:id", chats.DeleteChat) // Messages protected.GET("/chats/:id/messages", msgs.ListMessages) protected.POST("/chats/:id/messages", msgs.CreateMessage) protected.POST("/chats/:id/regenerate", msgs.Regenerate) // Chat Engine protected.POST("/chat/completions", comp.Complete) // API Configs protected.GET("/api-configs", apiCfg.ListConfigs) protected.POST("/api-configs", apiCfg.CreateConfig) protected.GET("/api-configs/:id", apiCfg.GetConfig) protected.PUT("/api-configs/:id", apiCfg.UpdateConfig) protected.DELETE("/api-configs/:id", apiCfg.DeleteConfig) protected.GET("/api-configs/:id/models", apiCfg.ListModels) protected.GET("/models", apiCfg.ListAllModels) } routes := r.Routes() routePaths := make(map[string]bool) for _, route := range routes { routePaths[route.Method+" "+route.Path] = true } expected := []string{ // Auth "POST /api/v1/auth/register", "POST /api/v1/auth/login", "POST /api/v1/auth/refresh", "POST /api/v1/auth/logout", // Chats "GET /api/v1/chats", "POST /api/v1/chats", "GET /api/v1/chats/:id", "PUT /api/v1/chats/:id", "DELETE /api/v1/chats/:id", // Messages "GET /api/v1/chats/:id/messages", "POST /api/v1/chats/:id/messages", "POST /api/v1/chats/:id/regenerate", // Chat Engine "POST /api/v1/chat/completions", // API Configs "GET /api/v1/api-configs", "POST /api/v1/api-configs", "GET /api/v1/api-configs/:id", "PUT /api/v1/api-configs/:id", "DELETE /api/v1/api-configs/:id", "GET /api/v1/api-configs/:id/models", // Models "GET /api/v1/models", } for _, e := range expected { if !routePaths[e] { t.Errorf("Missing route: %s", e) } } } func TestProviderRegistry(t *testing.T) { ids := providers.List() if len(ids) < 2 { t.Errorf("Expected at least 2 providers, got %d", len(ids)) } // OpenAI should be registered p, err := providers.Get("openai") if err != nil { t.Errorf("OpenAI provider not found: %v", err) } if p.ID() != "openai" { t.Errorf("Expected ID 'openai', got '%s'", p.ID()) } // Anthropic should be registered p, err = providers.Get("anthropic") if err != nil { t.Errorf("Anthropic provider not found: %v", err) } if p.ID() != "anthropic" { t.Errorf("Expected ID 'anthropic', got '%s'", p.ID()) } // Unknown should fail _, err = providers.Get("nonexistent") if err == nil { t.Error("Expected error for unknown provider") } } func TestAnthropicStaticModels(t *testing.T) { p := &providers.AnthropicProvider{} models, err := p.ListModels(nil, providers.ProviderConfig{}) if err != nil { t.Errorf("ListModels should not fail: %v", err) } if len(models) == 0 { t.Error("Expected some static models") } // Check that known models are present found := false for _, m := range models { if m.ID == "claude-sonnet-4-20250514" { found = true break } } if !found { t.Error("Expected claude-sonnet-4 in static model list") } } func TestStubHandler(t *testing.T) { r := gin.New() r.GET("/test", stubHandler("settings")) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/test", nil) r.ServeHTTP(w, req) if w.Code != 501 { t.Errorf("Expected 501, got %d", w.Code) } } func TestAuthMiddlewareNoDatabase(t *testing.T) { cfg := &config.Config{JWTSecret: "test"} r := gin.New() r.Use(middleware.Auth(cfg)) r.GET("/protected", func(c *gin.Context) { c.JSON(200, gin.H{"ok": true}) }) w := httptest.NewRecorder() req, _ := http.NewRequest("GET", "/protected", nil) r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Errorf("Expected pass-through with no DB, got %d", w.Code) } }