新功能(#75): 实现促销资格判定核心逻辑
Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
@@ -0,0 +1,150 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user