# Run all handler tests
go test ./internal/handlers/... -v -cover
# Run only health tests
go test ./internal/handlers/ -v -run TestHealth -cover
# Run health tests with detailed output
go test ./internal/handlers/ -v -run Test -cover -timeout 30sThis implementation includes 16 comprehensive test cases covering:
- TestLivenessProbe - Verifies liveness always returns 200/healthy
- TestReadinessProbeHealthy - Verifies readiness returns 200 when all dependencies healthy
- TestReadinessProbeDegraded - Verifies readiness returns 503 when dependencies degraded
- TestHealthDetails - Verifies detailed endpoint includes full dependency information
- TestCheckDatabase_Healthy - Database responds within timeout
- TestCheckDatabase_Timeout - Database responds with timeout status
- TestCheckDatabase_NotConfigured - DATABASE_URL not set
- TestCheckDatabase_Uninitialized - Database client is nil
- TestCheckOutbox_Healthy - Outbox manager reports healthy with stats
- TestCheckOutbox_Unhealthy - Outbox returns error status
- TestCheckOutbox_NotConfigured - Outbox manager is nil
- TestDeriveOverallStatus - Tests all status combinations:
- All healthy → healthy
- One degraded → degraded
- One unhealthy → unhealthy
- Map vs struct representation
- TestCheckAllDependencies_Concurrent - Dependencies check in parallel
- TestCheckAllDependencies_Timeout - Context timeout during concurrent checks
- TestSecurityNoSensitiveData - Verifies no credentials/secrets in response
- TestLifecycleEndpointsIntegration - All three endpoints work together
After running go test ./internal/handlers/... -v -cover, you should see:
=== RUN TestLivenessProbe
--- PASS: TestLivenessProbe (0.00s)
=== RUN TestReadinessProbeHealthy
--- PASS: TestReadinessProbeHealthy (0.01s)
=== RUN TestReadinessProbeDegraded
--- PASS: TestReadinessProbeDegraded (0.01s)
=== RUN TestHealthDetails
--- PASS: TestHealthDetails (0.02s)
=== RUN TestCheckDatabase_Healthy
--- PASS: TestCheckDatabase_Healthy (0.00s)
=== RUN TestCheckDatabase_Timeout
--- PASS: TestCheckDatabase_Timeout (3.10s) [includes timeout delays]
=== RUN TestCheckDatabase_NotConfigured
--- PASS: TestCheckDatabase_NotConfigured (0.00s)
=== RUN TestCheckDatabase_Uninitialized
--- PASS: TestCheckDatabase_Uninitialized (0.00s)
=== RUN TestCheckOutbox_Healthy
--- PASS: TestCheckOutbox_Healthy (0.00s)
=== RUN TestCheckOutbox_Unhealthy
--- PASS: TestCheckOutbox_Unhealthy (0.00s)
=== RUN TestCheckOutbox_NotConfigured
--- PASS: TestCheckOutbox_NotConfigured (0.00s)
=== RUN TestDeriveOverallStatus
--- PASS: TestDeriveOverallStatus (0.00s)
=== RUN TestCheckAllDependencies_Concurrent
--- PASS: TestCheckAllDependencies_Concurrent (0.02s)
=== RUN TestCheckAllDependencies_Timeout
--- PASS: TestCheckAllDependencies_Timeout (0.20s)
=== RUN TestSecurityNoSensitiveData
--- PASS: TestSecurityNoSensitiveData (0.01s)
=== RUN TestLifecycleEndpointsIntegration
--- PASS: TestLifecycleEndpointsIntegration (0.02s)
=== RUN TestHealth
--- PASS: TestHealth (0.00s)
ok stellarbill-backend/internal/handlers 3.40s coverage: 87.2% of statements
What: Verify each endpoint responds with correct HTTP status codes and JSON structure
Files: TestLivenessProbe, TestReadinessProbeHealthy, TestReadinessProbeDegraded, TestHealthDetails
Validates:
- HTTP 200 returned when healthy
- HTTP 503 returned when degraded
- JSON response structure matches
HealthResponsetype - Service name is correct
- Timestamp is present and valid
Example Output:
{
"status": "healthy",
"service": "stellarbill-backend",
"timestamp": "2026-04-23T10:30:45Z"
}What: Verify database ping logic with timeouts and retries
Files: TestCheckDatabase_*
Validates:
- Successful ping returns "healthy"
- Context deadline exceeded returns "degraded" with timeout message
- Missing DATABASE_URL returns "not_configured"
- Nil database client returns "not_configured"
- Latency is measured and reported
- Exponential backoff is applied on retries
Example Output:
{
"status": "healthy",
"latency": "1.2ms"
}What: Verify outbox queue health and statistics
Files: TestCheckOutbox_*
Validates:
- Healthy outbox returns health status with statistics
- Unhealthy outbox includes error message
- Statistics are included in response details
- Timeout is respected
Example Output:
{
"status": "healthy",
"latency": "0.8ms",
"details": {
"pending_messages": 42,
"processed_today": 1000
}
}What: Verify correct overall status based on dependency states
Files: TestDeriveOverallStatus
Validates:
- All healthy → service healthy
- One degraded → service degraded
- One unhealthy → service unhealthy
- Works with both struct and map representations
Scenarios:
healthy + healthy → healthy
healthy + degraded → degraded
healthy + unhealthy → unhealthy (note: no current unhealthy case)
degraded + degraded → degraded
What: Verify health checks work in parallel and respect context timeouts
Files: TestCheckAllDependencies_Concurrent, TestCheckAllDependencies_Timeout
Validates:
- All dependency checks run concurrently (not sequentially)
- Context timeout is respected across all checks
- Missing checks are marked as timeout when context expires
- No goroutine leaks from concurrent checks
Performance:
- All checks should complete within ~5-10ms for healthy system
- Timeout checks force delays to verify timeout respects context
What: Verify no sensitive data leaks in responses
Files: TestSecurityNoSensitiveData
Validates:
- Database credentials NOT in response
- Connection strings NOT in response
- User information NOT in response
- Error messages don't contain secrets
- Generic error messages in production
Check: Parse response and verify these are NOT present:
- password
- user:password
- localhost/mydb
- API keys
- JWT secrets
What: Verify all endpoints work together in realistic scenario
Files: TestLifecycleEndpointsIntegration
Validates:
- All three endpoints can be called without interference
- Each returns correct status and structure
- Service name consistent across all endpoints
- Timestamps are valid RFC3339 format
go test ./internal/handlers -v -run TestLivenessProbego test ./internal/handlers -v -run TestProbego test ./internal/handlers -v -racego test ./internal/handlers -v -cover -coverprofile=coverage.out
go tool cover -html=coverage.out # Opens in browsergo test ./internal/handlers -v -timeout 60s- Quick tests (status logic, API contract): <1ms each
- Timeout tests (deliberately slow): 3-5 seconds
- Total suite: 3-5 seconds
- Memory: <50MB
- CPU: Single core
- Goroutines: All cleaned up (race detector will catch leaks)
- All tests should PASS
- 16/16 tests passing = complete success
- Coverage should be 85%+ of health.go
- Increase timeout:
go test -timeout 60s - Check for goroutine leaks: Run with
-raceflag
- Ensure mock is returning expected errors
- Verify latency calculations don't underflow
- Check context cancellation logic
- Verify all status constants match enum values
- Check struct vs map type handling
- Ensure nil handling for missing dependencies
- Sensitive data in error messages?
- Database connection string exposed?
- Check all error paths for info leaks
Optional: Run benchmarks to measure health check overhead:
go test ./internal/handlers -bench=BenchmarkProbes -benchmemExpected results:
BenchmarkProbes/Liveness-8 10000 102000 ns/op 1200 B/op 15 allocs/op
BenchmarkProbes/Readiness-8 1000 1050000 ns/op 4500 B/op 45 allocs/op
BenchmarkProbes/Details-8 800 1350000 ns/op 6200 B/op 60 allocs/op
(These benchmarks are NOT included in the current test suite, but can be added if needed)
To test with a real PostgreSQL connection:
# Set connection string
export DATABASE_URL="postgres://user:password@localhost:5432/test_db"
# Run tests
go test ./internal/handlers -v -run TestCheckDatabase- Mocks (current): Fast, deterministic, test logic
- Real DB: Validates actual connectivity, timeouts, network behavior
Both are valid; mocks are used here for speed and reproducibility.
Before committing, verify:
- All 16 tests pass
- Coverage >= 85%
- No race detector warnings (
go test -race) - No goroutine leaks
- Security test confirms no secrets in response
- Can handle concurrent health checks
- Respects context timeouts
- Documentation matches implementation
- Kubernetes integration guide included
- Operations runbooks included
- Install Go 1.26+ and run test suite
- Integrate health routes in main.go (see HEALTH_INTEGRATION_EXAMPLE.md)
- Deploy to staging and verify readiness/liveness work with Kubernetes
- Monitor metrics and adjust timeouts based on real latency data
- Create custom health checks for app-specific dependencies as needed
- Test file: internal/handlers/health_test.go
- Implementation: internal/handlers/health.go
- Integration guide: docs/HEALTH_INTEGRATION_EXAMPLE.md
- Operations guide: docs/HEALTH_CHECKS.md