312 lines
11 KiB
Go
312 lines
11 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_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)
|
|
}
|
|
}
|