HH-442: isolate runtime processes and harden shutdown (#90)

* HH-442: isolate runtime processes and harden shutdown

* HH-442: harden worker shutdown races

* HH-442: gate dependency shutdown on active handlers

---------

Co-authored-by: Rogee <rogee@ipao.vip>
This commit is contained in:
Rogee
2026-08-22 02:38:15 +08:00
committed by GitHub
co-authored by rogee
parent 6d6d80dd86
commit 798ea43c2f
35 changed files with 1673 additions and 451 deletions
+62
View File
@@ -1,7 +1,9 @@
package handler
import (
"context"
"encoding/json"
"errors"
"net/http"
"net/http/httptest"
"testing"
@@ -93,6 +95,49 @@ func TestReadyHandler_Ready(t *testing.T) {
assert.True(t, resp["ready"].(bool))
}
func TestReadyHandler_RequiredDependencyFailure(t *testing.T) {
db := newTestDB(t)
router := gin.New()
router.GET("/ready", ReadyHandler(db, DependencyCheck{Name: "redis", Check: func(context.Context) error {
return errors.New("connection refused")
}}))
w := httptest.NewRecorder()
router.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/ready", nil))
assert.Equal(t, http.StatusServiceUnavailable, w.Code)
assert.JSONEq(t, `{"ready":false,"checks":{"database":"healthy","redis":"unhealthy: connection refused"}}`, w.Body.String())
}
func TestDatabaseOutageFailsReadinessButNotLiveness(t *testing.T) {
db := newTestDB(t)
sqlDB, err := db.DB()
require.NoError(t, err)
require.NoError(t, sqlDB.Close())
router := gin.New()
router.GET("/ready", ReadyHandler(db))
router.GET("/live", LiveHandler())
ready := httptest.NewRecorder()
router.ServeHTTP(ready, httptest.NewRequest(http.MethodGet, "/ready", nil))
assert.Equal(t, http.StatusServiceUnavailable, ready.Code)
live := httptest.NewRecorder()
router.ServeHTTP(live, httptest.NewRequest(http.MethodGet, "/live", nil))
assert.Equal(t, http.StatusOK, live.Code)
}
func TestHealthHandler_SelectedDependency(t *testing.T) {
db := newTestDB(t)
router := gin.New()
router.GET("/health", HealthHandler(db, time.Now(), "test", DependencyCheck{Name: "redis", Check: func(context.Context) error { return nil }}))
w := httptest.NewRecorder()
router.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/health?check=redis", nil))
assert.Equal(t, http.StatusOK, w.Code)
var response HealthResponse
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &response))
assert.Equal(t, map[string]string{"redis": "healthy"}, response.Checks)
}
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")
@@ -144,6 +189,23 @@ func TestPrometheusHandler(t *testing.T) {
assert.Contains(t, body, "# TYPE")
}
func TestHTTPMetricsRecordsRequestsErrorsAndLatency(t *testing.T) {
metrics := NewHTTPMetrics(time.Now())
router := gin.New()
router.Use(metrics.Middleware())
router.GET("/items/:id", func(c *gin.Context) { c.Status(http.StatusInternalServerError) })
router.GET("/metrics", metrics.Handler())
router.ServeHTTP(httptest.NewRecorder(), httptest.NewRequest(http.MethodGet, "/items/42", nil))
w := httptest.NewRecorder()
router.ServeHTTP(w, httptest.NewRequest(http.MethodGet, "/metrics", nil))
body := w.Body.String()
assert.Contains(t, body, `http_requests_total{method="GET",route="/items/:id",status="500"} 1`)
assert.Contains(t, body, `http_request_errors_total{method="GET",route="/items/:id",status="500"} 1`)
assert.Contains(t, body, `http_request_duration_seconds_count{method="GET",route="/items/:id",status="500"} 1`)
}
func TestFormatGauge(t *testing.T) {
result := formatGauge("test_metric", 42)
assert.Equal(t, "test_metric 42", result)