topfans/backend/services/assetService/repository/peripheral_repo_test.go
2026-07-17 16:53:57 +08:00

371 lines
13 KiB
Go

package repository
import (
"context"
"errors"
"testing"
"time"
"github.com/topfans/backend/pkg/models"
"gorm.io/gorm"
)
// setupPeripheralTestData helper: 准备测试用 asset + 可选 peripheral_info
// ★ 使用包内共享 helper(createTestStar / createTestUser / createTestAsset),
//
// 资源会自动被 cleanupTestDB 通过 mobile LIKE '199%' / identity_id LIKE 'test_peripheral_%' 清理
// (star identity_id "test_peripheral_asset" 匹配 cleanupTestDB 的 test_asset_% 规则)
func setupPeripheralTestData(t *testing.T, db *gorm.DB, withPeripheralInfo bool) int64 {
t.Helper()
star := createTestStar(t, db, "test_peripheral_asset")
user := createTestUser(t, db, "19900009001")
asset := createTestAsset(t, db, user.ID, star.StarID, "test_peripheral_asset")
if withPeripheralInfo {
// peripheral_info 用 raw SQL INSERT(避开任何潜在的 GORM 钩子)
if err := db.Exec(`INSERT INTO peripheral_info (asset_id, star_id, user_id, code, image, brand, company, hash, verifier, first_verified_at, created_at, updated_at)
VALUES (?, 87, 0, 'PERI-2026-001', '', 'TopFans', '上海文化', '0xabc', '官方', 1715600000000, 1, 1)`,
asset.ID).Error; err != nil {
t.Fatalf("Failed to create test peripheral_info: %v", err)
}
}
return asset.ID
}
func TestPeripheralRepo_GetAssetForVerification_Exists(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
assetID := setupPeripheralTestData(t, db, false)
got, err := repo.GetAssetForVerification(context.Background(), assetID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got == nil {
t.Fatal("expected asset, got nil")
}
if got.ID != assetID {
t.Errorf("expected id=%d, got %d", assetID, got.ID)
}
}
func TestPeripheralRepo_GetAssetForVerification_NotFound(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
got, err := repo.GetAssetForVerification(context.Background(), 999999999999)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != nil {
t.Errorf("expected nil, got %+v", got)
}
}
func TestPeripheralRepo_GetPeripheralInfo_Exists(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
assetID := setupPeripheralTestData(t, db, true)
got, err := repo.GetPeripheralInfo(context.Background(), assetID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got == nil {
t.Fatal("expected PeripheralInfo, got nil")
}
if got.Brand != "TopFans" {
t.Errorf("expected brand=TopFans, got %s", got.Brand)
}
}
func TestPeripheralRepo_GetPeripheralInfo_NotFound(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
got, err := repo.GetPeripheralInfo(context.Background(), 999999999999)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != nil {
t.Errorf("expected nil, got %+v", got)
}
}
// TestPeripheralRepo_GetPeripheralInfoByID_Exists 验证按 peripheral_info 主键 id 查到记录
// ★ 回归测试:2026-07-17 by-hash 接口 50003,根因是 GetPeripheralInfoByHash 错把 peripheral_info.id
//
// 传给 GetPeripheralInfo(asset_id 查),查不到。新增按 id 查的方法。
func TestPeripheralRepo_GetPeripheralInfoByID_Exists(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
setupPeripheralTestData(t, db, true) // 创建 peripheral_info
// 直接查 peripheral_info.id(setupPeripheralTestData 没返回这个值)
var peripheralID int64
if err := db.Raw(`SELECT id FROM peripheral_info WHERE code = 'PERI-2026-001' LIMIT 1`).Scan(&peripheralID).Error; err != nil {
t.Fatalf("query peripheral_info.id failed: %v", err)
}
if peripheralID == 0 {
t.Fatal("expected peripheral_info.id > 0, got 0")
}
got, err := repo.GetPeripheralInfoByID(context.Background(), peripheralID)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got == nil {
t.Fatal("expected PeripheralInfo, got nil")
}
if got.ID != peripheralID {
t.Errorf("expected id=%d, got %d", peripheralID, got.ID)
}
if got.Brand != "TopFans" {
t.Errorf("expected brand=TopFans, got %s", got.Brand)
}
}
func TestPeripheralRepo_GetPeripheralInfoByID_NotFound(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
got, err := repo.GetPeripheralInfoByID(context.Background(), 999999999999)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != nil {
t.Errorf("expected nil, got %+v", got)
}
}
func TestPeripheralRepo_GetPeripheralInfoByID_InvalidID(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
_, err := repo.GetPeripheralInfoByID(context.Background(), 0)
if err == nil {
t.Fatal("expected error for id=0, got nil")
}
}
// TestPeripheralRepo_ExistsRegistry_True 验证 ExistsRegistry 命中已有记录
func TestPeripheralRepo_ExistsRegistry_True(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
assetID := setupPeripheralTestData(t, db, false)
// 写入 1 条 asset_registry 记录(owner_uid=100)
db.Exec(`INSERT INTO asset_registry (owner_uid, asset_id, star_id, asset_type, status, created_at, updated_at)
VALUES (?, ?, 1, 'peripheral', 1, 1, 1)`, 100, assetID)
defer db.Exec("DELETE FROM asset_registry WHERE owner_uid = ? AND asset_id = ?", 100, assetID)
exists, err := repo.ExistsRegistry(context.Background(), 100, assetID, "peripheral")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if !exists {
t.Error("expected exists=true, got false")
}
}
// TestPeripheralRepo_ExistsRegistry_False 验证 ExistsRegistry 未命中时返 false
func TestPeripheralRepo_ExistsRegistry_False(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
assetID := setupPeripheralTestData(t, db, false)
// 不写入任何 registry 记录,查询应返 false
exists, err := repo.ExistsRegistry(context.Background(), 100, assetID, "peripheral")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if exists {
t.Error("expected exists=false, got true")
}
}
// TestPeripheralRepo_CountRecentMint 验证 CountRecentMint 仅统计 since 时间内的记录
func TestPeripheralRepo_CountRecentMint(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
star := createTestStar(t, db, "test_peripheral_count")
user := createTestUser(t, db, "19900009002")
// ★ 4 个 asset_id(3 条最近 + 1 条 25h 前),因 uk_registry_asset_type_id UNIQUE(asset_type, asset_id)
recentAsset1 := createTestAsset(t, db, user.ID, star.StarID, "test_peripheral_recent1")
recentAsset2 := createTestAsset(t, db, user.ID, star.StarID, "test_peripheral_recent2")
recentAsset3 := createTestAsset(t, db, user.ID, star.StarID, "test_peripheral_recent3")
oldAsset := createTestAsset(t, db, user.ID, star.StarID, "test_peripheral_old")
ownerUID := int64(101)
// 清理 owner_uid=101 的旧记录(避免别的测试残留影响)
db.Exec("DELETE FROM asset_registry WHERE owner_uid = ?", ownerUID)
// 3 条最近,1 条 25h 前
now := time.Now().UnixMilli()
db.Exec(`INSERT INTO asset_registry (owner_uid, asset_id, star_id, asset_type, status, created_at, updated_at)
VALUES (?, ?, ?, 'peripheral', 1, ?, ?), (?, ?, ?, 'peripheral', 1, ?, ?), (?, ?, ?, 'peripheral', 1, ?, ?)`,
ownerUID, recentAsset1.ID, star.StarID, now, now,
ownerUID, recentAsset2.ID, star.StarID, now, now,
ownerUID, recentAsset3.ID, star.StarID, now, now)
db.Exec(`INSERT INTO asset_registry (owner_uid, asset_id, star_id, asset_type, status, created_at, updated_at)
VALUES (?, ?, ?, 'peripheral', 1, ?, ?)`,
ownerUID, oldAsset.ID, star.StarID, now-25*3600*1000, now-25*3600*1000)
defer db.Exec("DELETE FROM asset_registry WHERE owner_uid = ?", ownerUID)
count, err := repo.CountRecentMint(context.Background(), ownerUID, "peripheral", 24*time.Hour)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if count != 3 {
t.Errorf("expected count=3, got %d", count)
}
}
// TestPeripheralRepo_InsertPeripheralRegistry verifies a peripheral registry row is inserted and returns generated fields.
func TestPeripheralRepo_InsertPeripheralRegistry(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
assetID := setupPeripheralTestData(t, db, false)
defer db.Where("asset_id = ?", assetID).Delete(&models.AssetRegistry{})
var asset models.Asset
if err := db.Select("id", "star_id").First(&asset, assetID).Error; err != nil {
t.Fatalf("query test asset failed: %v", err)
}
owner := createTestUser(t, db, "19900009003")
reg := &models.AssetRegistry{
OwnerUID: owner.ID,
AssetID: assetID,
StarID: asset.StarID,
AssetType: "peripheral",
Status: models.AssetRegistryStatusActive,
}
newID, createdAtMs, err := repo.InsertPeripheralRegistry(context.Background(), reg)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if newID <= 0 {
t.Errorf("expected newID > 0, got %d", newID)
}
if createdAtMs <= 0 {
t.Errorf("expected createdAtMs > 0, got %d", createdAtMs)
}
var got models.AssetRegistry
if err := db.First(&got, newID).Error; err != nil {
t.Fatalf("verify insert failed: %v", err)
}
if got.AssetType != "peripheral" {
t.Errorf("expected asset_type=peripheral, got %s", got.AssetType)
}
if got.AssetID != assetID {
t.Errorf("expected asset_id=%d, got %d", assetID, got.AssetID)
}
if got.OwnerUID != owner.ID {
t.Errorf("expected owner_uid=%d, got %d", owner.ID, got.OwnerUID)
}
}
// TestPeripheralRepo_RefreshVerifyCount verifies verify_count is refreshed from peripheral registry rows only.
func TestPeripheralRepo_RefreshVerifyCount(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
star := createTestStar(t, db, "test_asset_peripheral_refresh")
assetOwner := createTestUser(t, db, "19900009004")
asset := createTestAsset(t, db, assetOwner.ID, star.StarID, "test_asset_peripheral_refresh")
otherAsset := createTestAsset(t, db, assetOwner.ID, star.StarID, "test_asset_peripheral_refresh_other")
defer db.Where("asset_id IN ?", []int64{asset.ID, otherAsset.ID}).Delete(&models.AssetRegistry{})
registryOwner := createTestUser(t, db, "19900009005")
createRegistry := func(assetID int64, assetType string) {
t.Helper()
reg := &models.AssetRegistry{
OwnerUID: registryOwner.ID,
AssetID: assetID,
StarID: star.StarID,
AssetType: assetType,
Status: models.AssetRegistryStatusActive,
}
if err := db.Create(reg).Error; err != nil {
t.Fatalf("create test registry failed: %v", err)
}
}
createRegistry(asset.ID, "peripheral")
createRegistry(asset.ID, models.AssetTypeRegular)
createRegistry(otherAsset.ID, "peripheral")
if err := db.Table("assets").Where("id = ?", asset.ID).Update("verify_count", 99).Error; err != nil {
t.Fatalf("set stale verify_count failed: %v", err)
}
if err := repo.RefreshVerifyCount(context.Background(), asset.ID); err != nil {
t.Fatalf("unexpected error: %v", err)
}
var verifyCount int64
if err := db.Table("assets").Select("verify_count").Where("id = ?", asset.ID).Scan(&verifyCount).Error; err != nil {
t.Fatalf("query verify_count failed: %v", err)
}
if verifyCount != 1 {
t.Errorf("expected verify_count=1, got %d", verifyCount)
}
}
// TestPeripheralRepo_InsertPeripheralRegistry_Duplicate 验证并发插入触发 PG 唯一约束冲突时
//
// 返 sentinel ErrDuplicateRegistry(而非通用 PG 错误),
// 让 service 层能 errors.Is 识别并转 BizCodeAlreadyAdded (50004)。
func TestPeripheralRepo_InsertPeripheralRegistry_Duplicate(t *testing.T) {
db := setupTestDB(t)
defer cleanupTestDB(t, db)
repo := NewPeripheralRepository(db)
assetID := setupPeripheralTestData(t, db, false)
defer db.Where("asset_id = ?", assetID).Delete(&models.AssetRegistry{})
var asset models.Asset
if err := db.Select("id", "star_id").First(&asset, assetID).Error; err != nil {
t.Fatalf("query test asset failed: %v", err)
}
owner := createTestUser(t, db, "19900009006")
mkReg := func() *models.AssetRegistry {
return &models.AssetRegistry{
OwnerUID: owner.ID,
AssetID: assetID,
StarID: asset.StarID,
AssetType: "peripheral",
Status: models.AssetRegistryStatusActive,
}
}
// 第一次 INSERT 应成功
if _, _, err := repo.InsertPeripheralRegistry(context.Background(), mkReg()); err != nil {
t.Fatalf("first insert should succeed, got: %v", err)
}
// 第二次 INSERT 同 (owner_uid, star_id, asset_type, asset_id) 应触发 PG 23505
_, _, err := repo.InsertPeripheralRegistry(context.Background(), mkReg())
if err == nil {
t.Fatal("expected error on duplicate insert, got nil")
}
if !errors.Is(err, ErrDuplicateRegistry) {
t.Errorf("expected ErrDuplicateRegistry, got %v", err)
}
}