// Package healthcheck 验证 room-service ready/live 对存储、lease 和 drain 状态的映射。 package healthcheck import ( "context" "errors" "testing" "time" "hyapp/pkg/grpchealth" ) // okRuntime 模拟领域服务进程内状态正常。 type okRuntime struct{} // HealthCheck 返回 nil,让测试聚焦外部依赖和 listener 状态。 func (okRuntime) HealthCheck() error { return nil } // fakeMySQL 模拟 MySQL ping 结果。 type fakeMySQL struct { // err 是 Ping 返回的错误。 err error } // Ping 实现 MySQLPinger。 func (m fakeMySQL) Ping(context.Context) error { return m.err } // fakeRedis 分别模拟 Redis ping 和 lease Lua 探针结果。 type fakeRedis struct { pingErr error leaseErr error } // Ping 实现 RedisProbe 的基础连通性检查。 func (r fakeRedis) Ping(context.Context) error { return r.pingErr } // ProbeLease 实现 RedisProbe 的 lease 原子路径检查。 func (r fakeRedis) ProbeLease(context.Context, string, time.Time, time.Duration) error { return r.leaseErr } // TestReadyRequiresStorageAndLease 验证 ready 必须同时满足 listener、runtime、MySQL 和 Redis。 func TestReadyRequiresStorageAndLease(t *testing.T) { state := NewState("room-node-test", okRuntime{}, fakeMySQL{}, fakeRedis{}) // app.Run 进入 Serve 后会设置该标记;测试直接设置以隔离 State 行为。 state.MarkGRPCServing() if !grpchealth.Healthy(state.Ready(context.Background())) { t.Fatalf("ready should pass when listener, runtime, MySQL and Redis probes pass: %+v", state.Ready(context.Background())) } state.MarkDraining() // drain 中的节点不能继续接管房间或接收新命令。 if grpchealth.Healthy(state.Ready(context.Background())) { t.Fatalf("ready should fail while draining") } } // TestReadyFailsWhenRedisLeaseProbeFails 验证 Redis 连通但 Lua lease 路径失败时 ready 必须失败。 func TestReadyFailsWhenRedisLeaseProbeFails(t *testing.T) { state := NewState("room-node-test", okRuntime{}, fakeMySQL{}, fakeRedis{leaseErr: errors.New("lease eval failed")}) state.MarkGRPCServing() checks := state.Ready(context.Background()) if grpchealth.Healthy(checks) { t.Fatalf("ready should fail when Redis lease probe fails") } found := false for _, check := range checks { if check.Name == "redis_lease" && check.Status == grpchealth.StatusFail { // redis_lease 单独暴露,便于区分 Redis ping 成功但 owner 仲裁不可用。 found = true } } if !found { t.Fatalf("ready failure should include redis_lease check: %+v", checks) } }