550 lines
22 KiB
Go
550 lines
22 KiB
Go
package roomrps
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"testing"
|
|
"time"
|
|
|
|
gamev1 "hyapp.local/api/proto/game/v1"
|
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
|
"hyapp/pkg/xerr"
|
|
)
|
|
|
|
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.mp4", "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),
|
|
})
|
|
wallet := newFakeRoomRPSWallet(map[int64]int64{42: 10})
|
|
svc := New(Config{}, nil, nil, catalog, wallet)
|
|
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)
|
|
}
|
|
if challenge.GetInitiator().GetBalanceAfter() != 9 || wallet.balances[42] != 9 {
|
|
t.Fatalf("initiator balance snapshot mismatch: %+v", challenge.GetInitiator())
|
|
}
|
|
}
|
|
|
|
func TestCreateChallengeRejectsInsufficientCoinBalance(t *testing.T) {
|
|
catalog := newFakeGiftCatalog(map[string]*walletv1.GiftConfig{
|
|
"Gifi-3": roomRPSWalletGift("Gifi-3", "Tiger", "https://cdn.example.test/tiger.png", "", "", 100),
|
|
"Gifi-2": roomRPSWalletGift("Gifi-2", "Cowboy", "https://cdn.example.test/cowboy.png", "", "", 200),
|
|
"Gifi-1": roomRPSWalletGift("Gifi-1", "Warrior", "https://cdn.example.test/warrior.png", "", "", 300),
|
|
"lw1": roomRPSWalletGift("lw1", "Lucky Wheel", "https://cdn.example.test/lw1.png", "", "", 400),
|
|
})
|
|
wallet := newFakeRoomRPSWallet(map[int64]int64{42: 99})
|
|
svc := New(Config{}, nil, nil, catalog, wallet)
|
|
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)
|
|
}
|
|
|
|
_, _, err = svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-create-low-balance"},
|
|
UserId: 42,
|
|
RoomId: "room-1",
|
|
GiftIdText: "Gifi-3",
|
|
Gesture: "rock",
|
|
})
|
|
if !xerr.IsCode(err, xerr.InsufficientBalance) {
|
|
t.Fatalf("CreateChallenge() error = %v, want InsufficientBalance", err)
|
|
}
|
|
if len(svc.challenges) != 0 {
|
|
t.Fatalf("insufficient balance must not create pending challenge: %+v", svc.challenges)
|
|
}
|
|
if wallet.balances[42] != 99 || len(wallet.applies) != 1 {
|
|
t.Fatalf("insufficient create must not mutate wallet balance: balance=%d applies=%d", wallet.balances[42], len(wallet.applies))
|
|
}
|
|
}
|
|
|
|
func TestRoomRPSConfigPersistsAcrossServiceInstances(t *testing.T) {
|
|
store := newFakeRoomRPSConfigStore()
|
|
svc := New(Config{}, nil, nil, store)
|
|
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: "persist-1", GiftName: "Persist One", GiftIconUrl: "https://cdn.example.test/one.png", GiftPriceCoin: 11, Enabled: true, SortOrder: 1},
|
|
{GiftIdText: "persist-2", GiftName: "Persist Two", GiftIconUrl: "https://cdn.example.test/two.png", GiftPriceCoin: 22, Enabled: true, SortOrder: 2},
|
|
{GiftIdText: "persist-3", GiftName: "Persist Three", GiftIconUrl: "https://cdn.example.test/three.png", GiftPriceCoin: 33, Enabled: true, SortOrder: 3},
|
|
{GiftIdText: "persist-4", GiftName: "Persist Four", GiftIconUrl: "https://cdn.example.test/four.png", GiftPriceCoin: 44, Enabled: true, SortOrder: 4},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("UpdateConfig() error = %v", err)
|
|
}
|
|
if store.upserts != 1 {
|
|
t.Fatalf("expected one persistent upsert, got %d", store.upserts)
|
|
}
|
|
|
|
restarted := New(Config{}, nil, nil, store)
|
|
restarted.now = func() time.Time { return time.UnixMilli(1770000060000) }
|
|
config, _, err := restarted.GetConfig(context.Background(), "lalu")
|
|
if err != nil {
|
|
t.Fatalf("GetConfig() after restart error = %v", err)
|
|
}
|
|
if got := config.GetStakeGifts()[0]; got.GetGiftIdText() != "persist-1" || got.GetGiftPriceCoin() != 11 {
|
|
t.Fatalf("persistent config was not reused after restart: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestCreateChallengeUsesPersistentRoomRPSConfig(t *testing.T) {
|
|
store := newFakeRoomRPSConfigStore()
|
|
store.configs["lalu:"+GameID] = &gamev1.RoomRPSConfig{
|
|
AppCode: "lalu",
|
|
GameId: GameID,
|
|
Status: "active",
|
|
ChallengeTimeoutMs: 600000,
|
|
RevealCountdownMs: 3000,
|
|
StakeGifts: []*gamev1.RoomRPSStakeGift{
|
|
{GiftIdText: "db-gift", GiftName: "DB Gift", GiftIconUrl: "https://cdn.example.test/db.png", GiftPriceCoin: 222, Enabled: true, SortOrder: 1},
|
|
{GiftIdText: "db-gift-2", GiftName: "DB Gift 2", GiftIconUrl: "https://cdn.example.test/db2.png", GiftPriceCoin: 333, Enabled: true, SortOrder: 2},
|
|
{GiftIdText: "db-gift-3", GiftName: "DB Gift 3", GiftIconUrl: "https://cdn.example.test/db3.png", GiftPriceCoin: 444, Enabled: true, SortOrder: 3},
|
|
{GiftIdText: "db-gift-4", GiftName: "DB Gift 4", GiftIconUrl: "https://cdn.example.test/db4.png", GiftPriceCoin: 555, Enabled: true, SortOrder: 4},
|
|
},
|
|
CreatedAtMs: 1000,
|
|
UpdatedAtMs: 2000,
|
|
}
|
|
wallet := newFakeRoomRPSWallet(map[int64]int64{42: 1000})
|
|
svc := New(Config{}, nil, nil, store, wallet)
|
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
|
|
|
challenge, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu"},
|
|
UserId: 42,
|
|
RoomId: "room-1",
|
|
GiftIdText: "db-gift",
|
|
Gesture: "rock",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateChallenge() error = %v", err)
|
|
}
|
|
if challenge.GetStakeGiftIdText() != "db-gift" || challenge.GetStakeCoin() != 222 {
|
|
t.Fatalf("challenge did not use persistent config: %+v", challenge)
|
|
}
|
|
if challenge.GetInitiator().GetBalanceAfter() != 778 || wallet.balances[42] != 778 {
|
|
t.Fatalf("persistent config challenge did not debit stake: challenge=%+v wallet=%d", challenge, wallet.balances[42])
|
|
}
|
|
}
|
|
|
|
func TestAcceptChallengeRejectsInsufficientCoinBalance(t *testing.T) {
|
|
wallet := newFakeRoomRPSWallet(map[int64]int64{41: 1000, 42: 99})
|
|
svc := New(Config{}, nil, nil, wallet)
|
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
|
|
|
created, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-create"},
|
|
UserId: 41,
|
|
RoomId: "room-1",
|
|
GiftId: 10001,
|
|
Gesture: "rock",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateChallenge() error = %v", err)
|
|
}
|
|
|
|
_, _, err = svc.AcceptChallenge(context.Background(), &gamev1.AcceptRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-accept-low-balance"},
|
|
UserId: 42,
|
|
ChallengeId: created.GetChallengeId(),
|
|
Gesture: "paper",
|
|
})
|
|
if !xerr.IsCode(err, xerr.InsufficientBalance) {
|
|
t.Fatalf("AcceptChallenge() error = %v, want InsufficientBalance", err)
|
|
}
|
|
current, _, err := svc.GetChallenge(context.Background(), "lalu", created.GetChallengeId())
|
|
if err != nil {
|
|
t.Fatalf("GetChallenge() error = %v", err)
|
|
}
|
|
if current.GetStatus() != StatusPending || current.GetChallenger().GetUserId() != 0 {
|
|
t.Fatalf("insufficient balance must leave challenge pending without challenger: %+v", current)
|
|
}
|
|
if wallet.balances[41] != 900 || wallet.balances[42] != 99 {
|
|
t.Fatalf("accept failure should keep initiator debit and avoid challenger debit: balances=%+v", wallet.balances)
|
|
}
|
|
}
|
|
|
|
func TestAcceptChallengeRecordsChallengerCoinBalance(t *testing.T) {
|
|
wallet := newFakeRoomRPSWallet(map[int64]int64{41: 1000, 42: 500})
|
|
svc := New(Config{}, nil, nil, wallet)
|
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
|
|
|
created, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-create"},
|
|
UserId: 41,
|
|
RoomId: "room-1",
|
|
GiftId: 10001,
|
|
Gesture: "rock",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateChallenge() error = %v", err)
|
|
}
|
|
|
|
accepted, _, err := svc.AcceptChallenge(context.Background(), &gamev1.AcceptRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-accept"},
|
|
UserId: 42,
|
|
ChallengeId: created.GetChallengeId(),
|
|
Gesture: "paper",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("AcceptChallenge() error = %v", err)
|
|
}
|
|
if accepted.GetStatus() != StatusFinished || accepted.GetSettlementStatus() != SettlementSettled || accepted.GetInitiator().GetBalanceAfter() != 900 || accepted.GetChallenger().GetBalanceAfter() != 500 {
|
|
t.Fatalf("accepted challenge balance snapshot mismatch: %+v", accepted)
|
|
}
|
|
if wallet.balances[41] != 900 || wallet.balances[42] != 500 {
|
|
t.Fatalf("winner refund and loser debit mismatch: balances=%+v", wallet.balances)
|
|
}
|
|
if got := wallet.ops(); fmt.Sprint(got) != "[debit debit refund]" {
|
|
t.Fatalf("winner settlement should only debit both and refund winner, got ops=%v", got)
|
|
}
|
|
}
|
|
|
|
func TestAcceptChallengeDrawRefundsBothPlayers(t *testing.T) {
|
|
wallet := newFakeRoomRPSWallet(map[int64]int64{41: 1000, 42: 500})
|
|
svc := New(Config{}, nil, nil, wallet)
|
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
|
|
|
created, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-create-draw"},
|
|
UserId: 41,
|
|
RoomId: "room-1",
|
|
GiftId: 10001,
|
|
Gesture: "rock",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateChallenge() error = %v", err)
|
|
}
|
|
|
|
accepted, _, err := svc.AcceptChallenge(context.Background(), &gamev1.AcceptRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-accept-draw"},
|
|
UserId: 42,
|
|
ChallengeId: created.GetChallengeId(),
|
|
Gesture: "rock",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("AcceptChallenge() error = %v", err)
|
|
}
|
|
if accepted.GetStatus() != StatusFinished || accepted.GetSettlementStatus() != SettlementRefunded || accepted.GetWinnerUserId() != 0 {
|
|
t.Fatalf("draw challenge settlement mismatch: %+v", accepted)
|
|
}
|
|
if accepted.GetInitiator().GetBalanceAfter() != 1000 || accepted.GetChallenger().GetBalanceAfter() != 500 {
|
|
t.Fatalf("draw must refund both players in response: %+v", accepted)
|
|
}
|
|
if wallet.balances[41] != 1000 || wallet.balances[42] != 500 {
|
|
t.Fatalf("draw must refund both players in wallet: balances=%+v", wallet.balances)
|
|
}
|
|
if got := wallet.ops(); fmt.Sprint(got) != "[debit debit refund refund]" {
|
|
t.Fatalf("draw should only debit both and refund both without gift op, got ops=%v", got)
|
|
}
|
|
}
|
|
|
|
func TestExpireChallengeRefundsInitiatorDebit(t *testing.T) {
|
|
wallet := newFakeRoomRPSWallet(map[int64]int64{41: 1000})
|
|
svc := New(Config{}, nil, nil, wallet)
|
|
svc.now = func() time.Time { return time.UnixMilli(1770000000000) }
|
|
|
|
created, _, err := svc.CreateChallenge(context.Background(), &gamev1.CreateRoomRPSChallengeRequest{
|
|
Meta: &gamev1.RequestMeta{AppCode: "lalu", RequestId: "req-create-expire"},
|
|
UserId: 41,
|
|
RoomId: "room-1",
|
|
GiftId: 10001,
|
|
Gesture: "rock",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("CreateChallenge() error = %v", err)
|
|
}
|
|
if wallet.balances[41] != 900 || created.GetInitiator().GetBalanceAfter() != 900 {
|
|
t.Fatalf("create should debit initiator before pending challenge: challenge=%+v wallet=%d", created, wallet.balances[41])
|
|
}
|
|
|
|
expired, _, err := svc.ExpireChallenge(context.Background(), "lalu", created.GetChallengeId())
|
|
if err != nil {
|
|
t.Fatalf("ExpireChallenge() error = %v", err)
|
|
}
|
|
if expired.GetStatus() != StatusTimeout || expired.GetSettlementStatus() != SettlementRefunded || expired.GetInitiator().GetBalanceAfter() != 1000 {
|
|
t.Fatalf("expired challenge should refund initiator: %+v", expired)
|
|
}
|
|
if wallet.balances[41] != 1000 {
|
|
t.Fatalf("expired challenge wallet balance mismatch: %d", wallet.balances[41])
|
|
}
|
|
if got := wallet.ops(); fmt.Sprint(got) != "[debit refund]" {
|
|
t.Fatalf("expire should only debit then refund initiator, got ops=%v", got)
|
|
}
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
type fakeRoomRPSWallet struct {
|
|
balances map[int64]int64
|
|
requests []*walletv1.GetBalancesRequest
|
|
applies []*walletv1.ApplyGameCoinChangeRequest
|
|
replays map[string]*walletv1.ApplyGameCoinChangeResponse
|
|
}
|
|
|
|
func newFakeRoomRPSWallet(balances map[int64]int64) *fakeRoomRPSWallet {
|
|
return &fakeRoomRPSWallet{balances: balances, replays: make(map[string]*walletv1.ApplyGameCoinChangeResponse)}
|
|
}
|
|
|
|
func (f *fakeRoomRPSWallet) GetBalances(_ context.Context, req *walletv1.GetBalancesRequest) (*walletv1.GetBalancesResponse, error) {
|
|
f.requests = append(f.requests, req)
|
|
return &walletv1.GetBalancesResponse{Balances: []*walletv1.AssetBalance{{
|
|
AppCode: req.GetAppCode(),
|
|
AssetType: "COIN",
|
|
AvailableAmount: f.balances[req.GetUserId()],
|
|
}}}, nil
|
|
}
|
|
|
|
func (f *fakeRoomRPSWallet) ApplyGameCoinChange(_ context.Context, req *walletv1.ApplyGameCoinChangeRequest) (*walletv1.ApplyGameCoinChangeResponse, error) {
|
|
f.applies = append(f.applies, req)
|
|
if replay := f.replays[req.GetCommandId()]; replay != nil {
|
|
return replay, nil
|
|
}
|
|
switch req.GetOpType() {
|
|
case roomRPSOpDebit:
|
|
if f.balances[req.GetUserId()] < req.GetCoinAmount() {
|
|
return nil, xerr.New(xerr.InsufficientBalance, "insufficient balance")
|
|
}
|
|
f.balances[req.GetUserId()] -= req.GetCoinAmount()
|
|
case roomRPSOpRefund:
|
|
f.balances[req.GetUserId()] += req.GetCoinAmount()
|
|
default:
|
|
return nil, xerr.New(xerr.InvalidArgument, "unsupported op_type")
|
|
}
|
|
resp := &walletv1.ApplyGameCoinChangeResponse{
|
|
WalletTransactionId: "wtx-" + req.GetCommandId(),
|
|
BalanceAfter: f.balances[req.GetUserId()],
|
|
}
|
|
f.replays[req.GetCommandId()] = resp
|
|
return resp, nil
|
|
}
|
|
|
|
func (f *fakeRoomRPSWallet) ops() []string {
|
|
out := make([]string, 0, len(f.applies))
|
|
for _, req := range f.applies {
|
|
out = append(out, req.GetOpType())
|
|
}
|
|
return out
|
|
}
|
|
|
|
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,
|
|
},
|
|
}
|
|
}
|
|
|
|
type fakeRoomRPSConfigStore struct {
|
|
configs map[string]*gamev1.RoomRPSConfig
|
|
upserts int
|
|
}
|
|
|
|
func newFakeRoomRPSConfigStore() *fakeRoomRPSConfigStore {
|
|
return &fakeRoomRPSConfigStore{configs: make(map[string]*gamev1.RoomRPSConfig)}
|
|
}
|
|
|
|
func (f *fakeRoomRPSConfigStore) GetRoomRPSConfig(_ context.Context, appCode string, gameID string) (*gamev1.RoomRPSConfig, error) {
|
|
if gameID == "" {
|
|
gameID = GameID
|
|
}
|
|
config := f.configs[appCode+":"+gameID]
|
|
if config == nil {
|
|
return nil, xerr.New(xerr.NotFound, "room rps config not found")
|
|
}
|
|
return cloneConfig(config), nil
|
|
}
|
|
|
|
func (f *fakeRoomRPSConfigStore) UpsertRoomRPSConfig(_ context.Context, config *gamev1.RoomRPSConfig) (*gamev1.RoomRPSConfig, error) {
|
|
if config.GetGameId() == "" {
|
|
config.GameId = GameID
|
|
}
|
|
f.upserts++
|
|
f.configs[config.GetAppCode()+":"+config.GetGameId()] = cloneConfig(config)
|
|
return cloneConfig(config), nil
|
|
}
|