package service import ( "context" "database/sql" "errors" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "github.com/redis/go-redis/v9" "github.com/stretchr/testify/require" ) func TestHealthService_Liveness_AlwaysOK(t *testing.T) { s := NewHealthService(nil, nil) require.NoError(t, s.Liveness()) } func TestHealthService_Readiness_AllNilReturnsOK(t *testing.T) { // 当所有依赖都为 nil 时(早期启动或 unit test),readiness 应直接 OK。 s := NewHealthService(nil, nil) report := s.Readiness(context.Background()) require.True(t, report.OK) require.Empty(t, report.Details) } func TestHealthService_Readiness_DBPingFails(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true)) require.NoError(t, err) defer db.Close() mock.ExpectPing().WillReturnError(errors.New("connection refused")) s := NewHealthService(db, nil) report := s.Readiness(context.Background()) require.False(t, report.OK) require.Contains(t, report.Details, "database") require.False(t, report.Details["database"].OK) require.Contains(t, report.Details["database"].Error, "connection refused") } func TestHealthService_Readiness_DBOK(t *testing.T) { db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true)) require.NoError(t, err) defer db.Close() mock.ExpectPing() s := NewHealthService(db, nil) report := s.Readiness(context.Background()) require.True(t, report.OK) require.True(t, report.Details["database"].OK) } func TestHealthService_Readiness_RedisFails(t *testing.T) { // 指向一个不可达端口让 redis ping 立刻失败。 rdb := redis.NewClient(&redis.Options{ Addr: "127.0.0.1:1", DialTimeout: 200 * time.Millisecond, ReadTimeout: 200 * time.Millisecond, }) defer rdb.Close() s := NewHealthService(nil, rdb) s.timeout = 500 * time.Millisecond report := s.Readiness(context.Background()) require.False(t, report.OK) require.Contains(t, report.Details, "redis") require.False(t, report.Details["redis"].OK) } func TestHealthService_Readiness_PerComponentTimeout(t *testing.T) { // 验证 readiness 在超时时不会无限挂住。 db, mock, err := sqlmock.New(sqlmock.MonitorPingsOption(true)) require.NoError(t, err) defer db.Close() mock.ExpectPing().WillDelayFor(2 * time.Second) s := NewHealthService(db, nil) s.timeout = 100 * time.Millisecond start := time.Now() report := s.Readiness(context.Background()) elapsed := time.Since(start) require.Less(t, elapsed, 1*time.Second, "readiness should respect per-component timeout") require.False(t, report.OK) require.NotEmpty(t, report.Details["database"].Error, "timeout should propagate as an error") } // 抑制未使用包警告(database/sql 在签名里使用)。 var _ = sql.ErrNoRows