package service import ( "testing" "hyapp/services/room-service/internal/room/command" "hyapp/services/room-service/internal/room/state" ) func replayHeartbeat(t *testing.T, current *state.RoomState, userID int64, sentAtMS int64) { t.Helper() cmd := &command.RoomHeartbeat{Base: command.Base{ AppCode: "lalu", RequestID: "req-replay-heartbeat", CommandID: "cmd-replay-heartbeat", ActorID: userID, Room: current.RoomID, SentAtMS: sentAtMS, }} if err := replay(current, cmd, sentAtMS); err != nil { t.Fatalf("replay heartbeat for user %d failed: %v", userID, err) } } // 线上心跳会静默恢复丢失的 presence 后才提交命令日志;回放必须重现恢复(含 Version++), // 否则恢复出的 OnlineUsers/版本与快照谱系分叉。 func TestReplayRoomHeartbeatRecreatesMissingPresence(t *testing.T) { ownerID := int64(11) adminID := int64(22) viewerID := int64(33) current := state.NewRoomState("room-replay-heartbeat", ownerID, 4, "voice") current.AdminUsers[adminID] = true baseVersion := current.Version replayHeartbeat(t, current, viewerID, 1_000) viewer, exists := current.OnlineUsers[viewerID] if !exists || viewer.Role != "audience" || viewer.JoinedAtMS != 1_000 || viewer.LastSeenAtMS != 1_000 { t.Fatalf("replayed heal must recreate audience presence from SentAtMS: %+v", viewer) } if current.Version != baseVersion+1 { t.Fatalf("replayed heal must advance version exactly once: base=%d now=%d", baseVersion, current.Version) } // 角色从权威字段推导:owner/admin 恢复后徽标不丢失。 replayHeartbeat(t, current, ownerID, 2_000) if owner := current.OnlineUsers[ownerID]; owner == nil || owner.Role != "owner" { t.Fatalf("replayed heal must restore owner role: %+v", owner) } replayHeartbeat(t, current, adminID, 3_000) if admin := current.OnlineUsers[adminID]; admin == nil || admin.Role != "admin" { t.Fatalf("replayed heal must restore admin role: %+v", admin) } } func TestReplayRoomHeartbeatRefreshesExistingPresence(t *testing.T) { viewerID := int64(44) current := state.NewRoomState("room-replay-heartbeat-refresh", 11, 4, "voice") current.OnlineUsers[viewerID] = &state.RoomUserState{ UserID: viewerID, Role: "audience", JoinedAtMS: 500, LastSeenAtMS: 500, } baseVersion := current.Version replayHeartbeat(t, current, viewerID, 9_000) viewer := current.OnlineUsers[viewerID] if viewer.LastSeenAtMS != 9_000 || viewer.JoinedAtMS != 500 { t.Fatalf("replayed refresh must only update last_seen: %+v", viewer) } if current.Version != baseVersion+1 { t.Fatalf("replayed refresh must advance version exactly once: base=%d now=%d", baseVersion, current.Version) } }