package subscribe import ( "context" "fmt" "strings" "testing" "time" "github.com/DATA-DOG/go-sqlmock" "github.com/perfect-panel/server/internal/model/promo" "github.com/perfect-panel/server/internal/svc" "github.com/perfect-panel/server/internal/types" "gorm.io/driver/mysql" "gorm.io/gorm" ) 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 TestLoadSubscribePromoMapUsesCommonPromoEvaluation(t *testing.T) { db, mock, cleanup := newSubscribePromoTestDB(t) defer cleanup() end := time.Now().Add(time.Hour) mock.ExpectQuery("FROM subscribe_promo AS sp"). WillReturnRows(sqlmock.NewRows([]string{ "subscribe_id", "quantity", "rule_name", "rule_type", "promo_price", "params", "start_time", "end_time", }).AddRow(11, 3, "old name", promoRuleTypeCampaign, 999, "", nil, end)) promoModel := &fakeSubscribePromoModel{rules: []*promo.RuleWithPrice{ { Rule: promo.Rule{ Id: 8, Name: "公共活动价", Type: promo.RuleTypeCampaign, Enabled: true, EndTime: &end, }, PromoPrice: 888, }, }} got, err := loadSubscribePromoMap(context.Background(), &svc.ServiceContext{DB: db, PromoModel: promoModel}, []int64{11}) if err != nil { t.Fatalf("loadSubscribePromoMap returned error: %v", err) } if err := mock.ExpectationsWereMet(); err != nil { t.Fatalf("sql expectations: %v", err) } if promoModel.lastSubscribeID != 11 { t.Fatalf("promo subscribe id = %d, want 11", promoModel.lastSubscribeID) } if promoModel.lastQuantity != 3 { t.Fatalf("promo quantity = %d, want 3", promoModel.lastQuantity) } item := got[11][3] if item == nil { t.Fatal("quantity 3 promo should be present") } if item.RuleName != "公共活动价" { t.Fatalf("RuleName = %q, want 公共活动价", item.RuleName) } if item.PromoPrice != 888 { t.Fatalf("PromoPrice = %d, want 888", item.PromoPrice) } if item.ExpiresAt != end.Unix() { t.Fatalf("ExpiresAt = %d, want %d", item.ExpiresAt, end.Unix()) } } type fakeSubscribePromoModel struct { rules []*promo.RuleWithPrice lastSubscribeID int64 lastQuantity int64 } func (m *fakeSubscribePromoModel) QueryEligibleRules(_ context.Context, subscribeID int64, quantity int64) ([]*promo.RuleWithPrice, error) { m.lastSubscribeID = subscribeID m.lastQuantity = quantity return m.rules, nil } func (m *fakeSubscribePromoModel) InsertUsage(context.Context, *promo.Usage, ...*gorm.DB) error { return nil } func (m *fakeSubscribePromoModel) InsertRule(context.Context, *promo.Rule) error { return nil } func (m *fakeSubscribePromoModel) FindRule(context.Context, int64) (*promo.Rule, error) { return nil, gorm.ErrRecordNotFound } func (m *fakeSubscribePromoModel) UpdateRule(context.Context, *promo.Rule) error { return nil } func (m *fakeSubscribePromoModel) DeleteRule(context.Context, int64) error { return nil } func (m *fakeSubscribePromoModel) QueryRuleList(context.Context, int, int, string, *bool, string) (int64, []*promo.Rule, error) { return 0, nil, nil } func (m *fakeSubscribePromoModel) UpsertPrices(context.Context, int64, []*promo.SubscribePromo) error { return nil } func (m *fakeSubscribePromoModel) FindPrice(context.Context, int64) (*promo.SubscribePromo, error) { return nil, gorm.ErrRecordNotFound } func (m *fakeSubscribePromoModel) DeletePrice(context.Context, int64) error { return nil } func (m *fakeSubscribePromoModel) QueryPriceList(context.Context, promo.PriceFilter) (int64, []*promo.SubscribePromo, error) { return 0, nil, nil } func (m *fakeSubscribePromoModel) QueryUsageList(context.Context, promo.UsageFilter) (int64, []*promo.Usage, error) { return 0, nil, nil } func (m *fakeSubscribePromoModel) Transaction(context.Context, func(*gorm.DB) error) error { return nil } func newSubscribePromoTestDB(t *testing.T) (*gorm.DB, sqlmock.Sqlmock, func()) { t.Helper() sqlDB, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherFunc(func(expectedSQL, actualSQL string) error { if strings.Contains(actualSQL, expectedSQL) { return nil } return fmt.Errorf("actual sql %q does not contain %q", actualSQL, expectedSQL) }))) if err != nil { t.Fatalf("create sqlmock: %v", err) } db, err := gorm.Open(mysql.New(mysql.Config{Conn: sqlDB, SkipInitializeWithVersion: true}), &gorm.Config{}) if err != nil { _ = sqlDB.Close() t.Fatalf("open gorm db: %v", err) } return db, mock, func() { _ = sqlDB.Close() } } 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) } }