package luckygift import ( "context" "encoding/json" "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" || config.GiftID != "pool_95" { t.Fatalf("expected pool config scope, got pool=%q gift=%q", config.PoolID, config.GiftID) } } func TestUpsertConfigPreservesPoolScope(t *testing.T) { repository := &fakeLuckyGiftRepository{} service := New(repository) config := DefaultConfig("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" || repository.upserted.GiftID != "pool_98" { t.Fatalf("expected pool scope, got pool=%q gift=%q", repository.upserted.PoolID, repository.upserted.GiftID) } } 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) } } type fakeLuckyGiftRepository struct { getConfigCalls int upserted domain.Config outbox []domain.DrawOutbox grantedDrawID string deliveredOutboxID string } func (r *fakeLuckyGiftRepository) GetLuckyGiftConfig(context.Context, string) (domain.Config, bool, error) { r.getConfigCalls++ return domain.Config{}, false, nil } func (r *fakeLuckyGiftRepository) UpsertLuckyGiftConfig(_ context.Context, config domain.Config, _ int64) (domain.Config, error) { r.upserted = config return config, nil } func (r *fakeLuckyGiftRepository) ListLuckyGiftConfigs(context.Context) ([]domain.Config, 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 } 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 }