diff --git a/queue/logic/order/activateOrderLogic_invite_test.go b/queue/logic/order/activateOrderLogic_invite_test.go index 6813bf1..3b77679 100644 --- a/queue/logic/order/activateOrderLogic_invite_test.go +++ b/queue/logic/order/activateOrderLogic_invite_test.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "os" + "strings" "testing" "time" @@ -440,6 +441,165 @@ func TestHandleCommission_GiftDaysRecognizesUnlimitedSubscription(t *testing.T) } } +func TestHandleCommission_IdempotentForRepeatedActivation(t *testing.T) { + logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{ + ReferralPercentage: 10, + OnlyFirstPurchase: false, + GiftDays: 2, + }) + defer cleanup() + + referee := seedUser(t, db, 0, false) + referer := seedUser(t, db, 0, false) + referee.RefererId = referer.Id + + baseExpire := time.Now().Add(96 * time.Hour).Truncate(time.Second) + refereeSub := seedActiveSubscribe(t, db, referee.Id, baseExpire) + orderInfo := &order.Order{ + OrderNo: "ORD-IDEMPOTENT-001", + Type: OrderTypeSubscribe, + IsNew: true, + Amount: 100, + FeeAmount: 0, + CreatedAt: time.Now(), + } + + logic.handleCommission(context.Background(), referee, orderInfo) + logic.handleCommission(context.Background(), referee, orderInfo) + + assertExpireIncreasedByDays(t, db, refereeSub.Id, baseExpire, 2) + assertUserCommission(t, db, referer.Id, 10) + assertLogCountForOrder(t, db, modelLog.TypeCommission.Uint8(), orderInfo.OrderNo, 1) + assertLogCountForOrder(t, db, modelLog.TypeGift.Uint8(), orderInfo.OrderNo, 1) +} + +func TestHandleCommission_NoActiveSubscriptionWritesSkippedGiftLog(t *testing.T) { + logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{ + ReferralPercentage: 10, + OnlyFirstPurchase: false, + GiftDays: 2, + }) + defer cleanup() + + referee := seedUser(t, db, 0, false) + referer := seedUser(t, db, 0, false) + referee.RefererId = referer.Id + orderInfo := &order.Order{ + OrderNo: "ORD-SKIPPED-001", + Type: OrderTypeSubscribe, + IsNew: true, + Amount: 100, + FeeAmount: 0, + CreatedAt: time.Now(), + } + + logic.handleCommission(context.Background(), referee, orderInfo) + + assertUserCommission(t, db, referer.Id, 10) + assertLogCountForOrder(t, db, modelLog.TypeCommission.Uint8(), orderInfo.OrderNo, 1) + assertLogCountForOrder(t, db, modelLog.TypeGift.Uint8(), orderInfo.OrderNo, 1) + assertGiftLogRemarkContains(t, db, orderInfo.OrderNo, "skipped: no active subscription") +} + +func TestHandleCommission_GiftDaysZeroDoesNotWriteGiftLog(t *testing.T) { + logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{ + ReferralPercentage: 10, + OnlyFirstPurchase: false, + GiftDays: 0, + }) + defer cleanup() + + referee := seedUser(t, db, 0, false) + referer := seedUser(t, db, 0, false) + referee.RefererId = referer.Id + baseExpire := time.Now().Add(96 * time.Hour).Truncate(time.Second) + refereeSub := seedActiveSubscribe(t, db, referee.Id, baseExpire) + orderInfo := &order.Order{ + OrderNo: "ORD-GIFT-ZERO-001", + Type: OrderTypeSubscribe, + IsNew: true, + Amount: 100, + FeeAmount: 0, + CreatedAt: time.Now(), + } + + logic.handleCommission(context.Background(), referee, orderInfo) + + assertExpireIncreasedByDays(t, db, refereeSub.Id, baseExpire, 0) + assertUserCommission(t, db, referer.Id, 10) + assertLogCountForOrder(t, db, modelLog.TypeCommission.Uint8(), orderInfo.OrderNo, 1) + assertLogCountForOrder(t, db, modelLog.TypeGift.Uint8(), orderInfo.OrderNo, 0) +} + +func TestHandleCommission_GlobalOnlyFirstPurchaseBlocksCommissionAndGiftForNonFirstOrder(t *testing.T) { + logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{ + ReferralPercentage: 10, + OnlyFirstPurchase: true, + GiftDays: 2, + }) + defer cleanup() + + referee := seedUser(t, db, 0, false) + referer := seedUser(t, db, 0, false) + referee.RefererId = referer.Id + baseExpire := time.Now().Add(96 * time.Hour).Truncate(time.Second) + refereeSub := seedActiveSubscribe(t, db, referee.Id, baseExpire) + orderInfo := &order.Order{ + OrderNo: "ORD-NONFIRST-GLOBAL-001", + Type: OrderTypeRenewal, + IsNew: false, + Amount: 100, + FeeAmount: 0, + CreatedAt: time.Now(), + } + + logic.handleCommission(context.Background(), referee, orderInfo) + + assertExpireIncreasedByDays(t, db, refereeSub.Id, baseExpire, 0) + assertUserCommission(t, db, referer.Id, 0) + assertLogCountForOrder(t, db, modelLog.TypeCommission.Uint8(), orderInfo.OrderNo, 0) + assertLogCountForOrder(t, db, modelLog.TypeGift.Uint8(), orderInfo.OrderNo, 0) +} + +func TestHandleCommission_BothFamilySidesUseCorrectGiftOwners(t *testing.T) { + logic, db, cleanup := setupInviteTestLogic(t, config.InviteConfig{ + ReferralPercentage: 0, + OnlyFirstPurchase: false, + GiftDays: 2, + }) + defer cleanup() + + refereeOwner := seedUser(t, db, 0, false) + referee := seedUser(t, db, 0, false) + refererOwner := seedUser(t, db, 0, false) + referer := seedUser(t, db, 0, false) + seedFamily(t, db, refereeOwner.Id, referee.Id) + seedFamily(t, db, refererOwner.Id, referer.Id) + referee.RefererId = referer.Id + + refereeOwnerBaseExpire := time.Now().Add(72 * time.Hour).Truncate(time.Second) + refererOwnerBaseExpire := time.Now().Add(96 * time.Hour).Truncate(time.Second) + refereeOwnerSub := seedActiveSubscribe(t, db, refereeOwner.Id, refereeOwnerBaseExpire) + refererOwnerSub := seedActiveSubscribe(t, db, refererOwner.Id, refererOwnerBaseExpire) + orderInfo := &order.Order{ + OrderNo: "ORD-BOTH-FAMILIES-001", + Type: OrderTypeSubscribe, + IsNew: true, + Amount: 100, + FeeAmount: 0, + SubscriptionUserId: refereeOwner.Id, + CreatedAt: time.Now(), + } + + logic.handleCommission(context.Background(), referee, orderInfo) + + assertExpireIncreasedByDays(t, db, refereeOwnerSub.Id, refereeOwnerBaseExpire, 2) + assertExpireIncreasedByDays(t, db, refererOwnerSub.Id, refererOwnerBaseExpire, 2) + assertNoSubscribeForUser(t, db, referee.Id) + assertNoSubscribeForUser(t, db, referer.Id) + assertLogCountForOrder(t, db, modelLog.TypeGift.Uint8(), orderInfo.OrderNo, 2) +} + func setupInviteTestLogic(t *testing.T, inviteCfg config.InviteConfig) (*ActivateOrderLogic, *gorm.DB, func()) { t.Helper() @@ -596,6 +756,43 @@ func assertUserCommission(t *testing.T, db *gorm.DB, userID int64, expected int6 } } +func assertLogCountForOrder(t *testing.T, db *gorm.DB, logType uint8, orderNo string, expected int64) { + t.Helper() + var count int64 + if err := db.Model(&modelLog.SystemLog{}). + Where("type = ? AND content LIKE ?", logType, "%"+orderNo+"%"). + Count(&count).Error; err != nil { + t.Fatalf("count logs failed: %v", err) + } + if count != expected { + t.Fatalf("expected log type %d count=%d for order %s, got %d", logType, expected, orderNo, count) + } +} + +func assertGiftLogRemarkContains(t *testing.T, db *gorm.DB, orderNo string, want string) { + t.Helper() + var row modelLog.SystemLog + if err := db.Model(&modelLog.SystemLog{}). + Where("type = ? AND content LIKE ?", modelLog.TypeGift.Uint8(), "%"+orderNo+"%"). + First(&row).Error; err != nil { + t.Fatalf("query gift log failed: %v", err) + } + if !strings.Contains(row.Content, want) { + t.Fatalf("expected gift log content to contain %q, got %s", want, row.Content) + } +} + +func assertNoSubscribeForUser(t *testing.T, db *gorm.DB, userID int64) { + t.Helper() + var count int64 + if err := db.Model(&user.Subscribe{}).Where("user_id = ?", userID).Count(&count).Error; err != nil { + t.Fatalf("count subscribes failed: %v", err) + } + if count != 0 { + t.Fatalf("expected user %d to have no direct subscriptions, got %d", userID, count) + } +} + func boolPtr(v bool) *bool { return &v }