package wheel import ( "context" "testing" "time" "google.golang.org/grpc" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" domain "hyapp/services/activity-service/internal/domain/wheel" ) func TestDrawGrantsResourceReward(t *testing.T) { repository := &fakeWheelRepository{executeResult: domain.DrawResult{ DrawID: "wheel_draw_1", DrawIDs: []string{"wheel_draw_1"}, CommandID: "cmd-1", WheelID: "classic", SelectedTierID: "classic-1", RewardType: domain.RewardTypeGift, RewardID: "gift-1", RewardCount: 2, RewardStatus: domain.StatusPending, MetadataJSON: `{"resource_id":326}`, }} wallet := &fakeWheelWallet{} service := New(repository, WithWallet(wallet)) service.SetClock(func() time.Time { return time.UnixMilli(1700000000000).UTC() }) result, err := service.Draw(appcode.WithContext(context.Background(), "yumi"), domain.DrawCommand{ CommandID: "cmd-1", WheelID: "classic", UserID: 1001, DeviceID: "device-1", CoinSpent: 10000, }) if err != nil { t.Fatalf("Draw returned error: %v", err) } if result.RewardStatus != domain.StatusGranted { t.Fatalf("reward status mismatch: %s", result.RewardStatus) } if len(wallet.resourceGrants) != 1 { t.Fatalf("resource grants mismatch: %+v", wallet.resourceGrants) } req := wallet.resourceGrants[0] if req.GetCommandId() != "wheel_resource:wheel_draw_1" || req.GetTargetUserId() != 1001 || req.GetResourceId() != 326 || req.GetQuantity() != 2 { t.Fatalf("grant request mismatch: %+v", req) } if req.GetReason() != "wheel_reward" || req.GetGrantSource() != "wheel_reward" || req.GetOperatorUserId() != 1001 { t.Fatalf("grant audit fields mismatch: %+v", req) } if len(repository.markGrantedDrawIDs) != 1 || repository.markGrantedDrawIDs[0] != "wheel_draw_1" { t.Fatalf("marked draw IDs mismatch: %+v", repository.markGrantedDrawIDs) } } func TestDrawFulfillsBatchCoinAndResourceRewards(t *testing.T) { repository := &fakeWheelRepository{executeResult: domain.DrawResult{ DrawID: "wheel_draw_batch", DrawIDs: []string{"wheel_draw_batch"}, CommandID: "cmd-batch", WheelID: "advanced", RewardType: "batch", RewardStatus: domain.StatusPending, Rewards: []domain.DrawReward{ {SelectedTierID: "advanced-1", RewardType: domain.RewardTypeCoin, RewardCoins: 100}, {SelectedTierID: "advanced-2", RewardType: domain.RewardTypeProp, RewardID: "901", RewardCount: 1, MetadataJSON: `{"resource_id":901}`}, }, }} wallet := &fakeWheelWallet{coinBalanceAfter: 9900} service := New(repository, WithWallet(wallet)) service.SetClock(func() time.Time { return time.UnixMilli(1700000000000).UTC() }) result, err := service.Draw(appcode.WithContext(context.Background(), "yumi"), domain.DrawCommand{ CommandID: "cmd-batch", WheelID: "advanced", UserID: 2002, DeviceID: "device-2", CoinSpent: 50000, DrawCount: 2, }) if err != nil { t.Fatalf("Draw returned error: %v", err) } if result.RewardStatus != domain.StatusGranted { t.Fatalf("reward status mismatch: %s", result.RewardStatus) } if len(wallet.coinCredits) != 1 || wallet.coinCredits[0].GetCommandId() != "wheel_reward:wheel_draw_batch:1" { t.Fatalf("coin credit mismatch: %+v", wallet.coinCredits) } if len(wallet.resourceGrants) != 1 || wallet.resourceGrants[0].GetCommandId() != "wheel_resource:wheel_draw_batch:2" { t.Fatalf("resource grant mismatch: %+v", wallet.resourceGrants) } if result.CoinBalanceAfter != 9900 { t.Fatalf("coin balance mismatch: %d", result.CoinBalanceAfter) } } type fakeWheelRepository struct { executeResult domain.DrawResult markGrantedDrawIDs []string } func (r *fakeWheelRepository) PublishWheelRuleConfig(context.Context, domain.RuleConfig, int64) (domain.RuleConfig, error) { return domain.RuleConfig{}, nil } func (r *fakeWheelRepository) GetWheelRuleConfig(context.Context, string) (domain.RuleConfig, bool, error) { return domain.RuleConfig{}, false, nil } func (r *fakeWheelRepository) ExecuteWheelDraw(context.Context, domain.DrawCommand, int64) (domain.DrawResult, error) { return r.executeResult, nil } func (r *fakeWheelRepository) ListWheelDraws(context.Context, domain.DrawQuery) ([]domain.DrawResult, int64, error) { return nil, 0, nil } func (r *fakeWheelRepository) GetWheelDrawSummary(context.Context, domain.DrawQuery) (domain.DrawSummary, error) { return domain.DrawSummary{}, nil } func (r *fakeWheelRepository) MarkWheelDrawsGranted(_ context.Context, _ string, drawIDs []string, _ string, _ int64) error { r.markGrantedDrawIDs = append([]string(nil), drawIDs...) return nil } type fakeWheelWallet struct { coinCredits []*walletv1.CreditWheelRewardRequest resourceGrants []*walletv1.GrantResourceRequest coinBalanceAfter int64 } func (w *fakeWheelWallet) CreditWheelReward(_ context.Context, req *walletv1.CreditWheelRewardRequest, _ ...grpc.CallOption) (*walletv1.CreditWheelRewardResponse, error) { w.coinCredits = append(w.coinCredits, req) return &walletv1.CreditWheelRewardResponse{ TransactionId: "tx-" + req.GetCommandId(), Balance: &walletv1.AssetBalance{AssetType: "COIN", AvailableAmount: w.coinBalanceAfter}, }, nil } func (w *fakeWheelWallet) GrantResource(_ context.Context, req *walletv1.GrantResourceRequest, _ ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) { w.resourceGrants = append(w.resourceGrants, req) return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{GrantId: "grant-" + req.GetCommandId()}}, nil }