Merge branch 'test'
This commit is contained in:
commit
81cdba4096
@ -375,6 +375,48 @@ func TestSendVIPUsesVIPLevelConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestVIPGiftCleanupExpiresOldVIPResourcesOnly(t *testing.T) {
|
||||
service, _ := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
if err := db.AutoMigrate(&managerVIPCleanupPropsRow{}, &managerVIPCleanupBadgeRow{}); err != nil {
|
||||
t.Fatalf("auto migrate cleanup tables: %v", err)
|
||||
}
|
||||
now := time.Date(2026, 5, 30, 10, 0, 0, 0, time.UTC)
|
||||
future := now.AddDate(0, 0, 20)
|
||||
userID := int64(2)
|
||||
|
||||
seedManagerVIPCleanupProps(t, db,
|
||||
managerVIPCleanupPropsRow{ID: 1, UserID: userID, PropsID: 101, Type: propsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true},
|
||||
managerVIPCleanupPropsRow{ID: 2, UserID: userID, PropsID: 201, Type: propsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true},
|
||||
managerVIPCleanupPropsRow{ID: 3, UserID: userID, PropsID: 999, Type: propsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true},
|
||||
managerVIPCleanupPropsRow{ID: 4, UserID: userID, PropsID: 103, Type: propsTypeDataCard, ExpireTime: future, UseProps: true, AllowGive: true},
|
||||
)
|
||||
seedManagerVIPCleanupBadges(t, db,
|
||||
managerVIPCleanupBadgeRow{ID: 1, UserID: userID, BadgeID: 100, ExpireTime: future, UseProps: true},
|
||||
managerVIPCleanupBadgeRow{ID: 2, UserID: userID, BadgeID: 200, ExpireTime: future, UseProps: true},
|
||||
)
|
||||
|
||||
target := vipGiftResourcesFromConfig(model.VipLevelConfig{
|
||||
BadgeResourceID: 200,
|
||||
AvatarFrameResourceID: 201,
|
||||
})
|
||||
all := append(vipGiftResourcesFromConfig(model.VipLevelConfig{
|
||||
BadgeResourceID: 100,
|
||||
AvatarFrameResourceID: 101,
|
||||
BackgroundCardResourceID: 103,
|
||||
}), target...)
|
||||
if err := service.cleanupSupersededVIPGiftResources(context.Background(), userID, target, all, now); err != nil {
|
||||
t.Fatalf("cleanupSupersededVIPGiftResources() error = %v", err)
|
||||
}
|
||||
|
||||
assertManagerVIPCleanupProps(t, db, 101, now, false, false)
|
||||
assertManagerVIPCleanupProps(t, db, 103, now, false, false)
|
||||
assertManagerVIPCleanupProps(t, db, 201, future, true, true)
|
||||
assertManagerVIPCleanupProps(t, db, 999, future, true, true)
|
||||
assertManagerVIPCleanupBadge(t, db, 100, now, false)
|
||||
assertManagerVIPCleanupBadge(t, db, 200, future, true)
|
||||
}
|
||||
|
||||
func TestSendVIPRejectsLevelAboveGiftLimit(t *testing.T) {
|
||||
service, _ := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
@ -400,6 +442,77 @@ func TestSendVIPRejectsLevelAboveGiftLimit(t *testing.T) {
|
||||
assertAppErrorCode(t, err, "vip_not_giftable")
|
||||
}
|
||||
|
||||
type managerVIPCleanupPropsRow struct {
|
||||
ID int64 `gorm:"column:id;primaryKey"`
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
PropsID int64 `gorm:"column:props_id"`
|
||||
Type string `gorm:"column:type"`
|
||||
ExpireTime time.Time `gorm:"column:expire_time"`
|
||||
UseProps bool `gorm:"column:is_use_props"`
|
||||
AllowGive bool `gorm:"column:allow_give"`
|
||||
UpdateTime time.Time `gorm:"column:update_time"`
|
||||
}
|
||||
|
||||
func (managerVIPCleanupPropsRow) TableName() string {
|
||||
return "user_props_backpack"
|
||||
}
|
||||
|
||||
type managerVIPCleanupBadgeRow struct {
|
||||
ID int64 `gorm:"column:id;primaryKey"`
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
BadgeID int64 `gorm:"column:badge_id"`
|
||||
ExpireType string `gorm:"column:expire_type"`
|
||||
ExpireTime time.Time `gorm:"column:expire_time"`
|
||||
UseProps bool `gorm:"column:is_use_props"`
|
||||
UpdateTime time.Time `gorm:"column:update_time"`
|
||||
}
|
||||
|
||||
func (managerVIPCleanupBadgeRow) TableName() string {
|
||||
return "user_badge_backpack"
|
||||
}
|
||||
|
||||
func seedManagerVIPCleanupProps(t *testing.T, db *gorm.DB, rows ...managerVIPCleanupPropsRow) {
|
||||
t.Helper()
|
||||
for _, row := range rows {
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
t.Fatalf("seed manager cleanup props: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func seedManagerVIPCleanupBadges(t *testing.T, db *gorm.DB, rows ...managerVIPCleanupBadgeRow) {
|
||||
t.Helper()
|
||||
for _, row := range rows {
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
t.Fatalf("seed manager cleanup badge: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertManagerVIPCleanupProps(t *testing.T, db *gorm.DB, propsID int64, expireTime time.Time, useProps bool, allowGive bool) {
|
||||
t.Helper()
|
||||
var row managerVIPCleanupPropsRow
|
||||
if err := db.Where("props_id = ?", propsID).First(&row).Error; err != nil {
|
||||
t.Fatalf("load manager cleanup props %d: %v", propsID, err)
|
||||
}
|
||||
if !row.ExpireTime.Equal(expireTime) || row.UseProps != useProps || row.AllowGive != allowGive {
|
||||
t.Fatalf("props %d = expire:%s use:%v allow:%v, want expire:%s use:%v allow:%v",
|
||||
propsID, row.ExpireTime, row.UseProps, row.AllowGive, expireTime, useProps, allowGive)
|
||||
}
|
||||
}
|
||||
|
||||
func assertManagerVIPCleanupBadge(t *testing.T, db *gorm.DB, badgeID int64, expireTime time.Time, useProps bool) {
|
||||
t.Helper()
|
||||
var row managerVIPCleanupBadgeRow
|
||||
if err := db.Where("badge_id = ?", badgeID).First(&row).Error; err != nil {
|
||||
t.Fatalf("load manager cleanup badge %d: %v", badgeID, err)
|
||||
}
|
||||
if !row.ExpireTime.Equal(expireTime) || row.UseProps != useProps {
|
||||
t.Fatalf("badge %d = expire:%s use:%v, want expire:%s use:%v",
|
||||
badgeID, row.ExpireTime, row.UseProps, expireTime, useProps)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBanUserRejectsRechargedUser(t *testing.T) {
|
||||
service, fake := newManagerCenterTestService(t)
|
||||
db := service.db.(*gorm.DB)
|
||||
|
||||
@ -171,6 +171,7 @@ func (s *Service) sendVIPGift(ctx context.Context, manager AuthUser, target user
|
||||
CreateTime: now,
|
||||
}
|
||||
}
|
||||
previousResources := vipGiftResourcesFromState(state)
|
||||
applyVIPGiftConfigToState(state, config, startAt, expireAt, now)
|
||||
if err := tx.Save(state).Error; err != nil {
|
||||
return nil, serverError("vip_state_save_failed", err.Error())
|
||||
@ -209,7 +210,7 @@ func (s *Service) sendVIPGift(ctx context.Context, manager AuthUser, target user
|
||||
}
|
||||
committed = true
|
||||
|
||||
if err := s.grantVIPGiftResources(ctx, target.ID, manager.UserID, config, expireAt, now); err != nil {
|
||||
if err := s.grantVIPGiftResources(ctx, sysOrigin, target.ID, manager.UserID, config, expireAt, previousResources, now); err != nil {
|
||||
return nil, serverError("vip_resource_grant_failed", err.Error())
|
||||
}
|
||||
|
||||
@ -301,11 +302,8 @@ func applyVIPGiftConfigToState(state *model.UserVipState, config model.VipLevelC
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Service) grantVIPGiftResources(ctx context.Context, userID int64, managerID int64, config model.VipLevelConfig, expireAt time.Time, now time.Time) error {
|
||||
func (s *Service) grantVIPGiftResources(ctx context.Context, sysOrigin string, userID int64, managerID int64, config model.VipLevelConfig, expireAt time.Time, additionalOldResources []vipGiftResource, now time.Time) error {
|
||||
resources := vipGiftResourcesFromConfig(config)
|
||||
if len(resources) == 0 {
|
||||
return nil
|
||||
}
|
||||
days := config.DurationDays
|
||||
if days <= 0 {
|
||||
days = defaultVIPGiftDays
|
||||
@ -337,6 +335,14 @@ func (s *Service) grantVIPGiftResources(ctx context.Context, userID int64, manag
|
||||
return err
|
||||
}
|
||||
}
|
||||
allResources, err := s.vipGiftResourcesFromSysOrigin(ctx, sysOrigin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
allResources = append(allResources, additionalOldResources...)
|
||||
if err := s.cleanupSupersededVIPGiftResources(ctx, userID, resources, allResources, now); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.java.RemoveUserProfileCacheAll(ctx, userID); err != nil {
|
||||
return fmt.Errorf("remove vip user profile cache: %w", err)
|
||||
}
|
||||
@ -374,6 +380,7 @@ func vipGiftResourcesFromConfig(config model.VipLevelConfig) []vipGiftResource {
|
||||
{resourceID: config.EntryEffectResourceID, propsType: propsTypeRide},
|
||||
{resourceID: config.ChatBubbleResourceID, propsType: propsTypeChatBubble},
|
||||
{resourceID: config.FloatPictureResourceID, propsType: propsTypeFloatPicture},
|
||||
{resourceID: config.BackgroundCardResourceID, propsType: propsTypeDataCard},
|
||||
}
|
||||
out := make([]vipGiftResource, 0, len(resources))
|
||||
for _, resource := range resources {
|
||||
@ -384,6 +391,104 @@ func vipGiftResourcesFromConfig(config model.VipLevelConfig) []vipGiftResource {
|
||||
return out
|
||||
}
|
||||
|
||||
func vipGiftResourcesFromState(state *model.UserVipState) []vipGiftResource {
|
||||
if state == nil {
|
||||
return nil
|
||||
}
|
||||
return compactVIPGiftResources([]vipGiftResource{
|
||||
{resourceID: state.BadgeResourceID, propsType: propsTypeBadge, badge: true},
|
||||
{resourceID: state.ShortBadgeResourceID, propsType: propsTypeBadge, badge: true},
|
||||
{resourceID: state.AvatarFrameResourceID, propsType: propsTypeAvatarFrame},
|
||||
{resourceID: state.EntryEffectResourceID, propsType: propsTypeRide},
|
||||
{resourceID: state.ChatBubbleResourceID, propsType: propsTypeChatBubble},
|
||||
{resourceID: state.FloatPictureResourceID, propsType: propsTypeFloatPicture},
|
||||
{resourceID: state.BackgroundCardResourceID, propsType: propsTypeDataCard},
|
||||
})
|
||||
}
|
||||
|
||||
func compactVIPGiftResources(resources []vipGiftResource) []vipGiftResource {
|
||||
out := make([]vipGiftResource, 0, len(resources))
|
||||
for _, resource := range resources {
|
||||
if resource.resourceID > 0 {
|
||||
out = append(out, resource)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Service) vipGiftResourcesFromSysOrigin(ctx context.Context, sysOrigin string) ([]vipGiftResource, error) {
|
||||
var configs []model.VipLevelConfig
|
||||
if err := s.db.WithContext(ctx).
|
||||
Where("sys_origin = ?", normalizeSysOrigin(sysOrigin)).
|
||||
Find(&configs).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resources := make([]vipGiftResource, 0, len(configs)*7)
|
||||
for _, config := range configs {
|
||||
resources = append(resources, vipGiftResourcesFromConfig(config)...)
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
func (s *Service) cleanupSupersededVIPGiftResources(ctx context.Context, userID int64, targetResources []vipGiftResource, allVIPResources []vipGiftResource, now time.Time) error {
|
||||
targets := make(map[string]struct{}, len(targetResources))
|
||||
for _, resource := range targetResources {
|
||||
targets[vipGiftResourceKey(resource)] = struct{}{}
|
||||
}
|
||||
|
||||
badgeIDs := make([]int64, 0)
|
||||
propsIDsByType := map[string][]int64{}
|
||||
seen := make(map[string]struct{}, len(allVIPResources))
|
||||
for _, resource := range allVIPResources {
|
||||
if resource.resourceID <= 0 {
|
||||
continue
|
||||
}
|
||||
key := vipGiftResourceKey(resource)
|
||||
if _, exists := targets[key]; exists {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[key]; exists {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
if resource.badge {
|
||||
badgeIDs = append(badgeIDs, resource.resourceID)
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(resource.propsType) != "" {
|
||||
propsIDsByType[resource.propsType] = append(propsIDsByType[resource.propsType], resource.resourceID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(badgeIDs) > 0 {
|
||||
if err := s.db.WithContext(ctx).Exec(
|
||||
"UPDATE user_badge_backpack SET expire_type = ?, expire_time = ?, is_use_props = ?, update_time = ? WHERE user_id = ? AND badge_id IN ? AND expire_time > ?",
|
||||
"TEMPORARY", now, false, now, userID, badgeIDs, now,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for propsType, propsIDs := range propsIDsByType {
|
||||
if len(propsIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Exec(
|
||||
"UPDATE user_props_backpack SET expire_time = ?, is_use_props = ?, allow_give = ?, update_time = ? WHERE user_id = ? AND props_id IN ? AND type = ? AND expire_time > ?",
|
||||
now, false, false, now, userID, propsIDs, propsType, now,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func vipGiftResourceKey(resource vipGiftResource) string {
|
||||
if resource.badge {
|
||||
return fmt.Sprintf("BADGE:%d", resource.resourceID)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", resource.propsType, resource.resourceID)
|
||||
}
|
||||
|
||||
func (s *Service) managerApplicantName(ctx context.Context, managerID int64) string {
|
||||
row, err := s.loadUserRowByID(ctx, managerID)
|
||||
if err != nil {
|
||||
|
||||
@ -170,7 +170,7 @@ func (s *Service) Purchase(ctx context.Context, user AuthUser, req PurchaseReque
|
||||
if stateErr != nil {
|
||||
return nil, stateErr
|
||||
}
|
||||
if err := s.ensureStateResourcesGranted(ctx, user.UserID, state, now); err != nil {
|
||||
if err := s.ensureStateResourcesGranted(ctx, sysOrigin, user.UserID, state, now); err != nil {
|
||||
return nil, NewAppError(http.StatusBadGateway, "vip_resource_grant_failed", err.Error())
|
||||
}
|
||||
resources, resourceErr := s.loadResourceLookup(ctx, appendResourceIDsFromState(nil, state))
|
||||
@ -274,6 +274,7 @@ func (s *Service) Purchase(ctx context.Context, user AuthUser, req PurchaseReque
|
||||
}
|
||||
}
|
||||
|
||||
previousResources := vipGrantResourcesFromState(state)
|
||||
applyPlanToState(state, plan, now)
|
||||
order.Status = orderStatusSuccess
|
||||
order.ErrorMessage = ""
|
||||
@ -289,7 +290,7 @@ func (s *Service) Purchase(ctx context.Context, user AuthUser, req PurchaseReque
|
||||
}
|
||||
committed = true
|
||||
|
||||
if err := s.ensurePlanResourcesGranted(ctx, user.UserID, plan, now); err != nil {
|
||||
if err := s.ensurePlanResourcesGranted(ctx, sysOrigin, user.UserID, plan, configs, previousResources, now); err != nil {
|
||||
return nil, NewAppError(http.StatusBadGateway, "vip_resource_grant_failed", err.Error())
|
||||
}
|
||||
|
||||
@ -536,15 +537,28 @@ func expireState(state *model.UserVipState, now time.Time) {
|
||||
state.UpdateTime = now
|
||||
}
|
||||
|
||||
func (s *Service) ensurePlanResourcesGranted(ctx context.Context, userID int64, plan purchasePlan, now time.Time) error {
|
||||
return s.ensureVIPResourcesGranted(ctx, userID, vipGrantResourcesFromConfig(plan.TargetConfig), plan.ExpireAt, plan.DurationDays, now)
|
||||
func (s *Service) ensurePlanResourcesGranted(ctx context.Context, sysOrigin string, userID int64, plan purchasePlan, configs map[int]model.VipLevelConfig, additionalOldResources []vipGrantResource, now time.Time) error {
|
||||
targetResources := vipGrantResourcesFromConfig(plan.TargetConfig)
|
||||
if err := s.ensureVIPResourcesGranted(ctx, userID, targetResources, plan.ExpireAt, plan.DurationDays, now); err != nil {
|
||||
return err
|
||||
}
|
||||
allVIPResources := append(vipGrantResourcesFromConfigs(configs), additionalOldResources...)
|
||||
return s.cleanupSupersededVIPResources(ctx, userID, targetResources, allVIPResources, now)
|
||||
}
|
||||
|
||||
func (s *Service) ensureStateResourcesGranted(ctx context.Context, userID int64, state *model.UserVipState, now time.Time) error {
|
||||
func (s *Service) ensureStateResourcesGranted(ctx context.Context, sysOrigin string, userID int64, state *model.UserVipState, now time.Time) error {
|
||||
if !isActiveState(state, now) {
|
||||
return nil
|
||||
}
|
||||
return s.ensureVIPResourcesGranted(ctx, userID, vipGrantResourcesFromState(state), state.ExpireAt, vipGrantDaysUntil(now, state.ExpireAt), now)
|
||||
targetResources := vipGrantResourcesFromState(state)
|
||||
if err := s.ensureVIPResourcesGranted(ctx, userID, targetResources, state.ExpireAt, vipGrantDaysUntil(now, state.ExpireAt), now); err != nil {
|
||||
return err
|
||||
}
|
||||
configs, err := s.loadConfigMap(ctx, sysOrigin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return s.cleanupSupersededVIPResources(ctx, userID, targetResources, vipGrantResourcesFromConfigs(configs), now)
|
||||
}
|
||||
|
||||
func (s *Service) ensureVIPResourcesGranted(ctx context.Context, userID int64, resources []vipGrantResource, expireAt time.Time, days int, now time.Time) error {
|
||||
@ -685,6 +699,17 @@ func vipGrantResourcesFromState(state *model.UserVipState) []vipGrantResource {
|
||||
})
|
||||
}
|
||||
|
||||
func vipGrantResourcesFromConfigs(configs map[int]model.VipLevelConfig) []vipGrantResource {
|
||||
if len(configs) == 0 {
|
||||
return nil
|
||||
}
|
||||
resources := make([]vipGrantResource, 0, len(configs)*7)
|
||||
for _, config := range configs {
|
||||
resources = append(resources, vipGrantResourcesFromConfig(config)...)
|
||||
}
|
||||
return compactVIPGrantResources(resources)
|
||||
}
|
||||
|
||||
func compactVIPGrantResources(resources []vipGrantResource) []vipGrantResource {
|
||||
compacted := make([]vipGrantResource, 0, len(resources))
|
||||
for _, resource := range resources {
|
||||
@ -695,6 +720,65 @@ func compactVIPGrantResources(resources []vipGrantResource) []vipGrantResource {
|
||||
return compacted
|
||||
}
|
||||
|
||||
func (s *Service) cleanupSupersededVIPResources(ctx context.Context, userID int64, targetResources []vipGrantResource, allVIPResources []vipGrantResource, now time.Time) error {
|
||||
targets := make(map[string]struct{}, len(targetResources))
|
||||
for _, resource := range targetResources {
|
||||
targets[vipGrantResourceKey(resource)] = struct{}{}
|
||||
}
|
||||
|
||||
badgeIDs := make([]int64, 0)
|
||||
propsIDsByType := map[string][]int64{}
|
||||
seen := make(map[string]struct{}, len(allVIPResources))
|
||||
for _, resource := range allVIPResources {
|
||||
if resource.ResourceID <= 0 {
|
||||
continue
|
||||
}
|
||||
key := vipGrantResourceKey(resource)
|
||||
if _, exists := targets[key]; exists {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[key]; exists {
|
||||
continue
|
||||
}
|
||||
seen[key] = struct{}{}
|
||||
if resource.Badge {
|
||||
badgeIDs = append(badgeIDs, resource.ResourceID)
|
||||
continue
|
||||
}
|
||||
if strings.TrimSpace(resource.PropsType) != "" {
|
||||
propsIDsByType[resource.PropsType] = append(propsIDsByType[resource.PropsType], resource.ResourceID)
|
||||
}
|
||||
}
|
||||
|
||||
if len(badgeIDs) > 0 {
|
||||
if err := s.db.WithContext(ctx).Exec(
|
||||
"UPDATE user_badge_backpack SET expire_type = ?, expire_time = ?, is_use_props = ?, update_time = ? WHERE user_id = ? AND badge_id IN ? AND expire_time > ?",
|
||||
"TEMPORARY", now, false, now, userID, badgeIDs, now,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for propsType, propsIDs := range propsIDsByType {
|
||||
if len(propsIDs) == 0 {
|
||||
continue
|
||||
}
|
||||
if err := s.db.WithContext(ctx).Exec(
|
||||
"UPDATE user_props_backpack SET expire_time = ?, is_use_props = ?, allow_give = ?, update_time = ? WHERE user_id = ? AND props_id IN ? AND type = ? AND expire_time > ?",
|
||||
now, false, false, now, userID, propsIDs, propsType, now,
|
||||
).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func vipGrantResourceKey(resource vipGrantResource) string {
|
||||
if resource.Badge {
|
||||
return fmt.Sprintf("BADGE:%d", resource.ResourceID)
|
||||
}
|
||||
return fmt.Sprintf("%s:%d", resource.PropsType, resource.ResourceID)
|
||||
}
|
||||
|
||||
func vipGrantDaysUntil(now time.Time, expireAt time.Time) int {
|
||||
if !expireAt.After(now) {
|
||||
return 1
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package vip
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
@ -9,6 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
"chatapp3-golang/internal/model"
|
||||
|
||||
"gorm.io/driver/sqlite"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
func TestBuildPurchasePlanNewPurchase(t *testing.T) {
|
||||
@ -322,6 +326,47 @@ func TestVIPGrantResourcesFromConfigMapsBackpackTypes(t *testing.T) {
|
||||
assertGrant(6, 16, vipPropsTypeDataCard, false)
|
||||
}
|
||||
|
||||
func TestCleanupSupersededVIPResourcesExpiresOnlyOldVIPResources(t *testing.T) {
|
||||
db := openVIPCleanupTestDB(t)
|
||||
service := &Service{db: db}
|
||||
now := time.Date(2026, 5, 30, 10, 0, 0, 0, time.UTC)
|
||||
future := now.AddDate(0, 0, 20)
|
||||
oldTime := now.AddDate(0, 0, -1)
|
||||
userID := int64(1001)
|
||||
|
||||
seedVIPCleanupRows(t, db,
|
||||
vipCleanupPropsRow{ID: 1, UserID: userID, PropsID: 101, Type: vipPropsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true, UpdateTime: oldTime},
|
||||
vipCleanupPropsRow{ID: 2, UserID: userID, PropsID: 201, Type: vipPropsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true, UpdateTime: oldTime},
|
||||
vipCleanupPropsRow{ID: 3, UserID: userID, PropsID: 999, Type: vipPropsTypeAvatarFrame, ExpireTime: future, UseProps: true, AllowGive: true, UpdateTime: oldTime},
|
||||
vipCleanupPropsRow{ID: 4, UserID: userID, PropsID: 102, Type: vipPropsTypeRide, ExpireTime: future, UseProps: true, AllowGive: true, UpdateTime: oldTime},
|
||||
)
|
||||
seedVIPCleanupBadges(t, db,
|
||||
vipCleanupBadgeRow{ID: 1, UserID: userID, BadgeID: 100, ExpireTime: future, UseProps: true, UpdateTime: oldTime},
|
||||
vipCleanupBadgeRow{ID: 2, UserID: userID, BadgeID: 200, ExpireTime: future, UseProps: true, UpdateTime: oldTime},
|
||||
)
|
||||
|
||||
target := vipGrantResourcesFromConfig(model.VipLevelConfig{
|
||||
BadgeResourceID: 200,
|
||||
AvatarFrameResourceID: 201,
|
||||
})
|
||||
all := append(vipGrantResourcesFromConfig(model.VipLevelConfig{
|
||||
BadgeResourceID: 100,
|
||||
AvatarFrameResourceID: 101,
|
||||
EntryEffectResourceID: 102,
|
||||
}), target...)
|
||||
|
||||
if err := service.cleanupSupersededVIPResources(context.Background(), userID, target, all, now); err != nil {
|
||||
t.Fatalf("cleanupSupersededVIPResources() error = %v", err)
|
||||
}
|
||||
|
||||
assertVIPCleanupProps(t, db, 101, now, false, false)
|
||||
assertVIPCleanupProps(t, db, 102, now, false, false)
|
||||
assertVIPCleanupProps(t, db, 201, future, true, true)
|
||||
assertVIPCleanupProps(t, db, 999, future, true, true)
|
||||
assertVIPCleanupBadge(t, db, 100, now, false)
|
||||
assertVIPCleanupBadge(t, db, 200, future, true)
|
||||
}
|
||||
|
||||
func TestVIPGrantDaysUntilCeilsPartialDay(t *testing.T) {
|
||||
now := time.Date(2026, 4, 29, 10, 0, 0, 0, time.UTC)
|
||||
expireAt := now.Add((24 * time.Hour) + time.Second)
|
||||
@ -334,6 +379,89 @@ func TestVIPGrantDaysUntilCeilsPartialDay(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
type vipCleanupPropsRow struct {
|
||||
ID int64 `gorm:"column:id;primaryKey"`
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
PropsID int64 `gorm:"column:props_id"`
|
||||
Type string `gorm:"column:type"`
|
||||
ExpireTime time.Time `gorm:"column:expire_time"`
|
||||
UseProps bool `gorm:"column:is_use_props"`
|
||||
AllowGive bool `gorm:"column:allow_give"`
|
||||
UpdateTime time.Time `gorm:"column:update_time"`
|
||||
}
|
||||
|
||||
func (vipCleanupPropsRow) TableName() string {
|
||||
return "user_props_backpack"
|
||||
}
|
||||
|
||||
type vipCleanupBadgeRow struct {
|
||||
ID int64 `gorm:"column:id;primaryKey"`
|
||||
UserID int64 `gorm:"column:user_id"`
|
||||
BadgeID int64 `gorm:"column:badge_id"`
|
||||
ExpireType string `gorm:"column:expire_type"`
|
||||
ExpireTime time.Time `gorm:"column:expire_time"`
|
||||
UseProps bool `gorm:"column:is_use_props"`
|
||||
UpdateTime time.Time `gorm:"column:update_time"`
|
||||
}
|
||||
|
||||
func (vipCleanupBadgeRow) TableName() string {
|
||||
return "user_badge_backpack"
|
||||
}
|
||||
|
||||
func openVIPCleanupTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
|
||||
if err != nil {
|
||||
t.Fatalf("open sqlite: %v", err)
|
||||
}
|
||||
if err := db.AutoMigrate(&vipCleanupPropsRow{}, &vipCleanupBadgeRow{}); err != nil {
|
||||
t.Fatalf("auto migrate cleanup tables: %v", err)
|
||||
}
|
||||
return db
|
||||
}
|
||||
|
||||
func seedVIPCleanupRows(t *testing.T, db *gorm.DB, rows ...vipCleanupPropsRow) {
|
||||
t.Helper()
|
||||
for _, row := range rows {
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
t.Fatalf("seed props row: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func seedVIPCleanupBadges(t *testing.T, db *gorm.DB, rows ...vipCleanupBadgeRow) {
|
||||
t.Helper()
|
||||
for _, row := range rows {
|
||||
if err := db.Create(&row).Error; err != nil {
|
||||
t.Fatalf("seed badge row: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func assertVIPCleanupProps(t *testing.T, db *gorm.DB, propsID int64, expireTime time.Time, useProps bool, allowGive bool) {
|
||||
t.Helper()
|
||||
var row vipCleanupPropsRow
|
||||
if err := db.Where("props_id = ?", propsID).First(&row).Error; err != nil {
|
||||
t.Fatalf("load props %d: %v", propsID, err)
|
||||
}
|
||||
if !row.ExpireTime.Equal(expireTime) || row.UseProps != useProps || row.AllowGive != allowGive {
|
||||
t.Fatalf("props %d = expire:%s use:%v allow:%v, want expire:%s use:%v allow:%v",
|
||||
propsID, row.ExpireTime, row.UseProps, row.AllowGive, expireTime, useProps, allowGive)
|
||||
}
|
||||
}
|
||||
|
||||
func assertVIPCleanupBadge(t *testing.T, db *gorm.DB, badgeID int64, expireTime time.Time, useProps bool) {
|
||||
t.Helper()
|
||||
var row vipCleanupBadgeRow
|
||||
if err := db.Where("badge_id = ?", badgeID).First(&row).Error; err != nil {
|
||||
t.Fatalf("load badge %d: %v", badgeID, err)
|
||||
}
|
||||
if !row.ExpireTime.Equal(expireTime) || row.UseProps != useProps {
|
||||
t.Fatalf("badge %d = expire:%s use:%v, want expire:%s use:%v",
|
||||
badgeID, row.ExpireTime, row.UseProps, expireTime, useProps)
|
||||
}
|
||||
}
|
||||
|
||||
func vipTestConfigs() map[int]model.VipLevelConfig {
|
||||
configs := make(map[int]model.VipLevelConfig, levelCount)
|
||||
for level := 1; level <= levelCount; level++ {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user