264 lines
9.9 KiB
Go
264 lines
9.9 KiB
Go
package luckygift
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"google.golang.org/grpc"
|
|
walletv1 "hyapp.local/api/proto/wallet/v1"
|
|
"hyapp/pkg/appcode"
|
|
"hyapp/pkg/tencentim"
|
|
broadcastdomain "hyapp/services/activity-service/internal/domain/broadcast"
|
|
domain "hyapp/services/activity-service/internal/domain/luckygift"
|
|
broadcastservice "hyapp/services/activity-service/internal/service/broadcast"
|
|
)
|
|
|
|
func TestGetConfigReturnsPoolDefault(t *testing.T) {
|
|
repository := &fakeLuckyGiftRepository{}
|
|
service := New(repository)
|
|
|
|
config, err := service.GetConfig(appcode.WithContext(context.Background(), "hyapp"), "pool_95")
|
|
if err != nil {
|
|
t.Fatalf("GetConfig returned error: %v", err)
|
|
}
|
|
if repository.getConfigCalls != 1 {
|
|
t.Fatalf("expected repository to be called once, got %d", repository.getConfigCalls)
|
|
}
|
|
if config.PoolID != "pool_95" {
|
|
t.Fatalf("expected pool config scope, got pool=%q", config.PoolID)
|
|
}
|
|
}
|
|
|
|
func TestUpsertConfigPreservesPoolScope(t *testing.T) {
|
|
repository := &fakeLuckyGiftRepository{}
|
|
service := New(repository)
|
|
config := DefaultRuleConfig("hyapp", "pool_98")
|
|
config.Enabled = true
|
|
|
|
if _, err := service.UpsertConfig(appcode.WithContext(context.Background(), "hyapp"), config); err != nil {
|
|
t.Fatalf("UpsertConfig returned error: %v", err)
|
|
}
|
|
if repository.upserted.PoolID != "pool_98" {
|
|
t.Fatalf("expected pool scope, got pool=%q", repository.upserted.PoolID)
|
|
}
|
|
}
|
|
|
|
func TestProcessPendingDrawOutboxCreditsWalletAndPublishesRoomIM(t *testing.T) {
|
|
payload, _ := json.Marshal(map[string]any{
|
|
"event_type": "lucky_gift_drawn",
|
|
"event_id": "lucky_gift_drawn:lucky_draw_test",
|
|
"app_code": "lalu",
|
|
"draw_id": "lucky_draw_test",
|
|
"command_id": "cmd-gift",
|
|
"pool_id": "super_lucky",
|
|
"user_id": 42,
|
|
"sender_user_id": 42,
|
|
"target_user_id": 99,
|
|
"visible_region_id": 210,
|
|
"room_id": "room-1",
|
|
"gift_id": "rose",
|
|
"gift_count": 1,
|
|
"coin_spent": 100,
|
|
"effective_reward_coins": 300,
|
|
"multiplier_ppm": 3000000,
|
|
"stage_feedback": true,
|
|
"created_at_ms": 1760000000000,
|
|
})
|
|
repository := &fakeLuckyGiftRepository{
|
|
outbox: []domain.DrawOutbox{{
|
|
AppCode: "lalu",
|
|
OutboxID: "lucky_lucky_draw_test",
|
|
EventType: "LuckyGiftDrawn",
|
|
PayloadJSON: string(payload),
|
|
}},
|
|
}
|
|
wallet := &fakeLuckyGiftWallet{}
|
|
publisher := &fakeLuckyGiftPublisher{}
|
|
broadcaster := &fakeLuckyGiftRegionBroadcaster{}
|
|
service := New(repository, WithWallet(wallet), WithRoomPublisher(publisher), WithRegionBroadcaster(broadcaster))
|
|
service.SetClock(func() time.Time { return time.UnixMilli(1760000001000).UTC() })
|
|
|
|
processed, err := service.ProcessPendingDrawOutbox(context.Background(), WorkerOptions{WorkerID: "worker-1"})
|
|
if err != nil {
|
|
t.Fatalf("ProcessPendingDrawOutbox failed: %v", err)
|
|
}
|
|
if processed != 1 {
|
|
t.Fatalf("processed count mismatch: %d", processed)
|
|
}
|
|
if wallet.last == nil || wallet.last.GetDrawId() != "lucky_draw_test" || wallet.last.GetTargetUserId() != 42 || wallet.last.GetAmount() != 300 {
|
|
t.Fatalf("wallet reward request mismatch: %+v", wallet.last)
|
|
}
|
|
if publisher.last.GroupID != "room-1" || publisher.last.Desc != "lucky_gift_drawn" || publisher.last.EventID != "lucky_gift_drawn:lucky_draw_test" {
|
|
t.Fatalf("room im message mismatch: %+v", publisher.last)
|
|
}
|
|
if broadcaster.last.EventID != "lucky_gift_big_win:lucky_draw_test" || broadcaster.last.BroadcastType != broadcastdomain.TypeLuckyGiftBigWin || broadcaster.last.RegionID != 210 {
|
|
t.Fatalf("region lucky broadcast mismatch: %+v", broadcaster.last)
|
|
}
|
|
var broadcastPayload map[string]any
|
|
if err := json.Unmarshal([]byte(broadcaster.last.PayloadJSON), &broadcastPayload); err != nil {
|
|
t.Fatalf("decode region broadcast payload failed: %v", err)
|
|
}
|
|
if broadcastPayload["event_type"] != "lucky_gift_drawn" || broadcastPayload["broadcast_type"] != broadcastdomain.TypeLuckyGiftBigWin || broadcastPayload["room_id"] != "room-1" {
|
|
t.Fatalf("region broadcast payload mismatch: %+v", broadcastPayload)
|
|
}
|
|
if repository.grantedDrawID != "lucky_draw_test" || repository.deliveredOutboxID != "lucky_lucky_draw_test" {
|
|
t.Fatalf("repository settlement mismatch: granted=%s delivered=%s", repository.grantedDrawID, repository.deliveredOutboxID)
|
|
}
|
|
}
|
|
|
|
func TestProcessPendingBatchDrawOutboxCreditsWalletOnceAndGrantsAllDraws(t *testing.T) {
|
|
payload, _ := json.Marshal(map[string]any{
|
|
"event_type": "lucky_gift_drawn",
|
|
"event_id": "lucky_gift_drawn:lucky_draw_batch_1",
|
|
"app_code": "lalu",
|
|
"draw_id": "lucky_draw_batch_1",
|
|
"draw_ids": []string{"lucky_draw_batch_1", "lucky_draw_batch_2", "lucky_draw_batch_3"},
|
|
"command_id": "cmd-gift-batch",
|
|
"pool_id": "lucky_100",
|
|
"user_id": 42,
|
|
"sender_user_id": 42,
|
|
"target_user_id": 99,
|
|
"room_id": "room-1",
|
|
"gift_id": "rose",
|
|
"gift_count": 3,
|
|
"coin_spent": 300,
|
|
"effective_reward_coins": 800,
|
|
"multiplier_ppm": 8000000,
|
|
"created_at_ms": 1760000000000,
|
|
})
|
|
repository := &fakeLuckyGiftRepository{
|
|
outbox: []domain.DrawOutbox{{
|
|
AppCode: "lalu",
|
|
OutboxID: "lucky_lucky_draw_batch_1",
|
|
EventType: "LuckyGiftDrawn",
|
|
PayloadJSON: string(payload),
|
|
}},
|
|
}
|
|
wallet := &fakeLuckyGiftWallet{}
|
|
publisher := &fakeLuckyGiftPublisher{}
|
|
service := New(repository, WithWallet(wallet), WithRoomPublisher(publisher))
|
|
service.SetClock(func() time.Time { return time.UnixMilli(1760000001000).UTC() })
|
|
|
|
processed, err := service.ProcessPendingDrawOutbox(context.Background(), WorkerOptions{WorkerID: "worker-1"})
|
|
if err != nil {
|
|
t.Fatalf("ProcessPendingDrawOutbox failed: %v", err)
|
|
}
|
|
if processed != 1 {
|
|
t.Fatalf("processed count mismatch: %d", processed)
|
|
}
|
|
if wallet.last == nil || wallet.last.GetAmount() != 800 || wallet.last.GetDrawId() != "lucky_draw_batch_1" || wallet.last.GetCommandId() != "lucky_reward:lucky_draw_batch_1" {
|
|
t.Fatalf("wallet should receive one aggregate reward request: %+v", wallet.last)
|
|
}
|
|
if got := strings.Join(repository.grantedDrawIDs, ","); got != "lucky_draw_batch_1,lucky_draw_batch_2,lucky_draw_batch_3" {
|
|
t.Fatalf("all sub draws should be granted together, got %s", got)
|
|
}
|
|
}
|
|
|
|
type fakeLuckyGiftRepository struct {
|
|
getConfigCalls int
|
|
upserted domain.RuleConfig
|
|
outbox []domain.DrawOutbox
|
|
|
|
grantedDrawID string
|
|
grantedDrawIDs []string
|
|
deliveredOutboxID string
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) GetLuckyGiftRuleConfig(context.Context, string) (domain.RuleConfig, bool, error) {
|
|
r.getConfigCalls++
|
|
return domain.RuleConfig{}, false, nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) PublishLuckyGiftRuleConfig(_ context.Context, config domain.RuleConfig, _ int64) (domain.RuleConfig, error) {
|
|
r.upserted = config
|
|
return config, nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) ListLuckyGiftRuleConfigs(context.Context) ([]domain.RuleConfig, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) CheckLuckyGift(context.Context, domain.CheckCommand) (domain.CheckResult, error) {
|
|
return domain.CheckResult{}, nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) ExecuteLuckyGiftDraw(context.Context, domain.DrawCommand, int64) (domain.DrawResult, error) {
|
|
return domain.DrawResult{}, nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) ListLuckyGiftDraws(context.Context, domain.DrawQuery) ([]domain.DrawResult, int64, error) {
|
|
return nil, 0, nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) GetLuckyGiftDrawSummary(context.Context, domain.DrawQuery) (domain.DrawSummary, error) {
|
|
return domain.DrawSummary{}, nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) ClaimPendingLuckyGiftOutbox(context.Context, string, int64, time.Duration, int) ([]domain.DrawOutbox, error) {
|
|
return r.outbox, nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) MarkLuckyGiftOutboxDelivered(_ context.Context, event domain.DrawOutbox, _ int64) error {
|
|
r.deliveredOutboxID = event.OutboxID
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) MarkLuckyGiftOutboxRetryable(context.Context, domain.DrawOutbox, int, int64, string, int64) error {
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) MarkLuckyGiftOutboxFailed(context.Context, domain.DrawOutbox, int, string, int64) error {
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) MarkLuckyGiftDrawGranted(_ context.Context, _ string, drawID string, _ string, _ int64) error {
|
|
r.grantedDrawID = drawID
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) MarkLuckyGiftDrawFailed(context.Context, string, string, string, int64) error {
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) MarkLuckyGiftDrawsGranted(_ context.Context, _ string, drawIDs []string, _ string, _ int64) error {
|
|
r.grantedDrawIDs = append([]string(nil), drawIDs...)
|
|
if len(drawIDs) > 0 {
|
|
r.grantedDrawID = drawIDs[0]
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *fakeLuckyGiftRepository) MarkLuckyGiftDrawsFailed(context.Context, string, []string, string, int64) error {
|
|
return nil
|
|
}
|
|
|
|
type fakeLuckyGiftWallet struct {
|
|
last *walletv1.CreditLuckyGiftRewardRequest
|
|
}
|
|
|
|
func (w *fakeLuckyGiftWallet) CreditLuckyGiftReward(_ context.Context, req *walletv1.CreditLuckyGiftRewardRequest, _ ...grpc.CallOption) (*walletv1.CreditLuckyGiftRewardResponse, error) {
|
|
w.last = req
|
|
return &walletv1.CreditLuckyGiftRewardResponse{TransactionId: "wallet_tx_lucky", Amount: req.GetAmount()}, nil
|
|
}
|
|
|
|
type fakeLuckyGiftPublisher struct {
|
|
last tencentim.CustomGroupMessage
|
|
}
|
|
|
|
func (p *fakeLuckyGiftPublisher) PublishGroupCustomMessage(_ context.Context, message tencentim.CustomGroupMessage) error {
|
|
p.last = message
|
|
return nil
|
|
}
|
|
|
|
type fakeLuckyGiftRegionBroadcaster struct {
|
|
last broadcastservice.PublishInput
|
|
}
|
|
|
|
func (b *fakeLuckyGiftRegionBroadcaster) PublishRegionBroadcast(_ context.Context, input broadcastservice.PublishInput) (broadcastdomain.PublishResult, error) {
|
|
b.last = input
|
|
return broadcastdomain.PublishResult{EventID: input.EventID, Status: broadcastdomain.StatusPending, Created: true}, nil
|
|
}
|