209 lines
6.7 KiB
Go
209 lines
6.7 KiB
Go
package realname
|
|
|
|
import (
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"hfb_sys/backend/internal/database"
|
|
"hfb_sys/backend/internal/model"
|
|
"hfb_sys/backend/pkg/crypto"
|
|
|
|
"go.uber.org/zap"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func setupRealnameTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
db := database.NewTestDB()
|
|
if err := database.AutoMigrateIfMissing(db,
|
|
&model.User{},
|
|
&model.UserRealname{},
|
|
&model.UserInvitation{},
|
|
); err != nil {
|
|
t.Fatalf("迁移实名测试表失败: %v", err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
func newRealnameTestService(t *testing.T, db *gorm.DB) *Service {
|
|
t.Helper()
|
|
encryptor := &crypto.MockEncryptor{}
|
|
repo := NewRepository(db, encryptor, "test-realname-id-hash-key")
|
|
return NewService(repo, NewMockProvider(encryptor), zap.NewNop())
|
|
}
|
|
|
|
func TestRealnameIdentityMustBeUnique(t *testing.T) {
|
|
db := setupRealnameTestDB(t)
|
|
svc := newRealnameTestService(t, db)
|
|
|
|
user1 := model.User{Phone: "13700000001", Status: "active"}
|
|
user2 := model.User{Phone: "13700000002", Status: "active"}
|
|
if err := db.Create(&user1).Error; err != nil {
|
|
t.Fatalf("创建用户1失败: %v", err)
|
|
}
|
|
if err := db.Create(&user2).Error; err != nil {
|
|
t.Fatalf("创建用户2失败: %v", err)
|
|
}
|
|
|
|
const idNo = "110101199001010011"
|
|
if _, err := svc.Start(t.Context(), user1.ID, "张三", idNo); err != nil {
|
|
t.Fatalf("用户1首次实名失败: %v", err)
|
|
}
|
|
if _, err := svc.Start(t.Context(), user1.ID, "张三", idNo); !errors.Is(err, ErrAlreadyVerified) {
|
|
t.Fatalf("同一用户重复实名 error = %v, want ErrAlreadyVerified", err)
|
|
}
|
|
if _, err := svc.Start(t.Context(), user2.ID, "张三", idNo); !errors.Is(err, ErrIDNoAlreadyUsed) {
|
|
t.Fatalf("同一身份证绑定第二账号 error = %v, want ErrIDNoAlreadyUsed", err)
|
|
}
|
|
|
|
var record model.UserRealname
|
|
if err := db.Where("user_id = ?", user1.ID).First(&record).Error; err != nil {
|
|
t.Fatalf("读取用户1实名记录失败: %v", err)
|
|
}
|
|
if record.IDNoHash == nil || *record.IDNoHash == "" {
|
|
t.Fatal("实名通过后必须写入身份证指纹")
|
|
}
|
|
}
|
|
|
|
func TestRealnameIdentityCheckSupportsLegacyEncryptedRecord(t *testing.T) {
|
|
db := setupRealnameTestDB(t)
|
|
svc := newRealnameTestService(t, db)
|
|
|
|
legacyUser := model.User{Phone: "13700000011", Status: "active"}
|
|
otherUser := model.User{Phone: "13700000012", Status: "active"}
|
|
if err := db.Create(&legacyUser).Error; err != nil {
|
|
t.Fatalf("创建存量用户失败: %v", err)
|
|
}
|
|
if err := db.Create(&otherUser).Error; err != nil {
|
|
t.Fatalf("创建新用户失败: %v", err)
|
|
}
|
|
|
|
const idNo = "11010119900101002X"
|
|
now := time.Now()
|
|
if err := db.Create(&model.UserRealname{
|
|
UserID: legacyUser.ID,
|
|
Provider: "mock",
|
|
Status: StatusVerified,
|
|
MaskedIDNo: maskIDNo(idNo),
|
|
EncryptedIDNo: idNo,
|
|
VerifiedAt: &now,
|
|
}).Error; err != nil {
|
|
t.Fatalf("创建存量实名记录失败: %v", err)
|
|
}
|
|
|
|
if _, err := svc.Start(t.Context(), otherUser.ID, "李四", idNo); !errors.Is(err, ErrIDNoAlreadyUsed) {
|
|
t.Fatalf("存量实名身份证重复绑定 error = %v, want ErrIDNoAlreadyUsed", err)
|
|
}
|
|
|
|
var legacy model.UserRealname
|
|
if err := db.Where("user_id = ?", legacyUser.ID).First(&legacy).Error; err != nil {
|
|
t.Fatalf("读取存量实名记录失败: %v", err)
|
|
}
|
|
if legacy.IDNoHash == nil || *legacy.IDNoHash == "" {
|
|
t.Fatal("命中存量记录后应补写身份证指纹")
|
|
}
|
|
}
|
|
|
|
func TestSaveResultRejectsDuplicateIdentityAtDBLevel(t *testing.T) {
|
|
db := setupRealnameTestDB(t)
|
|
encryptor := &crypto.MockEncryptor{}
|
|
repo := NewRepository(db, encryptor, "test-realname-id-hash-key")
|
|
|
|
user1 := model.User{Phone: "13700000031", Status: "active"}
|
|
user2 := model.User{Phone: "13700000032", Status: "active"}
|
|
if err := db.Create(&user1).Error; err != nil {
|
|
t.Fatalf("创建用户1失败: %v", err)
|
|
}
|
|
if err := db.Create(&user2).Error; err != nil {
|
|
t.Fatalf("创建用户2失败: %v", err)
|
|
}
|
|
const idNo = "110101199001010049"
|
|
now := time.Now()
|
|
result := ProviderResult{
|
|
ProviderOrderNo: "mock_unique",
|
|
Status: StatusVerified,
|
|
MaskedName: "赵*",
|
|
EncryptedName: "赵六",
|
|
MaskedIDNo: maskIDNo(idNo),
|
|
EncryptedIDNo: idNo,
|
|
VerifiedAt: &now,
|
|
}
|
|
hash := FingerprintIDNo(repo.idHashKey, idNo)
|
|
if _, err := repo.SaveResult(t.Context(), user1.ID, "mock", result, hash); err != nil {
|
|
t.Fatalf("用户1保存实名失败: %v", err)
|
|
}
|
|
if _, err := repo.SaveResult(t.Context(), user2.ID, "mock", result, hash); !errors.Is(err, ErrIDNoAlreadyUsed) {
|
|
t.Fatalf("数据库唯一索引未拦截重复身份证: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestRealnameRejectedRecordCanRetryOnSameAccount(t *testing.T) {
|
|
db := setupRealnameTestDB(t)
|
|
svc := newRealnameTestService(t, db)
|
|
|
|
user := model.User{Phone: "13700000025", Status: "active", RealnameStatus: "rejected"}
|
|
if err := db.Create(&user).Error; err != nil {
|
|
t.Fatalf("创建用户失败: %v", err)
|
|
}
|
|
const idNo = "110101199001010022"
|
|
if err := db.Create(&model.UserRealname{
|
|
UserID: user.ID,
|
|
Provider: "mock",
|
|
Status: StatusRejected,
|
|
MaskedIDNo: maskIDNo(idNo),
|
|
EncryptedIDNo: idNo,
|
|
FailReason: "旧记录",
|
|
}).Error; err != nil {
|
|
t.Fatalf("创建驳回记录失败: %v", err)
|
|
}
|
|
|
|
if _, err := svc.Start(t.Context(), user.ID, "孙七", idNo); err != nil {
|
|
t.Fatalf("驳回后重新实名失败: %v", err)
|
|
}
|
|
var count int64
|
|
if err := db.Model(&model.UserRealname{}).Where("user_id = ?", user.ID).Count(&count).Error; err != nil {
|
|
t.Fatalf("统计实名记录失败: %v", err)
|
|
}
|
|
if count != 1 {
|
|
t.Fatalf("实名记录条数 = %d, want 1", count)
|
|
}
|
|
var record model.UserRealname
|
|
if err := db.Where("user_id = ?", user.ID).First(&record).Error; err != nil {
|
|
t.Fatalf("读取实名记录失败: %v", err)
|
|
}
|
|
if record.Status != StatusVerified || record.IDNoHash == nil {
|
|
t.Fatalf("重新实名后状态/指纹 = %s/%v", record.Status, record.IDNoHash)
|
|
}
|
|
}
|
|
|
|
func TestRealnameRejectedRecordDoesNotBlockAnotherAccount(t *testing.T) {
|
|
db := setupRealnameTestDB(t)
|
|
svc := newRealnameTestService(t, db)
|
|
|
|
rejectedUser := model.User{Phone: "13700000021", Status: "active"}
|
|
otherUser := model.User{Phone: "13700000022", Status: "active"}
|
|
if err := db.Create(&rejectedUser).Error; err != nil {
|
|
t.Fatalf("创建用户失败: %v", err)
|
|
}
|
|
if err := db.Create(&otherUser).Error; err != nil {
|
|
t.Fatalf("创建用户失败: %v", err)
|
|
}
|
|
|
|
const idNo = "110101199001010030"
|
|
if err := db.Create(&model.UserRealname{
|
|
UserID: rejectedUser.ID,
|
|
Provider: "mock",
|
|
Status: StatusRejected,
|
|
MaskedIDNo: maskIDNo(idNo),
|
|
EncryptedIDNo: idNo,
|
|
FailReason: "姓名不一致",
|
|
}).Error; err != nil {
|
|
t.Fatalf("创建驳回记录失败: %v", err)
|
|
}
|
|
|
|
if _, err := svc.Start(t.Context(), otherUser.ID, "王五", idNo); err != nil {
|
|
t.Fatalf("驳回记录不应占用身份证,实名失败: %v", err)
|
|
}
|
|
}
|