25811526bd
Co-authored-by: multica-agent <github@multica.ai>
179 lines
5.8 KiB
Go
179 lines
5.8 KiB
Go
package common
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
type fakePromoEligibilitySource struct {
|
|
userCreatedAt time.Time
|
|
lastExpireAt time.Time
|
|
}
|
|
|
|
func (s fakePromoEligibilitySource) UserCreatedAt(context.Context, int64) (time.Time, error) {
|
|
return s.userCreatedAt, nil
|
|
}
|
|
|
|
func (s fakePromoEligibilitySource) LastSubscribeExpireAt(context.Context, int64) (time.Time, error) {
|
|
return s.lastExpireAt, nil
|
|
}
|
|
|
|
func TestEvaluatePromoRulesAtPriorityFirstMatch(t *testing.T) {
|
|
now := time.Date(2026, 5, 27, 12, 0, 0, 0, time.UTC)
|
|
campaignEnd := now.Add(24 * time.Hour)
|
|
rules := []promoRule{
|
|
{
|
|
Id: 2,
|
|
Name: "campaign",
|
|
Type: PromoRuleTypeCampaign,
|
|
Priority: 20,
|
|
Enabled: true,
|
|
EndTime: &campaignEnd,
|
|
},
|
|
{
|
|
Id: 1,
|
|
Name: "new user",
|
|
Type: PromoRuleTypeNewUser,
|
|
Params: `{"window_hours":168}`,
|
|
Priority: 10,
|
|
Enabled: true,
|
|
},
|
|
}
|
|
prices := []subscribePromo{
|
|
{SubscribeId: 100, PromoRuleId: 1, PromoPrice: 599},
|
|
{SubscribeId: 100, PromoRuleId: 2, PromoPrice: 499},
|
|
}
|
|
|
|
got, err := evaluatePromoRulesAt(context.Background(), rules, prices, fakePromoEligibilitySource{
|
|
userCreatedAt: now.Add(-time.Hour),
|
|
}, 10, now)
|
|
if err != nil {
|
|
t.Fatalf("evaluatePromoRulesAt error: %v", err)
|
|
}
|
|
if !got.Eligible || got.RuleID != 2 || got.PromoPrice != 499 || got.RuleType != PromoRuleTypeCampaign {
|
|
t.Fatalf("unexpected promo result: %+v", got)
|
|
}
|
|
if !got.ExpiresAt.Equal(campaignEnd) {
|
|
t.Fatalf("expires_at = %v, want %v", got.ExpiresAt, campaignEnd)
|
|
}
|
|
}
|
|
|
|
func TestEvaluatePromoRulesAtSkipsUnavailableRules(t *testing.T) {
|
|
now := time.Date(2026, 5, 27, 12, 0, 0, 0, time.UTC)
|
|
futureStart := now.Add(time.Hour)
|
|
expiredEnd := now.Add(-time.Hour)
|
|
rules := []promoRule{
|
|
{Id: 1, Type: PromoRuleTypeCampaign, Enabled: true, StartTime: &futureStart},
|
|
{Id: 2, Type: PromoRuleTypeCampaign, Enabled: true, EndTime: &expiredEnd},
|
|
{Id: 3, Type: PromoRuleTypeCampaign, Enabled: false},
|
|
{Id: 4, Type: "unknown", Enabled: true},
|
|
{Id: 5, Type: PromoRuleTypeCampaign, Enabled: true},
|
|
}
|
|
prices := []subscribePromo{
|
|
{SubscribeId: 100, PromoRuleId: 1, PromoPrice: 100},
|
|
{SubscribeId: 100, PromoRuleId: 2, PromoPrice: 100},
|
|
{SubscribeId: 100, PromoRuleId: 3, PromoPrice: 100},
|
|
{SubscribeId: 100, PromoRuleId: 4, PromoPrice: 100},
|
|
{SubscribeId: 100, PromoRuleId: 5, PromoPrice: 88},
|
|
}
|
|
|
|
got, err := evaluatePromoRulesAt(context.Background(), rules, prices, fakePromoEligibilitySource{}, 10, now)
|
|
if err != nil {
|
|
t.Fatalf("evaluatePromoRulesAt error: %v", err)
|
|
}
|
|
if !got.Eligible || got.RuleID != 5 || got.PromoPrice != 88 {
|
|
t.Fatalf("unexpected promo result: %+v", got)
|
|
}
|
|
}
|
|
|
|
func TestEvaluatePromoNewUser(t *testing.T) {
|
|
now := time.Date(2026, 5, 27, 12, 0, 0, 0, time.UTC)
|
|
createdAt := now.Add(-23 * time.Hour)
|
|
|
|
eligible, expiresAt := evaluatePromoNewUser(createdAt, promoRuleParams{WindowHours: 24}, now)
|
|
if !eligible {
|
|
t.Fatal("new user should be eligible inside configured window")
|
|
}
|
|
if !expiresAt.Equal(createdAt.Add(24 * time.Hour)) {
|
|
t.Fatalf("expires_at = %v, want %v", expiresAt, createdAt.Add(24*time.Hour))
|
|
}
|
|
|
|
eligible, _ = evaluatePromoNewUser(createdAt, promoRuleParams{WindowHours: 12}, now)
|
|
if eligible {
|
|
t.Fatal("new user should not be eligible after configured window")
|
|
}
|
|
|
|
eligible, _ = evaluatePromoNewUser(createdAt, promoRuleParams{}, now)
|
|
if eligible {
|
|
t.Fatal("new user should not be eligible without positive window_hours")
|
|
}
|
|
}
|
|
|
|
func TestEvaluatePromoInactiveUser(t *testing.T) {
|
|
now := time.Date(2026, 5, 27, 12, 0, 0, 0, time.UTC)
|
|
endTime := now.Add(48 * time.Hour)
|
|
rule := promoRule{EndTime: &endTime}
|
|
|
|
eligible, expiresAt := evaluatePromoInactiveUser(time.Time{}, promoRuleParams{InactiveMonths: 3}, rule, now)
|
|
if !eligible {
|
|
t.Fatal("never purchased user should be eligible for inactive promo")
|
|
}
|
|
if !expiresAt.Equal(endTime) {
|
|
t.Fatalf("expires_at = %v, want %v", expiresAt, endTime)
|
|
}
|
|
|
|
eligible, _ = evaluatePromoInactiveUser(now.AddDate(0, -4, 0), promoRuleParams{InactiveMonths: 3}, rule, now)
|
|
if !eligible {
|
|
t.Fatal("expired before inactive threshold should be eligible")
|
|
}
|
|
|
|
eligible, _ = evaluatePromoInactiveUser(now.AddDate(0, -1, 0), promoRuleParams{InactiveMonths: 3}, rule, now)
|
|
if eligible {
|
|
t.Fatal("recently expired subscription should not be eligible")
|
|
}
|
|
|
|
eligible, _ = evaluatePromoInactiveUser(time.UnixMilli(0), promoRuleParams{InactiveMonths: 3}, rule, now)
|
|
if eligible {
|
|
t.Fatal("unlimited active subscription should not be eligible")
|
|
}
|
|
|
|
eligible, _ = evaluatePromoInactiveUser(now.Add(time.Hour), promoRuleParams{InactiveMonths: 3}, rule, now)
|
|
if eligible {
|
|
t.Fatal("currently active subscription should not be eligible")
|
|
}
|
|
|
|
eligible, _ = evaluatePromoInactiveUser(now.AddDate(0, -4, 0), promoRuleParams{}, rule, now)
|
|
if eligible {
|
|
t.Fatal("inactive promo should require positive inactive_months")
|
|
}
|
|
}
|
|
|
|
func TestLastSubscribeExpireAtIncludesUnlimitedSubscription(t *testing.T) {
|
|
db, err := gorm.Open(mysql.New(mysql.Config{
|
|
DSN: "gorm:password@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local",
|
|
SkipInitializeWithVersion: true,
|
|
}), &gorm.Config{
|
|
DryRun: true,
|
|
DisableAutomaticPing: true,
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("open gorm db: %v", err)
|
|
}
|
|
|
|
stmt := lastSubscribeExpireQuery(db, 10).Take(nil).Statement
|
|
sql := strings.ToLower(stmt.SQL.String())
|
|
if strings.Contains(sql, "expire_time <>") || strings.Contains(sql, "expire_time !=") {
|
|
t.Fatalf("last subscribe query should include unlimited subscription, sql: %s", stmt.SQL.String())
|
|
}
|
|
if !strings.Contains(sql, "case when expire_time = ? then 0 else 1 end asc") {
|
|
t.Fatalf("last subscribe query should prioritize unlimited subscription, sql: %s", stmt.SQL.String())
|
|
}
|
|
}
|