Files
yms-daemon/internal/healthcheck/actuator_test.go
T

118 lines
4.0 KiB
Go
Raw Normal View History

2026-08-15 20:58:01 +08:00
package healthcheck
import (
"bytes"
"context"
"errors"
"io"
"net/http"
"sync/atomic"
"testing"
"time"
)
const healthyActuatorSample = `{"status":"UP","components":{"db":{"status":"UP","components":{"dorisDataSource":{"status":"UP"},"postgresqlDataSource":{"status":"UP"}}},"diskSpace":{"status":"UP"},"ping":{"status":"UP"},"redis":{"status":"UP"}}}`
func TestActuatorCheckerAcceptsConfirmedResponseShape(t *testing.T) {
t.Parallel()
client := newHTTPClient(func(request *http.Request) *http.Response {
if request.URL.Path != "/yms/actuator/health" {
t.Errorf("unexpected health path: %s", request.URL.Path)
}
return response(http.StatusOK, healthyActuatorSample)
})
checker := newTestChecker(t, client)
report, err := checker.Wait(context.Background(), "http://127.0.0.1:8081/yms/actuator/health", time.Second, alwaysRunning)
if err != nil {
t.Fatalf("wait for healthy Actuator: %v", err)
}
if report.Status != "UP" || report.Components["db"] != "UP" || report.Components["redis"] != "UP" {
t.Fatalf("unexpected health report: %+v", report)
}
}
func TestActuatorCheckerChecksOnceForTransactionRecovery(t *testing.T) {
t.Parallel()
client := newHTTPClient(func(*http.Request) *http.Response {
return response(http.StatusOK, healthyActuatorSample)
})
checker := newTestChecker(t, client)
report, ready, err := checker.Check(context.Background(), "http://127.0.0.1:8081/yms/actuator/health", alwaysRunning)
if err != nil || !ready || report.Status != "UP" {
t.Fatalf("unexpected one-shot health result: report=%+v ready=%t err=%v", report, ready, err)
}
}
func TestActuatorCheckerSamplesReadinessWithoutRestarting(t *testing.T) {
t.Parallel()
var requests atomic.Int32
client := newHTTPClient(func(*http.Request) *http.Response {
if requests.Add(1) < 3 {
return response(http.StatusServiceUnavailable, `{"status":"DOWN"}`)
}
return response(http.StatusOK, healthyActuatorSample)
})
checker := newTestChecker(t, client)
if _, err := checker.Wait(context.Background(), "http://127.0.0.1:8081/yms/actuator/health", time.Second, alwaysRunning); err != nil {
t.Fatalf("wait for delayed readiness: %v", err)
}
if requests.Load() != 3 {
t.Fatalf("unexpected request count: %d", requests.Load())
}
}
func TestActuatorCheckerStopsImmediatelyWhenContainerStops(t *testing.T) {
t.Parallel()
checker := newTestChecker(t, http.DefaultClient)
_, err := checker.Wait(context.Background(), "http://127.0.0.1:8081/yms/actuator/health", time.Second,
func(context.Context) (bool, error) { return false, nil })
if !errors.Is(err, ErrWorkloadStopped) {
t.Fatalf("expected stopped workload error, got %v", err)
}
}
func TestActuatorCheckerHonorsOverallTimeout(t *testing.T) {
t.Parallel()
client := newHTTPClient(func(*http.Request) *http.Response {
return response(http.StatusServiceUnavailable, `{"status":"DOWN"}`)
})
checker := newTestChecker(t, client)
_, err := checker.Wait(context.Background(), "http://127.0.0.1:8081/yms/actuator/health", 25*time.Millisecond, alwaysRunning)
if !errors.Is(err, context.DeadlineExceeded) {
t.Fatalf("expected deadline error, got %v", err)
}
}
func newTestChecker(t *testing.T, client *http.Client) *ActuatorChecker {
t.Helper()
checker, err := NewActuatorChecker(client, 5*time.Millisecond)
if err != nil {
t.Fatalf("create Actuator checker: %v", err)
}
return checker
}
func alwaysRunning(context.Context) (bool, error) {
return true, nil
}
type roundTripFunc func(*http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
return f(request)
}
func newHTTPClient(handle func(*http.Request) *http.Response) *http.Client {
return &http.Client{Transport: roundTripFunc(func(request *http.Request) (*http.Response, error) {
return handle(request), nil
})}
}
func response(statusCode int, body string) *http.Response {
return &http.Response{
StatusCode: statusCode,
Header: make(http.Header),
Body: io.NopCloser(bytes.NewBufferString(body)),
}
}