package server import ( "encoding/json" "net/http" "net/http/httptest" "testing" "github.com/gin-gonic/gin" "github.com/spf13/viper" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "go.opentelemetry.io/contrib/instrumentation/github.com/gin-gonic/gin/otelgin" "nixcn-cms/api" "nixcn-cms/middleware" "nixcn-cms/testutil" ) func init() { gin.SetMode(gin.TestMode) } // buildRouter mirrors the engine setup inside Start() without blocking on // ListenAndServe, making it possible to use httptest. func buildRouter() *gin.Engine { r := gin.New() r.Use(otelgin.Middleware(viper.GetString("server.service_name"))) r.Use(middleware.GinLogger()) r.Use(gin.Recovery()) api.Handler(r.Group("/app/api/v1")) return r } func TestServerRouterResponds(t *testing.T) { testutil.Setup(t) testutil.SeedClient(t) r := buildRouter() srv := httptest.NewServer(r) defer srv.Close() // POST /auth/magic exists — any response other than 404 confirms routing. resp, err := http.Post(srv.URL+"/app/api/v1/auth/magic", "application/json", nil) require.NoError(t, err) defer resp.Body.Close() assert.NotEqual(t, http.StatusNotFound, resp.StatusCode) } func TestServerRouterNotFound(t *testing.T) { testutil.Setup(t) r := buildRouter() req := httptest.NewRequest(http.MethodGet, "/app/api/v1/nonexistent", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) assert.Equal(t, http.StatusNotFound, w.Code) } func TestServerRouterRecovery(t *testing.T) { testutil.Setup(t) r := gin.New() r.Use(gin.Recovery()) r.GET("/panic", func(c *gin.Context) { panic("test panic") }) req := httptest.NewRequest(http.MethodGet, "/panic", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) // Recovery middleware must catch the panic and return 500. assert.Equal(t, http.StatusInternalServerError, w.Code) } func TestServerModeRelease(t *testing.T) { viper.Set("server.debug_mode", false) viper.Set("server.service_name", "test-svc") defer viper.Reset() // Calling Start() would block, so we replicate only the mode-setting logic. if !viper.GetBool("server.debug_mode") { gin.SetMode(gin.ReleaseMode) } assert.Equal(t, gin.ReleaseMode, gin.Mode()) gin.SetMode(gin.TestMode) // restore } func TestServerHealthEndpoints(t *testing.T) { testutil.Setup(t) testutil.SeedClient(t) r := buildRouter() endpoints := []struct { method string path string }{ {http.MethodPost, "/app/api/v1/auth/magic"}, {http.MethodPost, "/app/api/v1/auth/exchange"}, {http.MethodGet, "/app/api/v1/event/list"}, {http.MethodGet, "/app/api/v1/user/info"}, } for _, ep := range endpoints { t.Run(ep.method+" "+ep.path, func(t *testing.T) { req := httptest.NewRequest(ep.method, ep.path, nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) // Route must be registered — anything other than 404 is fine. assert.NotEqual(t, http.StatusNotFound, w.Code, "route %s %s should be registered", ep.method, ep.path) // Response must be valid JSON. var body map[string]any err := json.Unmarshal(w.Body.Bytes(), &body) assert.NoError(t, err, "response must be valid JSON for %s %s", ep.method, ep.path) }) } }