Files
yms-daemon/internal/healthcheck/actuator_test.go
T
2026-08-17 10:10:14 +08:00

130 lines
5.1 KiB
Go

package healthcheck
import (
"bytes"
"context"
"errors"
"io"
"net/http"
"sync/atomic"
"testing"
"time"
)
// healthyActuatorSample 与线上抓包结果一致的 Actuator 健康响应样例。
const healthyActuatorSample = `{"status":"UP","components":{"db":{"status":"UP","components":{"dorisDataSource":{"status":"UP"},"postgresqlDataSource":{"status":"UP"}}},"diskSpace":{"status":"UP"},"ping":{"status":"UP"},"redis":{"status":"UP"}}}`
// TestActuatorCheckerAcceptsConfirmedResponseShape 验证检查器能接受与真实抓包一致的健康响应并正确解析各组件状态。
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)
}
}
// TestActuatorCheckerChecksOnceForTransactionRecovery 验证事务恢复路径中单次采样即可确认已记录的健康步骤。
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)
}
}
// TestActuatorCheckerSamplesReadinessWithoutRestarting 验证在服务暂未就绪时会持续采样直到就绪,且不会触发重启。
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())
}
}
// TestActuatorCheckerStopsImmediatelyWhenContainerStops 验证容器停止时等待立即以 ErrWorkloadStopped 失败。
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)
}
}
// TestActuatorCheckerHonorsOverallTimeout 验证整体超时到达后返回 DeadlineExceeded 错误。
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)
}
}
// newTestChecker 以固定短间隔创建测试用检查器。
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
}
// alwaysRunning 表示工作负载始终处于运行状态,用于大多数测试场景。
func alwaysRunning(context.Context) (bool, error) {
return true, nil
}
// roundTripFunc 将普通函数适配为 http.RoundTripper,便于在测试中模拟响应。
type roundTripFunc func(*http.Request) (*http.Response, error)
// RoundTrip 直接调用底层的处理函数返回响应。
func (f roundTripFunc) RoundTrip(request *http.Request) (*http.Response, error) {
return f(request)
}
// newHTTPClient 返回一个使用给定处理函数生成响应的测试 HTTP 客户端。
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
})}
}
// response 构造一个带指定状态码与响应体的最小 HTTP 响应。
func response(statusCode int, body string) *http.Response {
return &http.Response{
StatusCode: statusCode,
Header: make(http.Header),
Body: io.NopCloser(bytes.NewBufferString(body)),
}
}