This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/main_test.go
2026-02-18 17:47:21 +00:00

266 lines
7.3 KiB
Go

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()
settings := handlers.NewSettingsHandler()
adm := handlers.NewAdminHandler()
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)
protected.GET("/models/enabled", apiCfg.ListEnabledModels)
// User Settings & Profile
protected.GET("/profile", settings.GetProfile)
protected.PUT("/profile", settings.UpdateProfile)
protected.POST("/profile/password", settings.ChangePassword)
protected.GET("/settings", settings.GetSettings)
protected.PUT("/settings", settings.UpdateSettings)
}
// Admin routes
admin := api.Group("/admin")
{
admin.GET("/users", adm.ListUsers)
admin.POST("/users", adm.CreateUser)
admin.PUT("/users/:id/role", adm.UpdateUserRole)
admin.PUT("/users/:id/active", adm.ToggleUserActive)
admin.POST("/users/:id/reset-password", adm.ResetPassword)
admin.DELETE("/users/:id", adm.DeleteUser)
admin.GET("/settings", adm.ListGlobalSettings)
admin.GET("/settings/:key", adm.GetGlobalSetting)
admin.PUT("/settings/:key", adm.UpdateGlobalSetting)
admin.GET("/stats", adm.GetStats)
admin.GET("/configs", adm.ListGlobalConfigs)
admin.POST("/configs", adm.CreateGlobalConfig)
admin.DELETE("/configs/:id", adm.DeleteGlobalConfig)
admin.GET("/models", adm.ListModelConfigs)
admin.POST("/models/fetch", adm.FetchModels)
admin.PUT("/models/:id", adm.UpdateModelConfig)
admin.DELETE("/models/:id", adm.DeleteModelConfig)
}
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",
"GET /api/v1/models/enabled",
// Profile & Settings
"GET /api/v1/profile",
"PUT /api/v1/profile",
"POST /api/v1/profile/password",
"GET /api/v1/settings",
"PUT /api/v1/settings",
// Admin
"GET /api/v1/admin/users",
"POST /api/v1/admin/users",
"PUT /api/v1/admin/users/:id/role",
"PUT /api/v1/admin/users/:id/active",
"POST /api/v1/admin/users/:id/reset-password",
"DELETE /api/v1/admin/users/:id",
"GET /api/v1/admin/settings",
"GET /api/v1/admin/settings/:key",
"PUT /api/v1/admin/settings/:key",
"GET /api/v1/admin/stats",
"GET /api/v1/admin/configs",
"POST /api/v1/admin/configs",
"DELETE /api/v1/admin/configs/:id",
"GET /api/v1/admin/models",
"POST /api/v1/admin/models/fetch",
"PUT /api/v1/admin/models/:id",
"DELETE /api/v1/admin/models/:id",
}
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 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)
}
}