package service_test import ( "context" "testing" "time" roomv1 "hyapp.local/api/proto/room/v1" "hyapp/pkg/appcode" "hyapp/services/room-service/internal/integration" roomservice "hyapp/services/room-service/internal/room/service" "hyapp/services/room-service/internal/router" "hyapp/services/room-service/internal/testutil/mysqltest" ) type fakeRTCUserRemover struct { roomID string userID int64 } func (r *fakeRTCUserRemover) RemoveUserByStrRoomID(_ context.Context, roomID string, userID int64) error { r.roomID = roomID r.userID = userID return nil } func TestKickUserRemovesPresenceBanAndRTCConnection(t *testing.T) { ctx := context.Background() repository := mysqltest.NewRepository(t) rtc := &fakeRTCUserRemover{} svc := roomservice.New(roomservice.Config{ NodeID: "node-kick-test", LeaseTTL: 10 * time.Second, RankLimit: 20, SnapshotEveryN: 1, RTCUserRemover: rtc, }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) roomID := "room-kick-flow" ownerID := int64(7101) viewerID := int64(7201) if _, err := svc.CreateRoom(ctx, &roomv1.CreateRoomRequest{ Meta: roomservice.NewRequestMeta(roomID, ownerID), SeatCount: 10, Mode: "voice", VisibleRegionId: 8101, RoomName: "Kick Flow", RoomShortId: "kick-flow", }); err != nil { t.Fatalf("create kick room fixture failed: %v", err) } if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ Meta: roomservice.NewRequestMeta(roomID, viewerID), Role: "audience", }); err != nil { t.Fatalf("join kick target failed: %v", err) } kickResp, err := svc.KickUser(ctx, &roomv1.KickUserRequest{ Meta: roomservice.NewRequestMeta(roomID, ownerID), TargetUserId: viewerID, }) if err != nil { t.Fatalf("kick user failed: %v", err) } if !kickResp.GetRtcKicked() || kickResp.GetRtcKickError() != "" || rtc.roomID != roomID || rtc.userID != viewerID { t.Fatalf("kick must remove RTC user after Room Cell commit: resp=%+v rtc=%+v", kickResp, rtc) } if !containsInt64(kickResp.GetRoom().GetBanUserIds(), viewerID) || findRoomUser(kickResp.GetRoom(), viewerID) != nil { t.Fatalf("kick response must ban and remove target presence: %+v", kickResp.GetRoom()) } presenceResp, err := svc.VerifyRoomPresence(ctx, &roomv1.VerifyRoomPresenceRequest{ AppCode: appcode.Default, RoomId: roomID, UserId: viewerID, }) if err != nil { t.Fatalf("verify room presence after kick failed: %v", err) } if presenceResp.GetPresent() || presenceResp.GetReason() != "user_banned" { t.Fatalf("kicked user must not pass IM presence guard: %+v", presenceResp) } if _, err := svc.JoinRoom(ctx, &roomv1.JoinRoomRequest{ Meta: roomservice.NewRequestMeta(roomID, viewerID), Role: "audience", }); err == nil { t.Fatalf("kicked user must not join before unban") } } func findRoomUser(snapshot *roomv1.RoomSnapshot, userID int64) *roomv1.RoomUser { for _, user := range snapshot.GetOnlineUsers() { if user.GetUserId() == userID { return user } } return nil } func containsInt64(values []int64, target int64) bool { for _, value := range values { if value == target { return true } } return false }