feat(server): add /api/healthz endpoint with rich health info

Adds a Kubernetes-style healthz endpoint returning status, version,
uptime_seconds and timestamp. Non-breaking — /api/health is preserved.

- New route: GET /api/healthz
- New handler: handleHealthz with HealthzResponse struct
- New unit test: pkg/server/healthz_test.go (passes locally)
- New BDD scenario: features/health/health.feature
- BDD steps: pkg/bdd/steps/health_steps.go, common_steps.go

Note: BDD tests require Postgres and will be validated by CI.

🤖 Co-Authored-By: Mistral Vibe (devstral-2 / mistral-medium-3.5)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-03 12:25:24 +02:00
parent 8503d0824e
commit 4bbf68ebea
6 changed files with 128 additions and 1 deletions

View File

@@ -63,3 +63,39 @@ func (s *CommonSteps) theStatusCodeShouldBe(expectedStatus int) error {
}
return nil
}
// JSON field validation
func (s *CommonSteps) theResponseShouldBeJSONWithFields(fields string) error {
// Parse the fields comma-separated list
fieldList := strings.Split(fields, ", ")
for _, field := range fieldList {
field = strings.TrimSpace(field)
if !s.responseContainsJSONField(field) {
return fmt.Errorf("response does not contain field %q", field)
}
}
return nil
}
func (s *CommonSteps) responseContainsJSONField(field string) bool {
body := string(s.client.GetLastBody())
// Simple check - look for "field":" in the JSON
// This works for simple fields, may need enhancement for nested objects
searchString := `"` + field + `":`
return strings.Contains(body, searchString)
}
func (s *CommonSteps) theFieldShouldEqual(field, expectedValue string) error {
body := string(s.client.GetLastBody())
// Look for the field and extract its value
// Simple implementation: look for "field":"value" pattern
searchPattern := `"` + field + `":"` + expectedValue + `"`
if !strings.Contains(body, searchPattern) {
// Also try without quotes (for numbers)
searchPatternNum := `"` + field + `":` + expectedValue
if !strings.Contains(body, searchPatternNum) {
return fmt.Errorf("field %q does not equal %q in response: %s", field, expectedValue, body)
}
}
return nil
}