package service_test import ( "context" "testing" "time" "google.golang.org/protobuf/proto" roomeventsv1 "hyapp.local/api/proto/events/room/v1" 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" ) func TestAdminCreateRobotRoomProjectsOwnerRegionAndCountry(t *testing.T) { ctx := appcode.WithContext(context.Background(), appcode.Default) repository := mysqltest.NewRepository(t) svc := roomservice.New(roomservice.Config{ NodeID: "node-admin-robot-room-owner-region-country-test", LeaseTTL: 10 * time.Second, RankLimit: 20, SnapshotEveryN: 1, }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) resp, err := svc.AdminCreateRobotRoom(ctx, &roomv1.AdminCreateRobotRoomRequest{ Meta: roomservice.NewRequestMeta("", 9001), OwnerRobotUserId: 325379237278126080, CandidateRobotUserIds: []int64{325379237278126080}, MinRobotCount: 1, MaxRobotCount: 1, RoomName: "Robot Owner", RoomAvatar: testRoomCoverURL, VisibleRegionId: 686, OwnerCountryCode: "ae", GiftRule: &roomv1.AdminRobotRoomGiftRule{ GiftIds: []string{"84"}, LuckyGiftIds: []string{"28"}, NormalGiftIntervalMs: 10000, LuckyComboMin: 100, LuckyComboMax: 10000, LuckyPauseMinMs: 5000, LuckyPauseMaxMs: 20000, }, AdminId: 9001, }) if err != nil { t.Fatalf("AdminCreateRobotRoom failed: %v", err) } roomID := resp.GetRoom().GetRoomId() if roomID == "" { t.Fatalf("created robot room id is empty") } entries, err := repository.ListRoomListEntries(ctx, roomservice.RoomListQuery{VisibleRegionID: 686, CountryCode: "AE", Tab: "new", Limit: 10}) if err != nil { t.Fatalf("list robot room entries failed: %v", err) } if len(entries) != 1 || entries[0].RoomID != roomID || entries[0].VisibleRegionID != 686 || entries[0].OwnerCountryCode != "AE" { t.Fatalf("robot room owner region/country projection mismatch: %+v", entries) } } func TestAdminCreateRobotRoomUsesCandidatePoolAndActiveCount(t *testing.T) { ctx := appcode.WithContext(context.Background(), appcode.Default) repository := mysqltest.NewRepository(t) svc := roomservice.New(roomservice.Config{ NodeID: "node-admin-robot-room-pool-test", LeaseTTL: 10 * time.Second, RankLimit: 20, SnapshotEveryN: 1, }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) resp, err := svc.AdminCreateRobotRoom(ctx, &roomv1.AdminCreateRobotRoomRequest{ Meta: roomservice.NewRequestMeta("", 9001), OwnerRobotUserId: 1001, CandidateRobotUserIds: []int64{1001, 1002, 1003, 1004}, MinRobotCount: 2, MaxRobotCount: 2, RoomName: "Robot Pool", RoomAvatar: testRoomCoverURL, VisibleRegionId: 686, OwnerCountryCode: "ae", GiftRule: &roomv1.AdminRobotRoomGiftRule{ GiftIds: []string{"84"}, LuckyGiftIds: []string{"28"}, NormalGiftIntervalMs: 10000, LuckyComboMin: 1, LuckyComboMax: 1, LuckyPauseMinMs: 5000, LuckyPauseMaxMs: 20000, RobotStayMinMs: 180000, RobotStayMaxMs: 600000, RobotReplaceMinMs: 0, RobotReplaceMaxMs: 60000, MaxGiftSenders: 2, }, AdminId: 9001, }) if err != nil { t.Fatalf("AdminCreateRobotRoom failed: %v", err) } roomID := resp.GetRoom().GetRoomId() if got := len(resp.GetRoom().GetRobotUserIds()); got != 4 { t.Fatalf("robot room should persist full candidate pool for replacement, got %d", got) } if resp.GetRoom().GetGiftRule().GetMaxGiftSenders() != 2 { t.Fatalf("max gift senders should round-trip through room config") } snapshot, err := svc.GetRoomSnapshot(ctx, &roomv1.GetRoomSnapshotRequest{ Meta: &roomv1.RequestMeta{AppCode: appcode.Default, RoomId: roomID}, RoomId: roomID, ViewerUserId: 1001, }) if err != nil { t.Fatalf("snapshot failed: %v", err) } if got := len(snapshot.GetRoom().GetOnlineUsers()); got != 2 { t.Fatalf("robot room should only bootstrap active robot count online, got %d", got) } if got := occupiedSeatUserCount(snapshot.GetRoom()); got != 2 { t.Fatalf("robot room should only put active robots on virtual mic, got %d", got) } } func TestAdminCreateRobotRoomMarksRobotJoinOutbox(t *testing.T) { ctx := appcode.WithContext(context.Background(), appcode.Default) repository := mysqltest.NewRepository(t) svc := roomservice.New(roomservice.Config{ NodeID: "node-admin-robot-room-join-outbox-test", LeaseTTL: 10 * time.Second, RankLimit: 20, SnapshotEveryN: 1, }, router.NewMemoryDirectory(), repository, followTestWallet{}, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher()) _, err := svc.AdminCreateRobotRoom(ctx, &roomv1.AdminCreateRobotRoomRequest{ Meta: roomservice.NewRequestMeta("", 9001), OwnerRobotUserId: 2001, CandidateRobotUserIds: []int64{2001}, MinRobotCount: 1, MaxRobotCount: 1, RoomName: "Robot Join Outbox", RoomAvatar: testRoomCoverURL, VisibleRegionId: 686, OwnerCountryCode: "ae", GiftRule: &roomv1.AdminRobotRoomGiftRule{ GiftIds: []string{"84"}, NormalGiftIntervalMs: 10000, }, AdminId: 9001, }) if err != nil { t.Fatalf("AdminCreateRobotRoom failed: %v", err) } records, err := repository.ListPendingOutbox(ctx, 20) if err != nil { t.Fatalf("list pending outbox failed: %v", err) } joinEvents := 0 for _, record := range records { if record.EventType != "RoomUserJoined" { continue } joinEvents++ var joined roomeventsv1.RoomUserJoined if err := proto.Unmarshal(record.Envelope.GetBody(), &joined); err != nil { t.Fatalf("decode room join event failed: %v", err) } if joined.GetUserId() == 2001 && !joined.GetIsRobot() { t.Fatalf("robot join outbox must mark is_robot=true: %+v", &joined) } } if joinEvents == 0 { t.Fatalf("robot room creation should write RoomUserJoined outbox") } } func occupiedSeatUserCount(snapshot *roomv1.RoomSnapshot) int { count := 0 for _, seat := range snapshot.GetMicSeats() { if seat.GetUserId() > 0 { count++ } } return count }