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:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user