b5e50d1ee5
- loadSubscribePromoMap 返回 map[subscribeID]map[quantity]*SubscribePromo 二级映射 - 候选查询按 subscribe_id、quantity、priority DESC 排序,一次取出所有 quantity 档位 - 下单路径 QueryEligibleRules 将 quantity 条件移至 JOIN,精确匹配 - 永久订阅(expire_time=0)用 CASE WHEN 排序兜底,不再误判为回归促销 - 新增 promoEligibility、model、subscribe promo 单元测试 Co-authored-by: multica-agent <github@multica.ai>
161 lines
5.4 KiB
Go
161 lines
5.4 KiB
Go
package subscribe
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/perfect-panel/server/internal/model/user"
|
|
"github.com/perfect-panel/server/internal/types"
|
|
"gorm.io/driver/mysql"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func TestPromoEligibilityEvaluatorMatch(t *testing.T) {
|
|
now := time.Unix(1710000000, 0)
|
|
campaignEnd := now.Add(2 * time.Hour)
|
|
|
|
campaign := subscribePromoCandidate{
|
|
RuleName: "限时活动",
|
|
RuleType: promoRuleTypeCampaign,
|
|
PromoPrice: 99,
|
|
EndTime: &campaignEnd,
|
|
}
|
|
ok, expiresAt, err := (&promoEligibilityEvaluator{}).match(campaign, now)
|
|
if err != nil {
|
|
t.Fatalf("campaign match error: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatal("campaign promo should match without login")
|
|
}
|
|
if got, want := unixSeconds(expiresAt), campaignEnd.Unix(); got != want {
|
|
t.Fatalf("campaign expires_at = %d, want %d", got, want)
|
|
}
|
|
|
|
newUser := subscribePromoCandidate{
|
|
RuleName: "新客7天优惠",
|
|
RuleType: promoRuleTypeNewUser,
|
|
PromoPrice: 279,
|
|
Params: `{"window_hours":168}`,
|
|
}
|
|
ok, _, err = (&promoEligibilityEvaluator{}).match(newUser, now)
|
|
if err != nil {
|
|
t.Fatalf("anonymous new_user match error: %v", err)
|
|
}
|
|
if ok {
|
|
t.Fatal("new_user promo should not match without login")
|
|
}
|
|
|
|
userInfo := &user.User{Id: 1, CreatedAt: now.Add(-24 * time.Hour)}
|
|
ok, expiresAt, err = (&promoEligibilityEvaluator{userInfo: userInfo}).match(newUser, now)
|
|
if err != nil {
|
|
t.Fatalf("logged-in new_user match error: %v", err)
|
|
}
|
|
if !ok {
|
|
t.Fatal("new_user promo should match inside window")
|
|
}
|
|
if got, want := unixSeconds(expiresAt), userInfo.CreatedAt.Add(168*time.Hour).Unix(); got != want {
|
|
t.Fatalf("new_user expires_at = %d, want %d", got, want)
|
|
}
|
|
}
|
|
|
|
func TestSubscribePromoCandidateActiveWindow(t *testing.T) {
|
|
now := time.Unix(1710000000, 0)
|
|
start := now.Add(-time.Hour)
|
|
end := now.Add(time.Hour)
|
|
|
|
if !(subscribePromoCandidate{PromoPrice: 1, StartTime: &start, EndTime: &end}).isActive(now) {
|
|
t.Fatal("candidate inside active window should be active")
|
|
}
|
|
if (subscribePromoCandidate{PromoPrice: 0, StartTime: &start, EndTime: &end}).isActive(now) {
|
|
t.Fatal("candidate with zero promo price should not be active")
|
|
}
|
|
if (subscribePromoCandidate{PromoPrice: 1, StartTime: &end}).isActive(now) {
|
|
t.Fatal("candidate before start time should not be active")
|
|
}
|
|
if (subscribePromoCandidate{PromoPrice: 1, EndTime: &start}).isActive(now) {
|
|
t.Fatal("candidate after end time should not be active")
|
|
}
|
|
}
|
|
|
|
func TestLastSubscribeExpireAtPrioritizesPermanentSubscription(t *testing.T) {
|
|
db, err := gorm.Open(mysql.New(mysql.Config{
|
|
DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local",
|
|
SkipInitializeWithVersion: true,
|
|
}), &gorm.Config{DryRun: true, DisableAutomaticPing: true})
|
|
if err != nil {
|
|
t.Fatalf("open dry-run db: %v", err)
|
|
}
|
|
|
|
evaluator := &promoEligibilityEvaluator{
|
|
db: db,
|
|
userInfo: &user.User{Id: 7},
|
|
}
|
|
var item user.Subscribe
|
|
tx := evaluator.lastSubscribeExpireQuery().Limit(1).Take(&item)
|
|
|
|
sql := tx.Statement.SQL.String()
|
|
if !strings.Contains(sql, "CASE WHEN expire_time = ? THEN 0 ELSE 1 END") {
|
|
t.Fatalf("SQL missing permanent subscription priority order: %s", sql)
|
|
}
|
|
if strings.Contains(sql, "expire_time !=") {
|
|
t.Fatalf("SQL should not filter out permanent subscriptions: %s", sql)
|
|
}
|
|
if len(tx.Statement.Vars) < 2 {
|
|
t.Fatalf("SQL vars length = %d, want at least 2; vars=%v", len(tx.Statement.Vars), tx.Statement.Vars)
|
|
}
|
|
if got, want := tx.Statement.Vars[1], time.UnixMilli(0); got != want {
|
|
t.Fatalf("permanent subscription order var = %v, want %v; vars=%v", got, want, tx.Statement.Vars)
|
|
}
|
|
}
|
|
|
|
func TestQuerySubscribePromoCandidatesIncludesQuantity(t *testing.T) {
|
|
db, err := gorm.Open(mysql.New(mysql.Config{
|
|
DSN: "gorm:gorm@tcp(localhost:9910)/gorm?charset=utf8&parseTime=True&loc=Local",
|
|
SkipInitializeWithVersion: true,
|
|
}), &gorm.Config{DryRun: true, DisableAutomaticPing: true})
|
|
if err != nil {
|
|
t.Fatalf("open dry-run db: %v", err)
|
|
}
|
|
|
|
var candidates []subscribePromoCandidate
|
|
tx := subscribePromoCandidatesQuery(context.Background(), db, []int64{11, 12}, true).Scan(&candidates)
|
|
stmt := tx.Statement
|
|
sql := stmt.SQL.String()
|
|
if !strings.Contains(sql, "sp.subscribe_id, sp.quantity, sp.promo_price") {
|
|
t.Fatalf("SQL missing quantity select: %s", sql)
|
|
}
|
|
if !strings.Contains(sql, "ORDER BY sp.subscribe_id ASC,sp.quantity ASC,pr.priority DESC,pr.id ASC") {
|
|
t.Fatalf("SQL missing quantity order: %s", sql)
|
|
}
|
|
}
|
|
|
|
func TestApplySubscribeDiscountPromosMatchesQuantity(t *testing.T) {
|
|
subscribe := types.Subscribe{Discount: []types.SubscribeDiscount{
|
|
{Quantity: 1},
|
|
{Quantity: 12},
|
|
}}
|
|
promos := map[int64]*types.SubscribePromo{
|
|
3: {RuleName: "季度优惠", PromoPrice: 2900},
|
|
12: {RuleName: "年度优惠", PromoPrice: 9900},
|
|
}
|
|
|
|
applySubscribeDiscountPromos(&subscribe, promos)
|
|
if subscribe.Discount[0].Promo != nil {
|
|
t.Fatalf("quantity 1 promo should be nil, got %+v", subscribe.Discount[0].Promo)
|
|
}
|
|
if subscribe.Discount[1].Promo == nil {
|
|
t.Fatal("quantity 12 promo should match")
|
|
}
|
|
if got, want := subscribe.Discount[1].Promo.RuleName, "年度优惠"; got != want {
|
|
t.Fatalf("promo rule name = %q, want %q", got, want)
|
|
}
|
|
|
|
subscribe = types.Subscribe{Discount: []types.SubscribeDiscount{{Quantity: 6}}}
|
|
applySubscribeDiscountPromos(&subscribe, promos)
|
|
if subscribe.Discount[0].Promo != nil {
|
|
t.Fatalf("promo should be nil when quantity does not match, got %+v", subscribe.Discount[0].Promo)
|
|
}
|
|
}
|