package service import ( "context" "errors" "testing" "time" "gorm.io/gorm" "github.com/topfans/backend/services/assetService/repository" ) // setupAssetWithPeripheral 准备测试用 asset + peripheral_info // // ★ 复用包内共享 helper(createServiceTestStar / createServiceTestUser / createServiceTestAsset), // // 资源会自动被 cleanupServiceTestDB 清理。 // ★ peripheral_info 用 raw SQL INSERT(避开任何潜在的 GORM 钩子,参考 repository 包风格) // ★ assets.verify_count 通过 UPDATE 设置,因为 helper 的 createServiceTestAsset 默认 0 func setupAssetWithPeripheral(t *testing.T, db *gorm.DB) (assetID, starID int64) { t.Helper() star := createServiceTestStar(t, db, "test_peripheral_verify") user := createServiceTestUser(t, db, "19900099001") asset := createServiceTestAsset(t, db, user.ID, star.StarID, "p-verify") // 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', 'https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/1778876360335.jpg', 'BrandX', 'CompanyY', '0xdeadbeef', 'verifierZ', 1715600000000, 1, 1)`, asset.ID).Error; err != nil { t.Fatalf("Failed to create test peripheral_info: %v", err) } return asset.ID, star.StarID } func TestPeripheralService_GetVerification_Success(t *testing.T) { db := setupServiceTestDB(t) defer cleanupServiceTestDB(t, db) repo := repository.NewPeripheralRepository(db) svc := NewPeripheralService(repo) assetID, _ := setupAssetWithPeripheral(t, db) result, err := svc.GetVerification(context.Background(), assetID) if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.AssetID <= 0 { t.Errorf("expected assetID=%d, got %d", assetID, result.AssetID) } // verify_count 来自 peripheral_info.verify_count(扫码自增后的实时值) // 测试 fixture 未显式设置 verify_count,默认 0,调用 GetVerification 后自增为 1 if result.VerifyCount != 1 { t.Errorf("expected verify_count=1 (默认0 + 自增1), got %d", result.VerifyCount) } if result.Brand != "BrandX" { t.Errorf("expected brand=BrandX, got %s", result.Brand) } if result.Company != "CompanyY" { t.Errorf("expected company=CompanyY, got %s", result.Company) } if result.Hash != "0xdeadbeef" { t.Errorf("expected hash=0xdeadbeef, got %s", result.Hash) } if result.Verifier != "verifierZ" { t.Errorf("expected verifier=verifierZ, got %s", result.Verifier) } if result.Image != "https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/1778876360335.jpg" { t.Errorf("expected image=https://top-fans-test.oss-cn-shanghai.aliyuncs.com/asset/1/87/1778876360335.jpg, got %s", result.Image) } // first_verified_at=1715600000000 ms → 1715600000 s if result.VerifiedAt != 1715600000 { t.Errorf("expected verified_at=1715600000 (seconds), got %d", result.VerifiedAt) } } func TestPeripheralService_GetVerification_AssetNotFound(t *testing.T) { db := setupServiceTestDB(t) defer cleanupServiceTestDB(t, db) repo := repository.NewPeripheralRepository(db) svc := NewPeripheralService(repo) _, err := svc.GetVerification(context.Background(), 999999999999) var bizErr *BizError if !errors.As(err, &bizErr) { t.Fatalf("expected BizError, got %T: %v", err, err) } if bizErr.Code != BizCodeAssetNotFound { t.Errorf("expected code=%d, got %d", BizCodeAssetNotFound, bizErr.Code) } } // TestPeripheralService_GetVerification_PeripheralInfoMissing 验证"只有 asset 没有 peripheral_info"场景 // // 业务语义:周边验真接口要求必须存在 peripheral_info,否则视为"非周边"返回 50003。 func TestPeripheralService_GetVerification_PeripheralInfoMissing(t *testing.T) { db := setupServiceTestDB(t) defer cleanupServiceTestDB(t, db) repo := repository.NewPeripheralRepository(db) svc := NewPeripheralService(repo) // 只有 asset,没有 peripheral_info star := createServiceTestStar(t, db, "test_peripheral_verify_no_info") user := createServiceTestUser(t, db, "19900099002") asset := createServiceTestAsset(t, db, user.ID, star.StarID, "p-verify-noinfo") _, err := svc.GetVerification(context.Background(), asset.ID) var bizErr *BizError if !errors.As(err, &bizErr) { t.Fatalf("expected BizError, got %T: %v", err, err) } if bizErr.Code != BizCodeAssetNotFound { t.Errorf("expected code=%d, got %d", BizCodeAssetNotFound, bizErr.Code) } } // TestPeripheralService_MintFromPeripheral_Success 验证 happy path: // // asset + peripheral_info 存在 → ExistsRegistry=false → CountRecentMint<10 → INSERT → 返 MintResult func TestPeripheralService_MintFromPeripheral_Success(t *testing.T) { db := setupServiceTestDB(t) defer cleanupServiceTestDB(t, db) repo := repository.NewPeripheralRepository(db) svc := NewPeripheralService(repo) _, _ = setupAssetWithPeripheral(t, db) user := createServiceTestUser(t, db, "19900099901") ownerUID := user.ID // 清理 ownerUID 旧记录(避免残留影响) defer db.Exec("DELETE FROM asset_registry WHERE owner_uid = ?", ownerUID) result, err := svc.MintFromPeripheral(context.Background(), ownerUID, "PERI-2026-001") if err != nil { t.Fatalf("unexpected error: %v", err) } if result == nil { t.Fatal("expected result, got nil") } if result.InstanceID <= 0 { t.Errorf("expected instance_id > 0, got %d", result.InstanceID) } if result.MintedAt <= 0 { t.Errorf("expected minted_at > 0, got %d", result.MintedAt) } if result.AssetID <= 0 { t.Errorf("expected new asset_id > 0, got %d", result.AssetID) } if result.CoverImage == "" { t.Error("expected cover_image populated") } } // TestPeripheralService_MintFromPeripheral_AlreadyAdded 验证查重: // // 同一 owner_uid + asset_id + asset_type=peripheral 已存在记录 → 返 50004 func TestPeripheralService_MintFromPeripheral_AlreadyAdded(t *testing.T) { db := setupServiceTestDB(t) defer cleanupServiceTestDB(t, db) repo := repository.NewPeripheralRepository(db) svc := NewPeripheralService(repo) assetID, _ := setupAssetWithPeripheral(t, db) user := createServiceTestUser(t, db, "19900099002") ownerUID := user.ID // 先插一条,再调 mint if err := 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)`, ownerUID, assetID).Error; err != nil { t.Fatalf("Failed to seed asset_registry: %v", err) } defer db.Exec("DELETE FROM asset_registry WHERE owner_uid = ?", ownerUID) _, err := svc.MintFromPeripheral(context.Background(), ownerUID, "PERI-2026-001") var bizErr *BizError if !errors.As(err, &bizErr) { t.Fatalf("expected BizError, got %T: %v", err, err) } if bizErr.Code != BizCodeAlreadyAdded { t.Errorf("expected code=%d, got %d", BizCodeAlreadyAdded, bizErr.Code) } } // TestPeripheralService_MintFromPeripheral_RateLimited 验证限频: // // 同一 owner_uid 在 24h 内已有 10 条 peripheral mint → 返 50012 // // 注意:asset_registry 的 UNIQUE 约束是 (owner_uid, star_id, asset_type, asset_id), // // 故 10 条记录需 10 个不同的 asset_id。这里使用固定大数段 90000100~90000109 作为 fixture asset_id。 func TestPeripheralService_MintFromPeripheral_RateLimited(t *testing.T) { db := setupServiceTestDB(t) defer cleanupServiceTestDB(t, db) repo := repository.NewPeripheralRepository(db) svc := NewPeripheralService(repo) _, _ = setupAssetWithPeripheral(t, db) user := createServiceTestUser(t, db, "19900099003") ownerUID := user.ID // 插 10 条最近 mint(限频阈值);star_id=1 即可,asset_id 用 10 个不同值绕开 UNIQUE 约束 now := time.Now().UnixMilli() cleanupAssets := make([]int64, 0, 10) for i := 0; i < 10; i++ { fakeAssetID := int64(90000100 + i) cleanupAssets = append(cleanupAssets, fakeAssetID) if err := db.Exec(`INSERT INTO asset_registry (owner_uid, asset_id, star_id, asset_type, status, created_at, updated_at) VALUES (?, ?, 1, 'peripheral', 1, ?, ?)`, ownerUID, fakeAssetID, now, now).Error; err != nil { t.Fatalf("Failed to seed rate-limit asset_registry row %d: %v", i, err) } } defer db.Exec("DELETE FROM asset_registry WHERE owner_uid = ?", ownerUID) defer db.Exec("DELETE FROM assets WHERE id IN ?", cleanupAssets) _, err := svc.MintFromPeripheral(context.Background(), ownerUID, "PERI-2026-001") var bizErr *BizError if !errors.As(err, &bizErr) { t.Fatalf("expected BizError, got %T: %v", err, err) } if bizErr.Code != BizCodeRateLimited { t.Errorf("expected code=%d, got %d", BizCodeRateLimited, bizErr.Code) } } // TestPeripheralService_MintFromPeripheral_AssetNotFound 验证物品不存在: // // asset_id 不存在 → 返 50003(不区分"非周边"与"已下架") func TestPeripheralService_MintFromPeripheral_AssetNotFound(t *testing.T) { db := setupServiceTestDB(t) defer cleanupServiceTestDB(t, db) repo := repository.NewPeripheralRepository(db) svc := NewPeripheralService(repo) _, err := svc.MintFromPeripheral(context.Background(), 999, "999999999999") var bizErr *BizError if !errors.As(err, &bizErr) { t.Fatalf("expected BizError, got %T: %v", err, err) } if bizErr.Code != BizCodeAssetNotFound { t.Errorf("expected code=%d, got %d", BizCodeAssetNotFound, bizErr.Code) } } // TestPeripheralService_MintFromPeripheral_PeripheralInfoMissing 验证"有 asset 无 peripheral_info": // // asset 存在但 peripheral_info 缺失 → 返 50003(与 GetVerification 保持一致,不泄漏"非周边"细节) func TestPeripheralService_MintFromPeripheral_PeripheralInfoMissing(t *testing.T) { db := setupServiceTestDB(t) defer cleanupServiceTestDB(t, db) repo := repository.NewPeripheralRepository(db) svc := NewPeripheralService(repo) // 只有 asset,没有 peripheral_info(复用 setupAssetWithPeripheral 风格) star := createServiceTestStar(t, db, "test_peripheral_mint_no_info") user := createServiceTestUser(t, db, "19900099003") _ = createServiceTestAsset(t, db, user.ID, star.StarID, "p-mint-noinfo") _, err := svc.MintFromPeripheral(context.Background(), 999, "PERI-2026-001") var bizErr *BizError if !errors.As(err, &bizErr) { t.Fatalf("expected BizError, got %T: %v", err, err) } if bizErr.Code != BizCodeAssetNotFound { t.Errorf("expected code=%d, got %d", BizCodeAssetNotFound, bizErr.Code) } }