package firstrecharge import ( "context" "testing" "google.golang.org/grpc" walletv1 "hyapp.local/api/proto/wallet/v1" "hyapp/pkg/appcode" domain "hyapp/services/activity-service/internal/domain/firstrecharge" "hyapp/services/activity-service/internal/testutil/mysqltest" ) func TestFirstRechargeRewardConsumesRechargeFactWithMySQL(t *testing.T) { repository := mysqltest.NewRepository(t) wallet := &fakeWallet{} svc := New(repository, wallet) ctx := appcode.WithContext(context.Background(), "lalu") config, err := svc.UpdateConfig(ctx, domain.Config{ Enabled: true, UpdatedByAdminID: 90001, Tiers: []domain.Tier{{ TierCode: "first_099", TierName: "首冲 $0.99", USDMinorAmount: 99, GoogleProductID: "first_recharge_google_099", DiscountPercent: 70, ResourceGroupID: 88001, Status: domain.TierStatusActive, SortOrder: 1, }, { TierCode: "first_199", TierName: "首冲 $1.99", USDMinorAmount: 199, GoogleProductID: "first_recharge_google_199", DiscountPercent: 60, ResourceGroupID: 88002, Status: domain.TierStatusActive, SortOrder: 2, }}, }) if err != nil { t.Fatalf("UpdateConfig failed: %v", err) } if len(config.Tiers) != 2 || config.Tiers[0].TierID <= 0 || config.Tiers[1].TierID <= 0 { t.Fatalf("tier id was not generated: %+v", config.Tiers) } event := domain.RechargeEvent{ AppCode: "lalu", EventID: "evt-recharge-1", EventType: domain.RechargeEventWalletRecorded, TransactionID: "wtx-recharge-1", CommandID: "seller-transfer-1", UserID: 42001, RechargeCoinAmount: 0, RechargeSequence: 2, RechargeType: "google", RechargeUSDMinor: 99, GoogleProductID: "first_recharge_google_099", OccurredAtMS: 1710000000000, } err = svc.ConsumeWalletRechargeEvent(ctx, event) if err != nil { t.Fatalf("ConsumeWalletRechargeEvent failed: %v", err) } claim, exists, err := repository.GetFirstRechargeRewardClaimByUser(ctx, 42001) if err != nil { t.Fatalf("GetFirstRechargeRewardClaimByUser failed: %v", err) } if !exists || claim.Status != domain.StatusGranted || claim.ResourceGroupID != 88001 || claim.GoogleProductID != "first_recharge_google_099" || claim.RechargeCoinAmount != 0 { t.Fatalf("consume result mismatch: exists=%v claim=%+v", exists, claim) } if len(wallet.grants) != 1 || wallet.grants[0].GroupId != 88001 || wallet.grants[0].TargetUserId != 42001 { t.Fatalf("wallet grant mismatch: %+v", wallet.grants) } status, err := svc.GetStatus(ctx, 42001) if err != nil { t.Fatalf("GetStatus failed: %v", err) } if status.Rewarded || len(status.Tiers) != 1 || status.Tiers[0].GoogleProductID != "first_recharge_google_199" || len(status.Claims) != 1 { t.Fatalf("status should keep the unpurchased tier visible: %+v", status) } err = svc.ConsumeWalletRechargeEvent(ctx, event) if err != nil { t.Fatalf("retry ConsumeWalletRechargeEvent failed: %v", err) } if len(wallet.grants) != 1 { t.Fatalf("retry should be idempotent, grants=%d", len(wallet.grants)) } event.EventID = "evt-recharge-2" event.TransactionID = "wtx-recharge-2" event.CommandID = "seller-transfer-2" event.RechargeUSDMinor = 199 event.GoogleProductID = "first_recharge_google_199" err = svc.ConsumeWalletRechargeEvent(ctx, event) if err != nil { t.Fatalf("second recharge ConsumeWalletRechargeEvent failed: %v", err) } if len(wallet.grants) != 2 || wallet.grants[1].GroupId != 88002 { t.Fatalf("second tier should be granted, grants=%+v", wallet.grants) } status, err = svc.GetStatus(ctx, 42001) if err != nil { t.Fatalf("GetStatus after all tiers failed: %v", err) } if !status.Rewarded || len(status.Tiers) != 0 || len(status.Claims) != 2 { t.Fatalf("status should complete after all tiers are purchased: %+v", status) } } type fakeWallet struct { grants []*walletv1.GrantResourceGroupRequest } func (f *fakeWallet) GrantResourceGroup(_ context.Context, req *walletv1.GrantResourceGroupRequest, _ ...grpc.CallOption) (*walletv1.ResourceGrantResponse, error) { f.grants = append(f.grants, req) return &walletv1.ResourceGrantResponse{Grant: &walletv1.ResourceGrant{ AppCode: req.GetAppCode(), GrantId: "grant-first-recharge", CommandId: req.GetCommandId(), TargetUserId: req.GetTargetUserId(), GrantSource: req.GetGrantSource(), Status: "granted", }}, nil }