529 lines
20 KiB
Go
529 lines
20 KiB
Go
package invite
|
|
|
|
import (
|
|
"chatapp3-golang/internal/config"
|
|
"chatapp3-golang/internal/integration"
|
|
"chatapp3-golang/internal/model"
|
|
"context"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/alicebob/miniredis/v2"
|
|
"github.com/redis/go-redis/v9"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type inviteTestGateway struct {
|
|
grantGoldErr error
|
|
grantPropsErr error
|
|
grantGoldCalls int
|
|
}
|
|
|
|
func (g *inviteTestGateway) EnsureInviteCode(context.Context, string) (integration.InviteCode, error) {
|
|
return integration.InviteCode{InviteCode: "INV-TEST"}, nil
|
|
}
|
|
|
|
func (g *inviteTestGateway) GetDeviceFingerprint(context.Context, int64) (string, error) {
|
|
return "", nil
|
|
}
|
|
|
|
func (g *inviteTestGateway) GrantGold(context.Context, integration.GrantGoldRequest) error {
|
|
g.grantGoldCalls++
|
|
return g.grantGoldErr
|
|
}
|
|
|
|
func (g *inviteTestGateway) GrantProps(context.Context, integration.GrantPropsRequest) error {
|
|
return g.grantPropsErr
|
|
}
|
|
|
|
func newTestInviteService(t *testing.T, gateway *inviteTestGateway) (*InviteService, *gorm.DB, func()) {
|
|
t.Helper()
|
|
|
|
dbName := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
|
|
db, err := gorm.Open(sqlite.Open("file:"+dbName+"?mode=memory&cache=shared"), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("open sqlite: %v", err)
|
|
}
|
|
if err := db.AutoMigrate(
|
|
&model.InviteCodeMapping{},
|
|
&model.UserBaseInfo{},
|
|
&model.InviteCampaignConfig{},
|
|
&model.InviteCampaignRewardRule{},
|
|
&model.InviteCampaignRelation{},
|
|
&model.InviteCampaignInviteeProgress{},
|
|
&model.InviteCampaignMonthlyProgress{},
|
|
&model.InviteCampaignTaskClaim{},
|
|
&model.InviteCampaignRewardLog{},
|
|
&model.InviteCampaignRechargeEvent{},
|
|
); err != nil {
|
|
t.Fatalf("auto migrate: %v", err)
|
|
}
|
|
|
|
mr, err := miniredis.Run()
|
|
if err != nil {
|
|
t.Fatalf("miniredis run: %v", err)
|
|
}
|
|
redisClient := redis.NewClient(&redis.Options{Addr: mr.Addr()})
|
|
if gateway == nil {
|
|
gateway = &inviteTestGateway{}
|
|
}
|
|
service := NewInviteService(config.Config{
|
|
Invite: config.InviteConfig{PublicBaseURL: "https://invite.example.com"},
|
|
}, db, redisClient, gateway)
|
|
|
|
return service, db, func() {
|
|
redisClient.Close()
|
|
mr.Close()
|
|
}
|
|
}
|
|
|
|
func TestBuildInviteLinkUsesAppInviteH5Page(t *testing.T) {
|
|
service, _, cleanup := newTestInviteService(t, nil)
|
|
defer cleanup()
|
|
|
|
got := service.buildInviteLink("INV1001")
|
|
want := "https://invite.example.com/h5/app-invite/landing.html?code=INV1001"
|
|
if got != want {
|
|
t.Fatalf("invite link = %s, want %s", got, want)
|
|
}
|
|
|
|
service.cfg.Invite.PublicH5Path = "/custom/landing.html?view=gift"
|
|
got = service.buildInviteLink("INV1001")
|
|
want = "https://invite.example.com/custom/landing.html?code=INV1001&view=gift"
|
|
if got != want {
|
|
t.Fatalf("custom invite link = %s, want %s", got, want)
|
|
}
|
|
}
|
|
|
|
func seedInviteCampaign(t *testing.T, db *gorm.DB) time.Time {
|
|
t.Helper()
|
|
now := time.Now()
|
|
if err := db.Create(&model.InviteCampaignConfig{
|
|
ID: 1,
|
|
SysOrigin: "LIKEI",
|
|
Enabled: true,
|
|
Timezone: "Asia/Shanghai",
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed config: %v", err)
|
|
}
|
|
return now
|
|
}
|
|
|
|
func TestThirtyDayPeriodInfoAtReturnsNextPeriodStart(t *testing.T) {
|
|
anchor := time.Date(2026, 4, 1, 9, 30, 0, 0, time.FixedZone("CST", 8*3600))
|
|
now := time.Date(2026, 4, 30, 23, 59, 30, 0, time.FixedZone("CST", 8*3600))
|
|
periodKey, periodEnd, seconds := thirtyDayPeriodInfoAt(now, "Asia/Shanghai", anchor)
|
|
|
|
if periodKey != "20260401" {
|
|
t.Fatalf("periodKey = %s, want 20260401", periodKey)
|
|
}
|
|
if got := periodEnd.In(time.FixedZone("CST", 8*3600)).Format(time.RFC3339); got != "2026-05-01T00:00:00+08:00" {
|
|
t.Fatalf("periodEnd = %s, want 2026-05-01T00:00:00+08:00", got)
|
|
}
|
|
if seconds != 30 {
|
|
t.Fatalf("seconds = %d, want 30", seconds)
|
|
}
|
|
}
|
|
|
|
func TestGetHomeUsesValidUserCountForValidCountTasks(t *testing.T) {
|
|
service, db, cleanup := newTestInviteService(t, nil)
|
|
defer cleanup()
|
|
configTime := seedInviteCampaign(t, db)
|
|
|
|
now := time.Now()
|
|
periodKey, _, _ := thirtyDayPeriodInfoAt(now, "Asia/Shanghai", configTime)
|
|
if err := db.Create(&model.InviteCodeMapping{UserID: 1001, InviteCode: "INV1001", Account: "u1001"}).Error; err != nil {
|
|
t.Fatalf("seed invite code: %v", err)
|
|
}
|
|
if err := db.Create(&model.InviteCampaignRewardRule{
|
|
ID: 2001,
|
|
SysOrigin: "LIKEI",
|
|
RuleType: ruleTypeInviterValidCount,
|
|
ThresholdValue: 5,
|
|
GoldAmount: 100,
|
|
Enabled: true,
|
|
Sort: 1,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed rule: %v", err)
|
|
}
|
|
if err := db.Create(&model.InviteCampaignMonthlyProgress{
|
|
ID: 3001,
|
|
SysOrigin: "LIKEI",
|
|
InviterUserID: 1001,
|
|
MonthKey: periodKey,
|
|
InviteCount: 7,
|
|
ValidUserCount: 4,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed monthly progress: %v", err)
|
|
}
|
|
|
|
resp, err := service.GetHome(context.Background(), AuthUser{UserID: 1001, SysOrigin: "LIKEI"})
|
|
if err != nil {
|
|
t.Fatalf("GetHome() error = %v", err)
|
|
}
|
|
if len(resp.ValidCountTasks) != 1 {
|
|
t.Fatalf("ValidCountTasks length = %d, want 1", len(resp.ValidCountTasks))
|
|
}
|
|
task := resp.ValidCountTasks[0]
|
|
if task.Progress != 4 || task.Achieved {
|
|
t.Fatalf("task progress = %d achieved = %v, want progress 4 achieved false", task.Progress, task.Achieved)
|
|
}
|
|
if resp.SecondsToReset <= 0 || resp.PeriodEndAtMs <= resp.ServerTimeMs {
|
|
t.Fatalf("invalid countdown fields: server=%d end=%d seconds=%d", resp.ServerTimeMs, resp.PeriodEndAtMs, resp.SecondsToReset)
|
|
}
|
|
}
|
|
|
|
func TestClaimTaskUsesValidUserCountForValidCountTasks(t *testing.T) {
|
|
gateway := &inviteTestGateway{}
|
|
service, db, cleanup := newTestInviteService(t, gateway)
|
|
defer cleanup()
|
|
configTime := seedInviteCampaign(t, db)
|
|
|
|
now := time.Now()
|
|
periodKey, _, _ := thirtyDayPeriodInfoAt(now, "Asia/Shanghai", configTime)
|
|
if err := db.Create(&model.InviteCampaignRewardRule{
|
|
ID: 2002,
|
|
SysOrigin: "LIKEI",
|
|
RuleType: ruleTypeInviterValidCount,
|
|
ThresholdValue: 5,
|
|
GoldAmount: 100,
|
|
Enabled: true,
|
|
Sort: 1,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed rule: %v", err)
|
|
}
|
|
if err := db.Create(&model.InviteCampaignMonthlyProgress{
|
|
ID: 3002,
|
|
SysOrigin: "LIKEI",
|
|
InviterUserID: 1002,
|
|
MonthKey: periodKey,
|
|
InviteCount: 5,
|
|
ValidUserCount: 4,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed monthly progress: %v", err)
|
|
}
|
|
|
|
_, err := service.ClaimTask(context.Background(), AuthUser{UserID: 1002, SysOrigin: "LIKEI"}, TaskClaimRequest{RuleID: 2002})
|
|
if err == nil {
|
|
t.Fatalf("ClaimTask() error = nil, want task_not_reached")
|
|
}
|
|
if appErr, ok := err.(*AppError); !ok || appErr.Code != "task_not_reached" {
|
|
t.Fatalf("ClaimTask() error = %v, want task_not_reached", err)
|
|
}
|
|
if gateway.grantGoldCalls != 0 {
|
|
t.Fatalf("grantGoldCalls = %d, want 0", gateway.grantGoldCalls)
|
|
}
|
|
|
|
if err := db.Model(&model.InviteCampaignMonthlyProgress{}).
|
|
Where("id = ?", 3002).
|
|
Update("valid_user_count", 5).Error; err != nil {
|
|
t.Fatalf("update valid user count: %v", err)
|
|
}
|
|
resp, err := service.ClaimTask(context.Background(), AuthUser{UserID: 1002, SysOrigin: "LIKEI"}, TaskClaimRequest{RuleID: 2002})
|
|
if err != nil {
|
|
t.Fatalf("ClaimTask() after valid count reached error = %v", err)
|
|
}
|
|
if !resp.Claimed || resp.Progress != 5 {
|
|
t.Fatalf("claim response = %+v, want claimed progress 5", resp)
|
|
}
|
|
if gateway.grantGoldCalls != 1 {
|
|
t.Fatalf("grantGoldCalls = %d, want 1", gateway.grantGoldCalls)
|
|
}
|
|
}
|
|
|
|
func TestBindRegisteredCodeUsesRequestDeviceFingerprint(t *testing.T) {
|
|
service, db, cleanup := newTestInviteService(t, nil)
|
|
defer cleanup()
|
|
|
|
now := time.Now()
|
|
if err := db.Create(&model.InviteCampaignConfig{
|
|
ID: 101,
|
|
SysOrigin: "LIKEI",
|
|
Enabled: true,
|
|
Timezone: "Asia/Shanghai",
|
|
AntiCheatSameIPOnce: false,
|
|
AntiCheatSameDeviceOnce: true,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed config: %v", err)
|
|
}
|
|
if err := db.Create(&model.InviteCodeMapping{UserID: 9001, InviteCode: "INV9001", Account: "u9001"}).Error; err != nil {
|
|
t.Fatalf("seed invite code: %v", err)
|
|
}
|
|
if err := db.Create(&model.UserBaseInfo{ID: 9001, Account: "u9001", UserNickname: "Inviter", OriginSys: "LIKEI", CreateTime: now}).Error; err != nil {
|
|
t.Fatalf("seed inviter profile: %v", err)
|
|
}
|
|
|
|
resp, err := service.BindRegisteredCode(context.Background(), RegisterBindCodeRequest{
|
|
InviteCode: "INV9001",
|
|
SysOrigin: "LIKEI",
|
|
InviteeUserID: 9101,
|
|
ClientIP: "10.0.0.1",
|
|
DeviceFingerprint: "device-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BindRegisteredCode() error = %v", err)
|
|
}
|
|
if !resp.Eligible || resp.InviterUserID != 9001 {
|
|
t.Fatalf("BindRegisteredCode() response = %+v, want eligible inviter 9001", resp)
|
|
}
|
|
|
|
var relation model.InviteCampaignRelation
|
|
if err := db.Where("invitee_user_id = ?", 9101).First(&relation).Error; err != nil {
|
|
t.Fatalf("load relation: %v", err)
|
|
}
|
|
if relation.DeviceFingerprint != "device-a" {
|
|
t.Fatalf("relation device fingerprint = %q, want device-a", relation.DeviceFingerprint)
|
|
}
|
|
|
|
duplicateResp, err := service.BindRegisteredCode(context.Background(), RegisterBindCodeRequest{
|
|
InviteCode: "INV9001",
|
|
SysOrigin: "LIKEI",
|
|
InviteeUserID: 9102,
|
|
ClientIP: "10.0.0.2",
|
|
DeviceFingerprint: "device-a",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BindRegisteredCode() duplicate device error = %v", err)
|
|
}
|
|
if duplicateResp.Eligible || duplicateResp.BlockReason != blockReasonDuplicateDevice {
|
|
t.Fatalf("duplicate response = %+v, want blocked duplicate device", duplicateResp)
|
|
}
|
|
}
|
|
|
|
func TestBindCodeAcceptsSnakeInviteCodeAndIsIdempotent(t *testing.T) {
|
|
service, db, cleanup := newTestInviteService(t, nil)
|
|
defer cleanup()
|
|
|
|
now := time.Now()
|
|
if err := db.Create(&model.InviteCampaignConfig{
|
|
ID: 201,
|
|
SysOrigin: "LIKEI",
|
|
Enabled: true,
|
|
Timezone: "Asia/Shanghai",
|
|
AntiCheatSameIPOnce: false,
|
|
AntiCheatSameDeviceOnce: false,
|
|
CreateTime: now,
|
|
UpdateTime: now,
|
|
}).Error; err != nil {
|
|
t.Fatalf("seed config: %v", err)
|
|
}
|
|
if err := db.Create(&model.InviteCodeMapping{UserID: 9201, InviteCode: "INV9201", Account: "u9201"}).Error; err != nil {
|
|
t.Fatalf("seed invite code: %v", err)
|
|
}
|
|
if err := db.Create(&model.UserBaseInfo{ID: 9201, Account: "u9201", UserNickname: "Inviter", OriginSys: "LIKEI", CreateTime: now}).Error; err != nil {
|
|
t.Fatalf("seed inviter profile: %v", err)
|
|
}
|
|
|
|
user := AuthUser{UserID: 9301, SysOrigin: "LIKEI"}
|
|
resp, err := service.BindCode(context.Background(), user, "10.0.0.3", BindCodeRequest{InviteCodeSnake: "INV9201"})
|
|
if err != nil {
|
|
t.Fatalf("BindCode() error = %v", err)
|
|
}
|
|
if resp.AlreadyBound || !resp.Eligible || resp.InviterUserID != 9201 {
|
|
t.Fatalf("BindCode() response = %+v, want first bind eligible inviter 9201", resp)
|
|
}
|
|
|
|
again, err := service.BindCode(context.Background(), user, "10.0.0.3", BindCodeRequest{InviteCodeSnake: "INV9201"})
|
|
if err != nil {
|
|
t.Fatalf("BindCode() idempotent error = %v", err)
|
|
}
|
|
if !again.AlreadyBound || again.RelationID != resp.RelationID {
|
|
t.Fatalf("idempotent response = %+v, want same relation alreadyBound", again)
|
|
}
|
|
|
|
var relationCount int64
|
|
if err := db.Model(&model.InviteCampaignRelation{}).
|
|
Where("sys_origin = ? AND invitee_user_id = ?", "LIKEI", 9301).
|
|
Count(&relationCount).Error; err != nil {
|
|
t.Fatalf("count relations: %v", err)
|
|
}
|
|
if relationCount != 1 {
|
|
t.Fatalf("relation count = %d, want 1", relationCount)
|
|
}
|
|
|
|
var progress model.InviteCampaignMonthlyProgress
|
|
if err := db.Where("sys_origin = ? AND inviter_user_id = ?", "LIKEI", 9201).First(&progress).Error; err != nil {
|
|
t.Fatalf("load progress: %v", err)
|
|
}
|
|
if progress.InviteCount != 1 {
|
|
t.Fatalf("invite count = %d, want 1", progress.InviteCount)
|
|
}
|
|
}
|
|
|
|
func TestGetRankUsesLifetimeValidUserAndRewardStats(t *testing.T) {
|
|
service, db, cleanup := newTestInviteService(t, nil)
|
|
defer cleanup()
|
|
|
|
now := time.Now()
|
|
rows := []any{
|
|
&model.InviteCampaignRelation{ID: 1, SysOrigin: "LIKEI", InviterUserID: 20, InviteeUserID: 201, EligibleStatus: relationStatusEligible, BindTime: now, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignRelation{ID: 2, SysOrigin: "LIKEI", InviterUserID: 20, InviteeUserID: 202, EligibleStatus: relationStatusEligible, BindTime: now, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignRelation{ID: 3, SysOrigin: "LIKEI", InviterUserID: 20, InviteeUserID: 203, EligibleStatus: relationStatusEligible, BindTime: now, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignRelation{ID: 4, SysOrigin: "LIKEI", InviterUserID: 10, InviteeUserID: 101, EligibleStatus: relationStatusEligible, BindTime: now, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignRelation{ID: 5, SysOrigin: "LIKEI", InviterUserID: 10, InviteeUserID: 102, EligibleStatus: relationStatusBlocked, BindTime: now, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignInviteeProgress{ID: 101, RelationID: 1, SysOrigin: "LIKEI", InviterUserID: 20, InviteeUserID: 201, TotalRecharge: 80000, ValidUser: true, ValidTime: refTime(now), CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignInviteeProgress{ID: 102, RelationID: 2, SysOrigin: "LIKEI", InviterUserID: 20, InviteeUserID: 202, TotalRecharge: 79999, ValidUser: false, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignInviteeProgress{ID: 103, RelationID: 3, SysOrigin: "LIKEI", InviterUserID: 20, InviteeUserID: 203, TotalRecharge: 80000, ValidUser: true, ValidTime: refTime(now), CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignInviteeProgress{ID: 104, RelationID: 4, SysOrigin: "LIKEI", InviterUserID: 10, InviteeUserID: 101, TotalRecharge: 80000, ValidUser: true, ValidTime: refTime(now), CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignInviteeProgress{ID: 105, RelationID: 5, SysOrigin: "LIKEI", InviterUserID: 10, InviteeUserID: 102, TotalRecharge: 80000, ValidUser: true, ValidTime: refTime(now), CreateTime: now, UpdateTime: now},
|
|
&model.UserBaseInfo{ID: 20, Account: "user20", UserNickname: "Mina", UserAvatar: "avatar-20", OriginSys: "LIKEI", CreateTime: now},
|
|
&model.UserBaseInfo{ID: 10, Account: "user10", UserNickname: "Lina", UserAvatar: "avatar-10", OriginSys: "LIKEI", CreateTime: now},
|
|
&model.InviteCampaignRewardLog{ID: 11, SysOrigin: "LIKEI", InviterUserID: refInt64(20), TargetUserID: 20, BusinessKey: "r20-1", GoldAmount: 100, Status: rewardLogStatusSuccess, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignRewardLog{ID: 12, SysOrigin: "LIKEI", InviterUserID: refInt64(20), TargetUserID: 20, BusinessKey: "r20-2", GoldAmount: 200, Status: rewardLogStatusSuccess, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignRewardLog{ID: 13, SysOrigin: "LIKEI", InviterUserID: refInt64(10), TargetUserID: 10, BusinessKey: "r10-1", GoldAmount: 900, Status: rewardLogStatusSuccess, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignRewardLog{ID: 14, SysOrigin: "LIKEI", InviterUserID: refInt64(20), TargetUserID: 201, BusinessKey: "invitee-reward", GoldAmount: 999, Status: rewardLogStatusSuccess, CreateTime: now, UpdateTime: now},
|
|
&model.InviteCampaignRewardLog{ID: 15, SysOrigin: "LIKEI", InviterUserID: refInt64(20), TargetUserID: 20, BusinessKey: "failed-reward", GoldAmount: 999, Status: rewardLogStatusFailed, CreateTime: now, UpdateTime: now},
|
|
}
|
|
for _, row := range rows {
|
|
if err := db.Create(row).Error; err != nil {
|
|
t.Fatalf("seed row %T: %v", row, err)
|
|
}
|
|
}
|
|
|
|
resp, err := service.GetRank(context.Background(), AuthUser{SysOrigin: "LIKEI"}, 10)
|
|
if err != nil {
|
|
t.Fatalf("GetRank() error = %v", err)
|
|
}
|
|
if len(resp.Items) != 2 {
|
|
t.Fatalf("rank length = %d, want 2", len(resp.Items))
|
|
}
|
|
if got := resp.Items[0]; got.Rank != 1 || got.UserID != 20 || got.Nickname != "Mina" || got.InviteCount != 2 || got.RewardGoldCoins != 300 {
|
|
t.Fatalf("top rank = %+v, want user 20 count 2 reward 300", got)
|
|
}
|
|
if got := resp.Items[1]; got.Rank != 2 || got.UserID != 10 || got.InviteCount != 1 || got.RewardGoldCoins != 900 {
|
|
t.Fatalf("second rank = %+v, want user 10 count 1 reward 900", got)
|
|
}
|
|
}
|
|
|
|
func TestRechargeMarksValidUserOnlyAfterEightyThousandCoins(t *testing.T) {
|
|
service, db, cleanup := newTestInviteService(t, nil)
|
|
defer cleanup()
|
|
configTime := seedInviteCampaign(t, db)
|
|
|
|
payTime := configTime.Add(time.Hour)
|
|
periodKey, _, _ := thirtyDayPeriodInfoAt(payTime, "Asia/Shanghai", configTime)
|
|
rows := []any{
|
|
&model.InviteCampaignRewardRule{
|
|
ID: 2101,
|
|
SysOrigin: "LIKEI",
|
|
RuleType: ruleTypeInviteeRecharge,
|
|
ThresholdValue: 10000,
|
|
GoldAmount: 0,
|
|
Enabled: true,
|
|
Sort: 1,
|
|
CreateTime: configTime,
|
|
UpdateTime: configTime,
|
|
},
|
|
&model.InviteCampaignRelation{
|
|
ID: 4101,
|
|
SysOrigin: "LIKEI",
|
|
InviterUserID: 5101,
|
|
InviteeUserID: 5201,
|
|
EligibleStatus: relationStatusEligible,
|
|
BindTime: configTime,
|
|
CreateTime: configTime,
|
|
UpdateTime: configTime,
|
|
},
|
|
&model.InviteCampaignInviteeProgress{
|
|
ID: 4201,
|
|
RelationID: 4101,
|
|
SysOrigin: "LIKEI",
|
|
InviterUserID: 5101,
|
|
InviteeUserID: 5201,
|
|
CreateTime: configTime,
|
|
UpdateTime: configTime,
|
|
},
|
|
}
|
|
for _, row := range rows {
|
|
if err := db.Create(row).Error; err != nil {
|
|
t.Fatalf("seed row %T: %v", row, err)
|
|
}
|
|
}
|
|
|
|
resp, err := service.HandleRechargeSuccess(context.Background(), RechargeSuccessRequest{
|
|
OrderID: "order-79999",
|
|
UserID: 5201,
|
|
RechargeCoins: 79999,
|
|
PayTime: payTime.UnixMilli(),
|
|
SysOrigin: "LIKEI",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("HandleRechargeSuccess() 79999 error = %v", err)
|
|
}
|
|
if resp.ValidUser {
|
|
t.Fatalf("ValidUser = true after 79999 recharge, want false")
|
|
}
|
|
|
|
monthly, err := service.findMonthlyProgress(context.Background(), "LIKEI", 5101, periodKey)
|
|
if err != nil {
|
|
t.Fatalf("find monthly progress: %v", err)
|
|
}
|
|
if monthly.ValidUserCount != 0 || monthly.TotalRecharge != 79999 {
|
|
t.Fatalf("monthly after 79999 = %+v, want valid 0 total 79999", monthly)
|
|
}
|
|
|
|
resp, err = service.HandleRechargeSuccess(context.Background(), RechargeSuccessRequest{
|
|
OrderID: "order-80000",
|
|
UserID: 5201,
|
|
RechargeCoins: 1,
|
|
PayTime: payTime.Add(time.Minute).UnixMilli(),
|
|
SysOrigin: "LIKEI",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("HandleRechargeSuccess() 80000 error = %v", err)
|
|
}
|
|
if !resp.ValidUser {
|
|
t.Fatalf("ValidUser = false after 80000 recharge, want true")
|
|
}
|
|
|
|
monthly, err = service.findMonthlyProgress(context.Background(), "LIKEI", 5101, periodKey)
|
|
if err != nil {
|
|
t.Fatalf("find monthly progress after 80000: %v", err)
|
|
}
|
|
if monthly.ValidUserCount != 1 || monthly.TotalRecharge != 80000 {
|
|
t.Fatalf("monthly after 80000 = %+v, want valid 1 total 80000", monthly)
|
|
}
|
|
}
|
|
|
|
func TestDispatchGoldReturnsGrantError(t *testing.T) {
|
|
grantErr := errors.New("wallet unavailable")
|
|
service, db, cleanup := newTestInviteService(t, &inviteTestGateway{grantGoldErr: grantErr})
|
|
defer cleanup()
|
|
|
|
_, err := service.dispatchReward(context.Background(), rewardDispatchInput{
|
|
SysOrigin: "LIKEI",
|
|
TargetUserID: 1001,
|
|
Reward: RewardView{GoldAmount: 100},
|
|
Scene: ruleTypeInviterValidCount,
|
|
BusinessKey: "claim:1001:202604:1",
|
|
GoldOrigin: "INVITE_USER_REWARDS",
|
|
Remark: "test reward",
|
|
})
|
|
if !errors.Is(err, grantErr) {
|
|
t.Fatalf("dispatchReward() error = %v, want %v", err, grantErr)
|
|
}
|
|
|
|
var log model.InviteCampaignRewardLog
|
|
if err := db.Where("business_key = ?", "claim:1001:202604:1:gold").First(&log).Error; err != nil {
|
|
t.Fatalf("load reward log: %v", err)
|
|
}
|
|
if log.Status != rewardLogStatusFailed {
|
|
t.Fatalf("reward log status = %s, want %s", log.Status, rewardLogStatusFailed)
|
|
}
|
|
}
|