package regionimgroup import ( "context" "strings" "testing" "time" "chatapp3-golang/internal/config" "chatapp3-golang/internal/model" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func TestCreateRegionIMGroupSavesActiveGroup(t *testing.T) { service, db := newRegionIMGroupTestService(t) fakeIM := &fakeIMGateway{} service.im = fakeIM resp, err := service.CreateRegionIMGroup(context.Background(), RegionIMGroupRequest{ SysOrigin: "LIKEI", RegionCode: "AR", RegionName: "Arab", }) if err != nil { t.Fatalf("CreateRegionIMGroup() error = %v", err) } if resp.Status != imGroupStatusActive || resp.GroupID != defaultRegionIMGroupID("LIKEI", "AR") { t.Fatalf("CreateRegionIMGroup() response = %+v", resp) } if len(fakeIM.created) != 1 || fakeIM.created[0].groupID != resp.GroupID { t.Fatalf("created groups = %+v, want one %s", fakeIM.created, resp.GroupID) } var row model.VoiceRoomRegionIMGroup if err := db.Where("sys_origin = ? AND region_code = ?", "LIKEI", "AR").First(&row).Error; err != nil { t.Fatalf("load im group: %v", err) } if row.Status != imGroupStatusActive || row.GroupID != resp.GroupID { t.Fatalf("stored im group = %+v", row) } } func TestDefaultRegionIMGroupIDKeepsASCIIRegionReadable(t *testing.T) { got := defaultRegionIMGroupID("LIKEI", "IR") if got != "VRRP_LIKEI_IR" { t.Fatalf("defaultRegionIMGroupID() = %s, want VRRP_LIKEI_IR", got) } } func TestDefaultRegionIMGroupIDKeepsNonASCIIRegionsDistinct(t *testing.T) { middleEast := defaultRegionIMGroupID("LIKEI", "\u4e2d\u4e1c") southAsia := defaultRegionIMGroupID("LIKEI", "\u5357\u4e9a\u533a\u57df") if middleEast == southAsia { t.Fatalf("non-ascii regions share group id %s", middleEast) } if middleEast == "VRRP_LIKEI" || southAsia == "VRRP_LIKEI" { t.Fatalf("non-ascii region group ids must not collapse: %s %s", middleEast, southAsia) } if !strings.HasPrefix(middleEast, "VRRP_LIKEI_REGION_") || !strings.HasPrefix(southAsia, "VRRP_LIKEI_REGION_") { t.Fatalf("group ids = %s %s, want hashed region suffix", middleEast, southAsia) } } func TestDefaultRegionIMGroupNameIsGeneric(t *testing.T) { got := defaultRegionIMGroupName("LIKEI", "IR", "Iran") if got != "region-im-group-LIKEI-Iran" { t.Fatalf("defaultRegionIMGroupName() = %s, want generic region name", got) } if strings.Contains(got, "red-packet") { t.Fatalf("defaultRegionIMGroupName() contains red-packet: %s", got) } } func TestSendRegionBroadcastResolvesRegionAndEnrichesGiftPayload(t *testing.T) { service, db := newRegionIMGroupTestService(t) fakeIM := &fakeIMGateway{} service.im = fakeIM ctx := context.Background() now := time.Now() if err := db.Create(&model.VoiceRoomRegionIMGroup{ ID: 3002, SysOrigin: "LIKEI", RegionCode: "AR", RegionName: "Arab", GroupID: "@region-ar", GroupName: "region-ar", Status: imGroupStatusActive, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed im group: %v", err) } resp, err := service.SendRegionBroadcast(ctx, RegionBroadcastRequest{ SysOrigin: "LIKEI", SenderUserID: flexibleInt64(1001), Type: "SEND_GIFT", Data: map[string]any{ "roomId": "9001", "giftId": "5001", "giftQuantity": 2, "acceptUserIds": []any{"1002"}, }, }) if err != nil { t.Fatalf("SendRegionBroadcast() error = %v", err) } if resp.RegionCode != "AR" || resp.GroupID != "@region-ar" || resp.Type != "SEND_GIFT" { t.Fatalf("SendRegionBroadcast() response = %+v", resp) } if len(fakeIM.sent) != 1 { t.Fatalf("sent messages = %+v, want one region message", fakeIM.sent) } if fakeIM.sent[0].groupID != "@region-ar" { t.Fatalf("sent group = %s, want @region-ar", fakeIM.sent[0].groupID) } body, ok := fakeIM.sent[0].body.(map[string]any) if !ok { t.Fatalf("sent body type = %T", fakeIM.sent[0].body) } if body["type"] != "SEND_GIFT" { t.Fatalf("sent type = %v, want SEND_GIFT", body["type"]) } data, ok := body["data"].(map[string]any) if !ok { t.Fatalf("sent data type = %T", body["data"]) } if data["regionCode"] != "AR" || data["actualAccount"] != "1001" || data["userNickname"] != "sender" || data["acceptNickname"] != "claimer" { t.Fatalf("sent data = %+v", data) } } func TestSendRegionBroadcastPrefersJavaRegionResolver(t *testing.T) { service, db := newRegionIMGroupTestService(t) service.regionResolver = fakeRegionResolver{ regions: map[string]string{"LIKEI|SA": "中东区"}, } fakeIM := &fakeIMGateway{} service.im = fakeIM now := time.Now() if err := db.Create(&model.VoiceRoomRegionIMGroup{ ID: 3003, SysOrigin: "LIKEI", RegionCode: "中东区", RegionName: "Middle East", GroupID: "@region-middle-east", GroupName: "region-middle-east", Status: imGroupStatusActive, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed im group: %v", err) } resp, err := service.SendRegionBroadcast(context.Background(), RegionBroadcastRequest{ SysOrigin: "LIKEI", SenderUserID: flexibleInt64(1001), Type: "SEND_GIFT", Data: map[string]any{"roomId": "9001"}, }) if err != nil { t.Fatalf("SendRegionBroadcast() error = %v", err) } if resp.RegionCode != "中东区" || resp.GroupID != "@region-middle-east" { t.Fatalf("SendRegionBroadcast() response = %+v, want Java region group", resp) } } func newRegionIMGroupTestService(t *testing.T) (*Service, *gorm.DB) { t.Helper() db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.UserBaseInfo{}, &model.VoiceRoomRegionCountry{}, &model.VoiceRoomRegionIMGroup{}, ); err != nil { t.Fatalf("auto migrate: %v", err) } now := time.Now() if err := db.Create(&[]model.UserBaseInfo{ {ID: 1001, Account: "1001", UserNickname: "sender", UserAvatar: "sender.png", OriginSys: "LIKEI", CountryCode: "SA", CountryName: "Saudi Arabia", CreateTime: now}, {ID: 1002, Account: "1002", UserNickname: "claimer", UserAvatar: "claimer.png", OriginSys: "LIKEI", CountryCode: "SA", CountryName: "Saudi Arabia", CreateTime: now}, }).Error; err != nil { t.Fatalf("seed users: %v", err) } if err := db.Create(&model.VoiceRoomRegionCountry{ ID: 1, SysOrigin: "LIKEI", RegionCode: "AR", CountryCode: "SA", Enabled: true, CreateTime: now, UpdateTime: now, }).Error; err != nil { t.Fatalf("seed region: %v", err) } return NewService(config.Config{}, db), db } type fakeIMCreateRecord struct { groupID string groupName string } type fakeIMSendRecord struct { groupID string body any } type fakeIMGateway struct { created []fakeIMCreateRecord sent []fakeIMSendRecord } func (g *fakeIMGateway) CreateAVChatRoom(_ context.Context, groupID string, groupName string) (string, error) { g.created = append(g.created, fakeIMCreateRecord{groupID: groupID, groupName: groupName}) return groupID, nil } func (g *fakeIMGateway) SendCustomGroupMessage(_ context.Context, groupID string, body any) error { g.sent = append(g.sent, fakeIMSendRecord{groupID: groupID, body: body}) return nil } type fakeRegionResolver struct { regions map[string]string err error } func (g fakeRegionResolver) ResolveRegionCodeByCountryCode(_ context.Context, sysOrigin string, countryCode string) (string, error) { if g.err != nil { return "", g.err } key := strings.ToUpper(strings.TrimSpace(sysOrigin)) + "|" + strings.ToUpper(strings.TrimSpace(countryCode)) return strings.TrimSpace(g.regions[key]), nil }