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_100", TierName: "首冲 100+", MinCoinAmount: 100, MaxCoinAmount: 0, ResourceGroupID: 88001, Status: domain.TierStatusActive, }}, }) if err != nil { t.Fatalf("UpdateConfig failed: %v", err) } if len(config.Tiers) != 1 || config.Tiers[0].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: 500, RechargeSequence: 1, RechargeType: domain.RechargeTypeCoinSeller, 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 { 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 || status.Claim.ClaimID != claim.ClaimID { t.Fatalf("status should include granted claim: %+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.RechargeSequence = 2 err = svc.ConsumeWalletRechargeEvent(ctx, event) if err != nil { t.Fatalf("second recharge ConsumeWalletRechargeEvent failed: %v", err) } if len(wallet.grants) != 1 { t.Fatalf("second recharge should be ignored, grants=%d", len(wallet.grants)) } } 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 }