feat(T1.8): implement health checks for Kubernetes deployment
- Add internal/health package with health checker - Implement three endpoints: /health, /health/live, /health/ready - /health returns full JSON report with component status, latency, timestamp - /health/live for K8s liveness probe (service running) - /health/ready for K8s readiness probe (ready to accept traffic) - Temporal connectivity check via GetWorkflow call with timeout - Health check caching (30s interval) to prevent excessive checks - Graceful shutdown: health server stops on SIGINT/SIGTERM - Add --health flag to starter command to run health check - Worker runs health server on port 8081 alongside task queue worker - 10/10 unit tests passing - All verification criteria met Closes T1.8
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
package health
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// TestNewChecker tests the creation of a new health checker
|
||||
func TestNewChecker(t *testing.T) {
|
||||
checker := NewChecker(nil)
|
||||
assert.NotNil(t, checker)
|
||||
assert.Equal(t, 30*time.Second, checker.checkInterval)
|
||||
}
|
||||
|
||||
// TestCheckWithNilClient tests health check with nil client
|
||||
func TestCheckWithNilClient(t *testing.T) {
|
||||
checker := NewChecker(nil)
|
||||
report := checker.Check(context.Background())
|
||||
|
||||
assert.NotNil(t, report)
|
||||
assert.Equal(t, StatusUnhealthy, report.Status)
|
||||
assert.Len(t, report.Components, 1)
|
||||
assert.Equal(t, StatusUnhealthy, report.Components["temporal"].Status)
|
||||
assert.Equal(t, "Temporal client not initialized", report.Components["temporal"].Error)
|
||||
}
|
||||
|
||||
// TestIsHealthy tests the IsHealthy method
|
||||
func TestIsHealthy(t *testing.T) {
|
||||
checker := NewChecker(nil)
|
||||
assert.False(t, checker.IsHealthy(context.Background()))
|
||||
}
|
||||
|
||||
// TestGetReport tests the GetReport method
|
||||
func TestGetReport(t *testing.T) {
|
||||
checker := NewChecker(nil)
|
||||
report := checker.GetReport(context.Background())
|
||||
assert.NotNil(t, report)
|
||||
assert.Equal(t, StatusUnhealthy, report.Status)
|
||||
}
|
||||
|
||||
// TestHealthReportJSON tests JSON serialization
|
||||
func TestHealthReportJSON(t *testing.T) {
|
||||
checker := NewChecker(nil)
|
||||
report := checker.Check(context.Background())
|
||||
|
||||
jsonData, err := report.ToJSON()
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, jsonData)
|
||||
assert.Contains(t, string(jsonData), "unhealthy")
|
||||
assert.Contains(t, string(jsonData), "temporal")
|
||||
}
|
||||
|
||||
// TestHealthReportTimestamp tests that timestamp is set
|
||||
func TestHealthReportTimestamp(t *testing.T) {
|
||||
checker := NewChecker(nil)
|
||||
before := time.Now()
|
||||
report := checker.Check(context.Background())
|
||||
after := time.Now()
|
||||
|
||||
assert.True(t, report.Timestamp.After(before) || report.Timestamp.Equal(before))
|
||||
assert.True(t, report.Timestamp.Before(after) || report.Timestamp.Equal(after))
|
||||
}
|
||||
|
||||
// TestStatusString tests Status string representation
|
||||
func TestStatusString(t *testing.T) {
|
||||
assert.Equal(t, "healthy", StatusHealthy.String())
|
||||
assert.Equal(t, "unhealthy", StatusUnhealthy.String())
|
||||
assert.Equal(t, "unknown", StatusUnknown.String())
|
||||
}
|
||||
|
||||
// TestComponentHealthLatency tests that latency is recorded
|
||||
func TestComponentHealthLatency(t *testing.T) {
|
||||
checker := NewChecker(nil)
|
||||
report := checker.Check(context.Background())
|
||||
|
||||
assert.NotNil(t, report.Components["temporal"])
|
||||
assert.GreaterOrEqual(t, report.Components["temporal"].Latency, int64(0))
|
||||
}
|
||||
|
||||
// TestHealthCheckCaching tests that recent checks are cached
|
||||
func TestHealthCheckCaching(t *testing.T) {
|
||||
checker := NewChecker(nil)
|
||||
|
||||
// First check
|
||||
report1 := checker.Check(context.Background())
|
||||
time1 := report1.Timestamp
|
||||
|
||||
// Second check immediately (should be cached)
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
report2 := checker.Check(context.Background())
|
||||
time2 := report2.Timestamp
|
||||
|
||||
// Timestamps should be the same or very close (cached)
|
||||
assert.Equal(t, time1, time2, "second check should use cached result")
|
||||
}
|
||||
|
||||
// TestComponentHealthDefaults tests default component health values
|
||||
func TestComponentHealthDefaults(t *testing.T) {
|
||||
comp := ComponentHealth{
|
||||
Name: "test",
|
||||
Status: StatusHealthy,
|
||||
}
|
||||
|
||||
assert.Equal(t, "test", comp.Name)
|
||||
assert.Equal(t, StatusHealthy, comp.Status)
|
||||
assert.Empty(t, comp.Error)
|
||||
}
|
||||
Reference in New Issue
Block a user