package roomrps import ( "context" "testing" "time" gamev1 "hyapp.local/api/proto/game/v1" walletv1 "hyapp.local/api/proto/wallet/v1" ) func TestUpdateConfigEnrichesStakeGiftsFromWalletCatalog(t *testing.T) { catalog := newFakeGiftCatalog(map[string]*walletv1.GiftConfig{ "2001": roomRPSWalletGift("2001", "Rose", "https://cdn.example.test/rose.png", "", "https://cdn.example.test/rose.mp4", 100), "2002": roomRPSWalletGift("2002", "Bell", "", "https://cdn.example.test/bell-preview.png", "https://cdn.example.test/bell.svga", 300), "2003": roomRPSWalletGift("2003", "Crown", "", "", "https://cdn.example.test/crown.svga", 500), "2004": roomRPSWalletGift("2004", "Cup", "https://cdn.example.test/cup.png", "", "", 1000), }) svc := New(Config{}, nil, nil, catalog) svc.now = func() time.Time { return time.UnixMilli(1770000000000) } config, _, err := svc.UpdateConfig(context.Background(), "lalu", &gamev1.RoomRPSConfig{ Status: "active", ChallengeTimeoutMs: 600000, RevealCountdownMs: 3000, StakeGifts: []*gamev1.RoomRPSStakeGift{ {GiftIdText: "2001", Enabled: true, SortOrder: 1}, {GiftIdText: "2002", Enabled: true, SortOrder: 2}, {GiftIdText: "2003", Enabled: true, SortOrder: 3}, {GiftIdText: "2004", Enabled: true, SortOrder: 4}, }, }) if err != nil { t.Fatalf("UpdateConfig() error = %v", err) } if got := config.GetStakeGifts()[0]; got.GetGiftName() != "Rose" || got.GetGiftIconUrl() != "https://cdn.example.test/rose.png" || got.GetGiftAnimationUrl() != "https://cdn.example.test/rose.mp4" || got.GetGiftPriceCoin() != 100 { t.Fatalf("first gift was not enriched from wallet catalog: %+v", got) } if got := config.GetStakeGifts()[1]; got.GetGiftIconUrl() != "https://cdn.example.test/bell-preview.png" || got.GetGiftAnimationUrl() != "https://cdn.example.test/bell.svga" { t.Fatalf("second gift should use preview_url as icon and animation_url as animation: %+v", got) } if got := config.GetStakeGifts()[2]; got.GetGiftIconUrl() != "" || got.GetGiftAnimationUrl() != "https://cdn.example.test/crown.svga" { t.Fatalf("third gift should not use animation_url as display icon: %+v", got) } if len(catalog.requests) != 4 { t.Fatalf("expected one exact catalog lookup per missing gift, got %d", len(catalog.requests)) } if req := catalog.requests[0]; req.GetAppCode() != "lalu" || req.GetKeyword() != "2001" || req.GetPage() != 1 || req.GetPageSize() != 100 { t.Fatalf("catalog lookup request mismatch: %+v", req) } if got := config.GetStakeGifts()[0]; got.GetGiftIdText() != "2001" || got.GetGiftId() != 2001 { t.Fatalf("numeric gift should keep string primary ID and legacy number: %+v", got) } } func TestGetConfigBackfillsLegacyEmptyGiftSnapshot(t *testing.T) { catalog := newFakeGiftCatalog(map[string]*walletv1.GiftConfig{ "Gifi-3": roomRPSWalletGift("Gifi-3", "Rocket", "https://cdn.example.test/rocket.png", "", "", 900), }) svc := New(Config{}, nil, nil, catalog) svc.now = func() time.Time { return time.UnixMilli(1770000000000) } svc.configs["lalu"] = &gamev1.RoomRPSConfig{ AppCode: "lalu", GameId: GameID, Status: "active", ChallengeTimeoutMs: 600000, RevealCountdownMs: 3000, StakeGifts: []*gamev1.RoomRPSStakeGift{ {GiftIdText: "Gifi-3", Enabled: true, SortOrder: 1}, {GiftId: 10002, Enabled: true, SortOrder: 2}, {GiftId: 10003, Enabled: true, SortOrder: 3}, {GiftId: 10004, Enabled: true, SortOrder: 4}, }, CreatedAtMs: 1000, UpdatedAtMs: 2000, } config, _, err := svc.GetConfig(context.Background(), "lalu") if err != nil { t.Fatalf("GetConfig() error = %v", err) } if got := config.GetStakeGifts()[0]; got.GetGiftName() != "Rocket" || got.GetGiftIconUrl() != "https://cdn.example.test/rocket.png" || got.GetGiftPriceCoin() != 900 { t.Fatalf("legacy config response was not enriched: %+v", got) } if cached := svc.configs["lalu"].GetStakeGifts()[0]; cached.GetGiftIconUrl() == "" { t.Fatalf("legacy config was not backfilled into service cache: %+v", cached) } } func TestGetConfigMovesLegacyAnimatedIconIntoAnimationURL(t *testing.T) { catalog := newFakeGiftCatalog(map[string]*walletv1.GiftConfig{ "Gifi-3": roomRPSWalletGift("Gifi-3", "Rocket", "https://cdn.example.test/rocket-cover.png", "", "https://cdn.example.test/rocket.mp4", 900), }) svc := New(Config{}, nil, nil, catalog) svc.now = func() time.Time { return time.UnixMilli(1770000000000) } svc.configs["lalu"] = &gamev1.RoomRPSConfig{ AppCode: "lalu", GameId: GameID, Status: "active", ChallengeTimeoutMs: 600000, RevealCountdownMs: 3000, StakeGifts: []*gamev1.RoomRPSStakeGift{ {GiftIdText: "Gifi-3", GiftName: "Rocket", GiftIconUrl: "https://cdn.example.test/rocket.mp4", GiftPriceCoin: 900, Enabled: true, SortOrder: 1}, {GiftId: 10002, Enabled: true, SortOrder: 2}, {GiftId: 10003, Enabled: true, SortOrder: 3}, {GiftId: 10004, Enabled: true, SortOrder: 4}, }, CreatedAtMs: 1000, UpdatedAtMs: 2000, } config, _, err := svc.GetConfig(context.Background(), "lalu") if err != nil { t.Fatalf("GetConfig() error = %v", err) } if got := config.GetStakeGifts()[0]; got.GetGiftIconUrl() != "https://cdn.example.test/rocket-cover.png" || got.GetGiftAnimationUrl() != "https://cdn.example.test/rocket.mp4" { t.Fatalf("legacy animated icon was not split into cover and animation fields: %+v", got) } } func TestCreateChallengeMatchesStringGiftID(t *testing.T) { catalog := newFakeGiftCatalog(map[string]*walletv1.GiftConfig{ "Gifi-3": roomRPSWalletGift("Gifi-3", "Tiger", "https://cdn.example.test/tiger.png", "", "", 1), "Gifi-2": roomRPSWalletGift("Gifi-2", "Cowboy", "https://cdn.example.test/cowboy.png", "", "", 1), "Gifi-1": roomRPSWalletGift("Gifi-1", "Warrior", "https://cdn.example.test/warrior.png", "", "", 1), "lw1": roomRPSWalletGift("lw1", "Lucky Wheel", "https://cdn.example.test/lw1.png", "", "", 111), }) svc := New(Config{}, nil, nil, catalog) svc.now = func() time.Time { return time.UnixMilli(1770000000000) } _, _, err := svc.UpdateConfig(context.Background(), "lalu", &gamev1.RoomRPSConfig{ Status: "active", ChallengeTimeoutMs: 600000, RevealCountdownMs: 3000, StakeGifts: []*gamev1.RoomRPSStakeGift{ {GiftIdText: "Gifi-3", Enabled: true, SortOrder: 1}, {GiftIdText: "Gifi-2", Enabled: true, SortOrder: 2}, {GiftIdText: "Gifi-1", Enabled: true, SortOrder: 3}, {GiftIdText: "lw1", Enabled: true, SortOrder: 4}, }, }) if err != nil { t.Fatalf("UpdateConfig() error = %v", err) } challenge, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{ Meta: &gamev1.RequestMeta{AppCode: "lalu"}, UserId: 42, RoomId: "room-1", RegionId: 1001, GiftIdText: "Gifi-3", Gesture: "rock", }) if err != nil { t.Fatalf("CreateChallenge() error = %v", err) } if challenge.GetStakeGiftIdText() != "Gifi-3" || challenge.GetStakeGiftId() != 0 || challenge.GetStakeCoin() != 1 { t.Fatalf("string gift challenge mismatch: %+v", challenge) } } type fakeGiftCatalog struct { gifts map[string]*walletv1.GiftConfig requests []*walletv1.ListGiftConfigsRequest } func newFakeGiftCatalog(gifts map[string]*walletv1.GiftConfig) *fakeGiftCatalog { return &fakeGiftCatalog{gifts: gifts} } func (f *fakeGiftCatalog) ListGiftConfigs(_ context.Context, req *walletv1.ListGiftConfigsRequest) (*walletv1.ListGiftConfigsResponse, error) { f.requests = append(f.requests, req) if gift := f.gifts[req.GetKeyword()]; gift != nil { return &walletv1.ListGiftConfigsResponse{Gifts: []*walletv1.GiftConfig{gift}, Total: 1}, nil } return &walletv1.ListGiftConfigsResponse{Gifts: []*walletv1.GiftConfig{}, Total: 0}, nil } func roomRPSWalletGift(giftID string, name string, assetURL string, previewURL string, animationURL string, coinPrice int64) *walletv1.GiftConfig { return &walletv1.GiftConfig{ AppCode: "lalu", GiftId: giftID, Name: name, Status: "active", CoinPrice: coinPrice, Resource: &walletv1.Resource{ ResourceId: 100, ResourceType: "gift", Name: name, Status: "active", AssetUrl: assetURL, PreviewUrl: previewURL, AnimationUrl: animationURL, }, } }