155 lines
4.3 KiB
Go
155 lines
4.3 KiB
Go
package service
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
membershipmodel "wx_service/internal/membership/model"
|
|
usermodel "wx_service/internal/model"
|
|
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/logger"
|
|
)
|
|
|
|
func setupRedeemTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
|
|
db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{
|
|
Logger: logger.Default.LogMode(logger.Silent),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("open sqlite: %v", err)
|
|
}
|
|
|
|
if err := db.AutoMigrate(
|
|
&membershipmodel.MembershipRedeemCode{},
|
|
&membershipmodel.MembershipRedemption{},
|
|
&usermodel.UserMembership{},
|
|
); err != nil {
|
|
t.Fatalf("auto migrate: %v", err)
|
|
}
|
|
|
|
return db
|
|
}
|
|
|
|
func seedRedeemCode(t *testing.T, db *gorm.DB, plainCode string, durationDays int, expiresAt *time.Time, maxUses int, status string) membershipmodel.MembershipRedeemCode {
|
|
t.Helper()
|
|
|
|
code := normalizeCode(plainCode)
|
|
record := membershipmodel.MembershipRedeemCode{
|
|
CodeHash: hashCode(code),
|
|
CodeSuffix: suffixOf(code, 6),
|
|
Plan: "month",
|
|
DurationDays: durationDays,
|
|
ExpiresAt: expiresAt,
|
|
MaxUses: maxUses,
|
|
UsedUses: 0,
|
|
Status: status,
|
|
}
|
|
if err := db.Create(&record).Error; err != nil {
|
|
t.Fatalf("seed redeem code: %v", err)
|
|
}
|
|
return record
|
|
}
|
|
|
|
func TestRedeemCodeServiceRedeemSuccessAndRepeat(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := setupRedeemTestDB(t)
|
|
svc := NewRedeemCodeService(db, "")
|
|
user := &usermodel.User{ID: 1, MiniProgramID: 10}
|
|
|
|
code := "TEST-CODE-001"
|
|
seedRedeemCode(t, db, code, 30, nil, 1, "active")
|
|
|
|
res, err := svc.Redeem(context.Background(), user, code, "127.0.0.1", "ut")
|
|
if err != nil {
|
|
t.Fatalf("redeem success: %v", err)
|
|
}
|
|
if res == nil || res.Extended {
|
|
t.Fatalf("first redeem should create membership and extended=false")
|
|
}
|
|
|
|
var membership usermodel.UserMembership
|
|
if err := db.Where("mini_program_id = ? AND user_id = ?", user.MiniProgramID, user.ID).First(&membership).Error; err != nil {
|
|
t.Fatalf("query membership: %v", err)
|
|
}
|
|
if membership.Status != "active" {
|
|
t.Fatalf("membership status=%s, want=active", membership.Status)
|
|
}
|
|
|
|
var storedCode membershipmodel.MembershipRedeemCode
|
|
if err := db.Where("code_hash = ?", hashCode(normalizeCode(code))).First(&storedCode).Error; err != nil {
|
|
t.Fatalf("query redeem code: %v", err)
|
|
}
|
|
if storedCode.UsedUses != 1 {
|
|
t.Fatalf("used_uses=%d, want=1", storedCode.UsedUses)
|
|
}
|
|
|
|
_, err = svc.Redeem(context.Background(), user, code, "127.0.0.1", "ut")
|
|
if !errors.Is(err, ErrRedeemCodeUsedUp) {
|
|
t.Fatalf("repeat redeem err=%v, want ErrRedeemCodeUsedUp", err)
|
|
}
|
|
}
|
|
|
|
func TestRedeemCodeServiceRedeemExpiredCode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := setupRedeemTestDB(t)
|
|
svc := NewRedeemCodeService(db, "")
|
|
user := &usermodel.User{ID: 2, MiniProgramID: 10}
|
|
|
|
expired := time.Now().Add(-1 * time.Hour)
|
|
code := "TEST-CODE-EXPIRED"
|
|
seedRedeemCode(t, db, code, 30, &expired, 1, "active")
|
|
|
|
_, err := svc.Redeem(context.Background(), user, code, "127.0.0.1", "ut")
|
|
if !errors.Is(err, ErrRedeemCodeExpired) {
|
|
t.Fatalf("redeem expired err=%v, want ErrRedeemCodeExpired", err)
|
|
}
|
|
}
|
|
|
|
func TestRedeemCodeServiceRedeemInvalidCode(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := setupRedeemTestDB(t)
|
|
svc := NewRedeemCodeService(db, "")
|
|
user := &usermodel.User{ID: 3, MiniProgramID: 10}
|
|
|
|
_, err := svc.Redeem(context.Background(), user, "NOT-FOUND-CODE", "127.0.0.1", "ut")
|
|
if !errors.Is(err, ErrRedeemCodeInvalid) {
|
|
t.Fatalf("redeem invalid err=%v, want ErrRedeemCodeInvalid", err)
|
|
}
|
|
}
|
|
|
|
func TestRedeemCodeServiceRedeemExtendsActiveMembership(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
db := setupRedeemTestDB(t)
|
|
svc := NewRedeemCodeService(db, "")
|
|
user := &usermodel.User{ID: 4, MiniProgramID: 10}
|
|
|
|
firstCode := "TEST-CODE-A"
|
|
secondCode := "TEST-CODE-B"
|
|
seedRedeemCode(t, db, firstCode, 10, nil, 1, "active")
|
|
seedRedeemCode(t, db, secondCode, 15, nil, 1, "active")
|
|
|
|
firstRes, err := svc.Redeem(context.Background(), user, firstCode, "127.0.0.1", "ut")
|
|
if err != nil {
|
|
t.Fatalf("first redeem: %v", err)
|
|
}
|
|
secondRes, err := svc.Redeem(context.Background(), user, secondCode, "127.0.0.1", "ut")
|
|
if err != nil {
|
|
t.Fatalf("second redeem: %v", err)
|
|
}
|
|
if secondRes == nil || !secondRes.Extended {
|
|
t.Fatalf("second redeem should extend existing membership")
|
|
}
|
|
if !secondRes.EndsAt.After(firstRes.EndsAt) {
|
|
t.Fatalf("second ends_at=%s should be after first ends_at=%s", secondRes.EndsAt, firstRes.EndsAt)
|
|
}
|
|
}
|