package repository import ( "testing" "time" "github.com/stretchr/testify/assert" "github.com/topfans/backend/pkg/database" "github.com/topfans/backend/pkg/models" "github.com/topfans/backend/services/galleryService/config" "gorm.io/gorm" ) // setupTestDB 设置测试数据库 func setupTestDB(t *testing.T) *gorm.DB { dbConfig := database.Config{ Host: "localhost", Port: 5432, User: "haihuizhu", Password: "admin", DBName: "top-fans", SSLMode: "disable", TimeZone: "Asia/Shanghai", } if err := database.Init(dbConfig); err != nil { t.Skipf("Skipping test: failed to connect to test database: %v", err) } db := database.GetDB() // 迁移展馆相关表 if err := db.AutoMigrate(&models.BoothSlot{}, &models.Exhibition{}); err != nil { t.Logf("Warning: Failed to migrate gallery tables (may already exist): %v", err) } // 测试开始前先清理一次,确保测试环境干净 cleanupTestDB(t, db) return db } // cleanupTestDB 清理测试数据 func cleanupTestDB(t *testing.T, db *gorm.DB) { // 清理测试数据(注意外键约束顺序) // 先清理展品展示记录,再清理展位 db.Exec("DELETE FROM exhibitions WHERE occupier_uid IN (SELECT id FROM users WHERE mobile LIKE '199%')") db.Exec("DELETE FROM booth_slots WHERE user_id IN (SELECT id FROM users WHERE mobile LIKE '199%')") } // createTestStar 创建测试用明星 func createTestStar(t *testing.T, db *gorm.DB, identityID string) *models.Star { // 先检查是否已存在 var existingStar models.Star if err := db.Where("identity_id = ?", identityID).First(&existingStar).Error; err == nil { // 已存在,返回现有的 return &existingStar } // 不存在,创建新的 star := &models.Star{ Name: "测试明星-" + identityID, IdentityID: identityID, IsActive: true, } if err := db.Create(star).Error; err != nil { t.Fatalf("Failed to create test star: %v", err) } return star } // createTestUser 创建测试用户 func createTestUser(t *testing.T, db *gorm.DB, mobile string) *models.User { user := &models.User{ Mobile: mobile, PasswordHash: "test_hash", IsActive: true, } if err := db.Create(user).Error; err != nil { t.Fatalf("Failed to create test user: %v", err) } return user } // createTestFanProfile 创建测试粉丝档案 func createTestFanProfile(t *testing.T, db *gorm.DB, userID, starID int64, nickname string) *models.FanProfile { profile := &models.FanProfile{ UserID: userID, StarID: starID, Nickname: nickname, Level: 1, } if err := db.Create(profile).Error; err != nil { t.Fatalf("Failed to create test fan profile: %v", err) } return profile } // TestCreateInitialSlots 测试创建初始展位 func TestCreateInitialSlots(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_001") user := createTestUser(t, db, "19900000001") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户001") // 测试创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err, "创建初始展位应该成功") // 验证展位数量 count, err := repo.GetSlotCount(user.ID, star.StarID) assert.NoError(t, err) assert.Equal(t, int64(config.GalleryRules.InitialSlotCount), count, "初始展位数量应该为3") // 验证展位信息 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) assert.Len(t, slots, config.GalleryRules.InitialSlotCount, "展位列表长度应该为3") // 验证展位属性 for i, slot := range slots { assert.Equal(t, i+1, slot.SlotIndex, "展位序号应该从1开始") assert.True(t, slot.IsEnabled, "初始展位应该已解锁") assert.Equal(t, "public", slot.Visibility, "初始展位应该是公有的") assert.Equal(t, "free", slot.UnlockType, "初始展位应该是免费的") assert.Equal(t, 0, slot.UnlockValue, "初始展位解锁值应该为0") } } // TestCreateInitialSlots_Idempotent 测试懒加载的幂等性(避免重复创建) func TestCreateInitialSlots_Idempotent(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_002") user := createTestUser(t, db, "19900000002") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户002") // 第一次创建 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err, "第一次创建应该成功") // 第二次创建(应该不会报错,也不会重复创建) err = repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err, "第二次创建应该不报错(幂等性)") // 验证展位数量仍然是3 count, err := repo.GetSlotCount(user.ID, star.StarID) assert.NoError(t, err) assert.Equal(t, int64(config.GalleryRules.InitialSlotCount), count, "展位数量应该仍然为3,不会重复创建") } // TestGetSlotsByUser 测试查询展位列表 func TestGetSlotsByUser(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_003") user := createTestUser(t, db, "19900000003") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户003") // 创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err) // 查询展位列表 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) assert.Len(t, slots, config.GalleryRules.InitialSlotCount, "应该返回3个展位") // 验证排序(按 slot_index 升序) for i, slot := range slots { assert.Equal(t, i+1, slot.SlotIndex, "展位应该按序号升序排列") } } // TestGetSlotByID 测试根据ID获取展位 func TestGetSlotByID(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_004") user := createTestUser(t, db, "19900000004") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户004") // 创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err) // 获取展位列表 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) assert.NotEmpty(t, slots) // 根据ID获取第一个展位 firstSlot := slots[0] slot, err := repo.GetSlotByID(firstSlot.SlotID) assert.NoError(t, err) assert.NotNil(t, slot) assert.Equal(t, firstSlot.SlotID, slot.SlotID, "展位ID应该匹配") assert.Equal(t, firstSlot.SlotIndex, slot.SlotIndex, "展位序号应该匹配") // 测试获取不存在的展位 _, err = repo.GetSlotByID(999999) assert.Error(t, err, "获取不存在的展位应该返回错误") } // TestCreateSlot 测试创建新展位(解锁) func TestCreateSlot(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_005") user := createTestUser(t, db, "19900000005") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户005") // 创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err) // 创建第4个展位(需要解锁) hostProfileID := generateHostProfileID(user.ID, star.StarID) newSlot := &models.BoothSlot{ HostProfileID: hostProfileID, UserID: user.ID, StarID: star.StarID, SlotIndex: 4, Visibility: "public", IsEnabled: true, UnlockType: "level", UnlockValue: 5, } err = repo.CreateSlot(newSlot) assert.NoError(t, err, "创建新展位应该成功") // 验证展位数量 count, err := repo.GetSlotCount(user.ID, star.StarID) assert.NoError(t, err) assert.Equal(t, int64(4), count, "展位数量应该为4") // 验证新展位信息 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) assert.Len(t, slots, 4, "展位列表长度应该为4") lastSlot := slots[3] assert.Equal(t, 4, lastSlot.SlotIndex, "新展位序号应该为4") assert.True(t, lastSlot.IsEnabled, "新展位应该已解锁") assert.Equal(t, "level", lastSlot.UnlockType, "解锁方式应该为等级") assert.Equal(t, 5, lastSlot.UnlockValue, "解锁条件应该为等级5") } // TestCreateExhibition 测试创建展品展示记录 func TestCreateExhibition(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_006") user := createTestUser(t, db, "19900000006") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户006") // 创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err) // 获取第一个展位 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) firstSlot := slots[0] // 创建展品展示记录 now := time.Now().UnixMilli() expireAt := now + config.GalleryRules.GrabSlotDuration*1000 exhibition := &models.Exhibition{ AssetID: 12345, SlotID: firstSlot.SlotID, HostProfileID: firstSlot.HostProfileID, OccupierUID: user.ID, OccupierStarID: star.StarID, StartTime: now, ExpireAt: expireAt, } err = repo.CreateExhibition(exhibition) assert.NoError(t, err, "创建展品展示记录应该成功") // 验证展品展示记录 fetchedExhibition, err := repo.GetExhibitionByAsset(12345) assert.NoError(t, err) assert.NotNil(t, fetchedExhibition) assert.Equal(t, int64(12345), fetchedExhibition.AssetID, "资产ID应该匹配") assert.Equal(t, firstSlot.SlotID, fetchedExhibition.SlotID, "展位ID应该匹配") } // TestGetExhibitionBySlot 测试根据展位ID获取展品展示记录 func TestGetExhibitionBySlot(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_007") user := createTestUser(t, db, "19900000007") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户007") // 创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err) // 获取第一个展位 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) firstSlot := slots[0] // 创建展品展示记录 now := time.Now().UnixMilli() expireAt := now + config.GalleryRules.GrabSlotDuration*1000 exhibition := &models.Exhibition{ AssetID: 23456, SlotID: firstSlot.SlotID, HostProfileID: firstSlot.HostProfileID, OccupierUID: user.ID, OccupierStarID: star.StarID, StartTime: now, ExpireAt: expireAt, } err = repo.CreateExhibition(exhibition) assert.NoError(t, err) // 根据展位ID获取展品展示记录 fetchedExhibition, err := repo.GetExhibitionBySlot(firstSlot.SlotID) assert.NoError(t, err) assert.NotNil(t, fetchedExhibition) assert.Equal(t, int64(23456), fetchedExhibition.AssetID, "资产ID应该匹配") assert.Equal(t, firstSlot.SlotID, fetchedExhibition.SlotID, "展位ID应该匹配") } // TestDeleteExhibition 测试删除展品展示记录 func TestDeleteExhibition(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_008") user := createTestUser(t, db, "19900000008") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户008") // 创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err) // 获取第一个展位 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) firstSlot := slots[0] // 创建展品展示记录 now := time.Now().UnixMilli() expireAt := now + config.GalleryRules.GrabSlotDuration*1000 exhibition := &models.Exhibition{ AssetID: 34567, SlotID: firstSlot.SlotID, HostProfileID: firstSlot.HostProfileID, OccupierUID: user.ID, OccupierStarID: star.StarID, StartTime: now, ExpireAt: expireAt, } err = repo.CreateExhibition(exhibition) assert.NoError(t, err) // 验证展品展示记录存在 fetchedExhibition, err := repo.GetExhibitionByAsset(34567) assert.NoError(t, err) assert.NotNil(t, fetchedExhibition) // 删除展品展示记录 err = repo.DeleteExhibition(fetchedExhibition.ID) assert.NoError(t, err, "删除展品展示记录应该成功") // 验证展品展示记录已删除 fetchedExhibition, err = repo.GetExhibitionByAsset(34567) assert.NoError(t, err) assert.Nil(t, fetchedExhibition, "展品展示记录应该已被删除") } // TestDeleteExhibitionByAsset 测试根据资产ID删除展品展示记录 func TestDeleteExhibitionByAsset(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_009") user := createTestUser(t, db, "19900000009") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户009") // 创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err) // 获取第一个展位 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) firstSlot := slots[0] // 创建展品展示记录 now := time.Now().UnixMilli() expireAt := now + config.GalleryRules.GrabSlotDuration*1000 exhibition := &models.Exhibition{ AssetID: 45678, SlotID: firstSlot.SlotID, HostProfileID: firstSlot.HostProfileID, OccupierUID: user.ID, OccupierStarID: star.StarID, StartTime: now, ExpireAt: expireAt, } err = repo.CreateExhibition(exhibition) assert.NoError(t, err) // 根据资产ID删除展品展示记录 err = repo.DeleteExhibitionByAsset(45678) assert.NoError(t, err, "根据资产ID删除展品展示记录应该成功") // 验证展品展示记录已删除 fetchedExhibition, err := repo.GetExhibitionByAsset(45678) assert.NoError(t, err) assert.Nil(t, fetchedExhibition, "展品展示记录应该已被删除") } // TestGetExpiredExhibitions 测试获取过期的展品展示记录 func TestGetExpiredExhibitions(t *testing.T) { db := setupTestDB(t) defer cleanupTestDB(t, db) repo := NewGalleryRepository(db) // 创建测试用户和明星 star := createTestStar(t, db, "test_gallery_010") user := createTestUser(t, db, "19900000010") createTestFanProfile(t, db, user.ID, star.StarID, "测试用户010") // 创建初始展位 err := repo.CreateInitialSlots(user.ID, star.StarID, user.ID*1000000+star.StarID) assert.NoError(t, err) // 获取展位 slots, err := repo.GetSlotsByUser(user.ID, star.StarID) assert.NoError(t, err) // 创建已过期的展品展示记录 now := time.Now().UnixMilli() pastExpireTime := now - 1000*60*60 // 1小时前过期 expiredExhibition := &models.Exhibition{ AssetID: 56789, SlotID: slots[0].SlotID, HostProfileID: slots[0].HostProfileID, OccupierUID: user.ID, OccupierStarID: star.StarID, StartTime: now - 1000*60*60*5, // 5小时前开始 ExpireAt: pastExpireTime, } err = repo.CreateExhibition(expiredExhibition) assert.NoError(t, err) // 创建未过期的展品展示记录 futureExpireTime := now + 1000*60*60 // 1小时后过期 validExhibition := &models.Exhibition{ AssetID: 67890, SlotID: slots[1].SlotID, HostProfileID: slots[1].HostProfileID, OccupierUID: user.ID, OccupierStarID: star.StarID, StartTime: now, ExpireAt: futureExpireTime, } err = repo.CreateExhibition(validExhibition) assert.NoError(t, err) // 获取过期的展品展示记录 expiredExhibitions, err := repo.GetExpiredExhibitions(now) assert.NoError(t, err) assert.Len(t, expiredExhibitions, 1, "应该只有1个过期的展品展示记录") assert.Equal(t, int64(56789), expiredExhibitions[0].AssetID, "过期的展品资产ID应该为56789") }