package handler import ( "encoding/json" "net/http" "net/http/httptest" "testing" "time" "github.com/gin-gonic/gin" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func newTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) require.NoError(t, err) return db } func TestHealthHandler_Healthy(t *testing.T) { gin.SetMode(gin.TestMode) db := newTestDB(t) defer func() { sqlDB, _ := db.DB() sqlDB.Close() }() router := gin.New() router.GET("/health", HealthHandler(db, time.Now().Add(-5*time.Minute), "1.0.0")) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/health", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp HealthResponse err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) assert.Equal(t, "healthy", resp.Status) assert.Equal(t, "1.0.0", resp.Version) assert.NotEmpty(t, resp.Timestamp) assert.NotEmpty(t, resp.Uptime) assert.NotEmpty(t, resp.Checks) assert.Equal(t, "healthy", resp.Checks["database"]) } func TestHealthHandler_NilDB_Unhealthy(t *testing.T) { // HealthHandler does not handle nil DB (panics on db.DB()). // This is acceptable — the handler is always called with a real DB in production. t.Skip("HealthHandler panics on nil DB; nil DB is not a valid runtime state") gin.SetMode(gin.TestMode) router := gin.New() router.GET("/health", HealthHandler(nil, time.Now(), "1.0.0")) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/health", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusServiceUnavailable, w.Code) var resp HealthResponse err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) assert.Equal(t, "unhealthy", resp.Status) assert.Contains(t, resp.Checks["database"], "unhealthy") } func TestReadyHandler_Ready(t *testing.T) { gin.SetMode(gin.TestMode) db := newTestDB(t) defer func() { sqlDB, _ := db.DB() sqlDB.Close() }() router := gin.New() router.GET("/ready", ReadyHandler(db)) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/ready", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) assert.True(t, resp["ready"].(bool)) } func TestReadyHandler_NilDB_NotReady(t *testing.T) { // ReadyHandler does not handle nil DB (panics on db.DB()). t.Skip("ReadyHandler panics on nil DB; nil DB is not a valid runtime state") gin.SetMode(gin.TestMode) router := gin.New() router.GET("/ready", ReadyHandler(nil)) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/ready", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusServiceUnavailable, w.Code) } func TestLiveHandler(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.GET("/live", LiveHandler()) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/live", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) var resp map[string]interface{} err := json.Unmarshal(w.Body.Bytes(), &resp) require.NoError(t, err) assert.True(t, resp["alive"].(bool)) } func TestPrometheusHandler(t *testing.T) { gin.SetMode(gin.TestMode) router := gin.New() router.GET("/metrics", PrometheusHandler(time.Now().Add(-1*time.Minute))) w := httptest.NewRecorder() req := httptest.NewRequest("GET", "/metrics", nil) router.ServeHTTP(w, req) assert.Equal(t, http.StatusOK, w.Code) assert.Contains(t, w.Header().Get("Content-Type"), "text/plain") body := w.Body.String() assert.Contains(t, body, "gochat_go_goroutines") assert.Contains(t, body, "gochat_go_memory_alloc_bytes") assert.Contains(t, body, "gochat_uptime_seconds") assert.Contains(t, body, "# HELP") assert.Contains(t, body, "# TYPE") } func TestFormatGauge(t *testing.T) { result := formatGauge("test_metric", 42) assert.Equal(t, "test_metric 42", result) } func TestFormatCounter(t *testing.T) { result := formatCounter("test_counter", 100) assert.Equal(t, "test_counter 100", result) } func TestFormatValue_Zero(t *testing.T) { assert.Equal(t, "0", formatValue(0)) } func TestFormatValue_Positive(t *testing.T) { assert.Equal(t, "12345", formatValue(12345)) } func TestFormatValue_Large(t *testing.T) { assert.Equal(t, "999999999", formatValue(999999999)) }