237 lines
9.2 KiB
Go

package service_test
import (
"context"
"testing"
"time"
"google.golang.org/protobuf/proto"
activityv1 "hyapp.local/api/proto/activity/v1"
roomv1 "hyapp.local/api/proto/room/v1"
walletv1 "hyapp.local/api/proto/wallet/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 luckyGiftTestClient struct {
checks []*activityv1.CheckLuckyGiftRequest
draws []*activityv1.ExecuteLuckyGiftDrawRequest
drawResults []*activityv1.LuckyGiftDrawResult
}
func (c *luckyGiftTestClient) CheckLuckyGift(_ context.Context, req *activityv1.CheckLuckyGiftRequest) (*activityv1.CheckLuckyGiftResponse, error) {
c.checks = append(c.checks, req)
return &activityv1.CheckLuckyGiftResponse{
Enabled: true,
Reason: "enabled",
GiftId: req.GetGiftId(),
PoolId: req.GetPoolId(),
RuleVersion: 12,
ExperiencePool: "novice",
}, nil
}
func (c *luckyGiftTestClient) ExecuteLuckyGiftDraw(_ context.Context, req *activityv1.ExecuteLuckyGiftDrawRequest) (*activityv1.ExecuteLuckyGiftDrawResponse, error) {
c.draws = append(c.draws, req)
meta := req.GetLuckyGift()
if len(c.drawResults) > 0 {
index := len(c.draws) - 1
if index >= len(c.drawResults) {
index = len(c.drawResults) - 1
}
result := proto.Clone(c.drawResults[index]).(*activityv1.LuckyGiftDrawResult)
if result.CommandId == "" {
result.CommandId = meta.GetCommandId()
}
if result.PoolId == "" {
result.PoolId = meta.GetPoolId()
}
if result.GiftId == "" {
result.GiftId = meta.GetGiftId()
}
return &activityv1.ExecuteLuckyGiftDrawResponse{Result: result}, nil
}
return &activityv1.ExecuteLuckyGiftDrawResponse{Result: &activityv1.LuckyGiftDrawResult{
DrawId: "lucky_draw_test",
CommandId: meta.GetCommandId(),
PoolId: meta.GetPoolId(),
GiftId: meta.GetGiftId(),
RuleVersion: 12,
ExperiencePool: "novice",
SelectedTierId: "novice_2x",
MultiplierPpm: 2_000_000,
BaseRewardCoins: 200,
EffectiveRewardCoins: 200,
RewardStatus: "granted",
WalletTransactionId: "wallet_tx_lucky",
CoinBalanceAfter: 8800,
CreatedAtMs: 1_779_258_000_000,
}}, nil
}
func TestSendGiftReturnsLuckyGiftDrawResult(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{{
BillingReceiptId: "receipt-lucky",
CoinSpent: 100,
GiftPointAdded: 100,
HeatValue: 100,
GiftTypeCode: "super_lucky",
}}}
luckyGift := &luckyGiftTestClient{drawResults: []*activityv1.LuckyGiftDrawResult{
{
DrawId: "lucky_draw_target_202",
SelectedTierId: "novice_2x",
MultiplierPpm: 2_000_000,
BaseRewardCoins: 200,
EffectiveRewardCoins: 200,
RewardStatus: "granted",
WalletTransactionId: "wallet_tx_lucky_202",
CoinBalanceAfter: 8800,
CreatedAtMs: 1_779_258_000_000,
},
{
DrawId: "lucky_draw_target_303",
SelectedTierId: "normal_3x",
MultiplierPpm: 3_000_000,
BaseRewardCoins: 300,
EffectiveRewardCoins: 300,
RewardStatus: "granted",
WalletTransactionId: "wallet_tx_lucky_303",
CoinBalanceAfter: 9100,
CreatedAtMs: 1_779_258_000_001,
StageFeedback: true,
HighMultiplier: true,
},
}}
svc := roomservice.New(roomservice.Config{
NodeID: "node-lucky-test",
LeaseTTL: 10 * time.Second,
RankLimit: 20,
SnapshotEveryN: 1,
}, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher(), luckyGift)
roomID := "room-lucky-gift"
ownerID := int64(101)
viewerID := int64(202)
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
joinRocketRoom(t, ctx, svc, roomID, viewerID)
resp, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
Meta: &roomv1.RequestMeta{
RequestId: "req-lucky",
CommandId: "cmd-lucky",
ActorUserId: ownerID,
RoomId: roomID,
SessionId: "device-session-1",
AppCode: appcode.Default,
SentAtMs: time.Date(2026, 5, 20, 12, 0, 0, 0, time.UTC).UnixMilli(),
},
TargetType: "user",
TargetUserId: viewerID,
GiftId: "rose",
GiftCount: 1,
PoolId: "super_lucky",
})
if err != nil {
t.Fatalf("send lucky gift failed: %v", err)
}
if resp.GetLuckyGift().GetDrawId() != "lucky_draw_test" || resp.GetLuckyGift().GetMultiplierPpm() != 2_000_000 || resp.GetLuckyGift().GetEffectiveRewardCoins() != 200 || resp.GetLuckyGift().GetRewardStatus() != "granted" || resp.GetLuckyGift().GetWalletTransactionId() != "wallet_tx_lucky" || resp.GetLuckyGift().GetCoinBalanceAfter() != 8800 {
t.Fatalf("lucky gift result mismatch: %+v", resp.GetLuckyGift())
}
if len(resp.GetLuckyGifts()) != 1 || resp.GetLuckyGifts()[0].GetTargetUserId() != viewerID {
t.Fatalf("single-target lucky_gifts response mismatch: %+v", resp.GetLuckyGifts())
}
if len(luckyGift.checks) != 1 || luckyGift.checks[0].GetPoolId() != "super_lucky" || luckyGift.checks[0].GetGiftId() != "rose" {
t.Fatalf("lucky check request mismatch: %+v", luckyGift.checks)
}
if len(luckyGift.draws) != 1 {
t.Fatalf("expected one lucky draw, got %d", len(luckyGift.draws))
}
drawMeta := luckyGift.draws[0].GetLuckyGift()
if drawMeta.GetCommandId() != "cmd-lucky" ||
drawMeta.GetCoinSpent() != 100 ||
drawMeta.GetDeviceId() != "device-session-1" ||
drawMeta.GetAnchorId() != "101" ||
drawMeta.GetVisibleRegionId() != 9001 {
t.Fatalf("lucky draw meta mismatch: %+v", drawMeta)
}
}
func TestSendGiftReturnsLuckyGiftDrawResultsForMultipleTargets(t *testing.T) {
ctx := context.Background()
repository := mysqltest.NewRepository(t)
wallet := &rocketTestWallet{debits: []*walletv1.DebitGiftResponse{
{BillingReceiptId: "receipt-lucky-202", CoinSpent: 100, ChargeAmount: 100, GiftPointAdded: 100, HeatValue: 100, GiftTypeCode: "super_lucky"},
{BillingReceiptId: "receipt-lucky-303", CoinSpent: 200, ChargeAmount: 200, GiftPointAdded: 200, HeatValue: 200, GiftTypeCode: "super_lucky"},
}}
luckyGift := &luckyGiftTestClient{}
svc := roomservice.New(roomservice.Config{
NodeID: "node-lucky-multi-test",
LeaseTTL: 10 * time.Second,
RankLimit: 20,
SnapshotEveryN: 1,
}, router.NewMemoryDirectory(), repository, wallet, integration.NewNoopRoomEventPublisher(), integration.NewNoopOutboxPublisher(), luckyGift)
roomID := "room-lucky-gift-multi"
ownerID := int64(101)
firstTargetID := int64(202)
secondTargetID := int64(303)
createRocketRoom(t, ctx, svc, roomID, ownerID, 9001)
joinRocketRoom(t, ctx, svc, roomID, firstTargetID)
joinRocketRoom(t, ctx, svc, roomID, secondTargetID)
resp, err := svc.SendGift(ctx, &roomv1.SendGiftRequest{
Meta: &roomv1.RequestMeta{
RequestId: "req-lucky-multi",
CommandId: "cmd-lucky-multi",
ActorUserId: ownerID,
RoomId: roomID,
SessionId: "device-session-multi",
AppCode: appcode.Default,
SentAtMs: time.Date(2026, 5, 20, 12, 0, 0, 0, time.UTC).UnixMilli(),
},
TargetType: "user",
TargetUserIds: []int64{firstTargetID, secondTargetID},
GiftId: "rose",
GiftCount: 1,
PoolId: "super_lucky",
})
if err != nil {
t.Fatalf("send multi-target lucky gift failed: %v", err)
}
if len(luckyGift.checks) != 1 || luckyGift.checks[0].GetPoolId() != "super_lucky" {
t.Fatalf("multi-target lucky gift should check pool once: %+v", luckyGift.checks)
}
if len(luckyGift.draws) != 2 {
t.Fatalf("multi-target lucky gift should draw once per target, got %d", len(luckyGift.draws))
}
firstDraw := luckyGift.draws[0].GetLuckyGift()
secondDraw := luckyGift.draws[1].GetLuckyGift()
if firstDraw.GetCommandId() != "cmd-lucky-multi:target:202" || firstDraw.GetTargetUserId() != firstTargetID || firstDraw.GetCoinSpent() != 100 {
t.Fatalf("first target lucky draw mismatch: %+v", firstDraw)
}
if secondDraw.GetCommandId() != "cmd-lucky-multi:target:303" || secondDraw.GetTargetUserId() != secondTargetID || secondDraw.GetCoinSpent() != 200 {
t.Fatalf("second target lucky draw mismatch: %+v", secondDraw)
}
if resp.GetLuckyGift().GetCommandId() != "cmd-lucky-multi" ||
resp.GetLuckyGift().GetTargetUserId() != 0 ||
resp.GetLuckyGift().GetSelectedTierId() != "batch" ||
resp.GetLuckyGift().GetMultiplierPpm() != 5_000_000 ||
resp.GetLuckyGift().GetEffectiveRewardCoins() != 500 ||
resp.GetLuckyGift().GetWalletTransactionId() != "" ||
resp.GetLuckyGift().GetCoinBalanceAfter() != 9100 ||
!resp.GetLuckyGift().GetStageFeedback() ||
!resp.GetLuckyGift().GetHighMultiplier() ||
len(resp.GetLuckyGifts()) != 2 {
t.Fatalf("multi-target lucky response must include aggregate and list results: %+v", resp)
}
if resp.GetLuckyGifts()[0].GetCommandId() != "cmd-lucky-multi:target:202" || resp.GetLuckyGifts()[0].GetMultiplierPpm() != 2_000_000 || resp.GetLuckyGifts()[1].GetCommandId() != "cmd-lucky-multi:target:303" || resp.GetLuckyGifts()[1].GetMultiplierPpm() != 3_000_000 || resp.GetLuckyGifts()[1].GetTargetUserId() != secondTargetID {
t.Fatalf("multi-target lucky_gifts response mismatch: %+v", resp.GetLuckyGifts())
}
}