2025-04-03 07:32:14 +00:00
|
|
|
|
package repository
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
|
2025-04-04 07:39:49 +00:00
|
|
|
|
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/kyc"
|
2025-04-03 07:32:14 +00:00
|
|
|
|
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
|
|
|
|
|
mgo "code.30cm.net/digimon/library-go/mongo"
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"github.com/alicebob/miniredis/v2"
|
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/cache"
|
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/mon"
|
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func SetupTestKYCRepository(db string) (repository.KYCRepository, func(), error) {
|
|
|
|
|
h, p, tearDown, err := startMongoContainer()
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, err
|
|
|
|
|
}
|
|
|
|
|
s, _ := miniredis.Run()
|
|
|
|
|
|
|
|
|
|
conf := &mgo.Conf{
|
|
|
|
|
Schema: Schema,
|
|
|
|
|
Host: fmt.Sprintf("%s:%s", h, p),
|
|
|
|
|
Database: db,
|
|
|
|
|
MaxStaleness: 300,
|
|
|
|
|
MaxPoolSize: 100,
|
|
|
|
|
MinPoolSize: 100,
|
|
|
|
|
MaxConnIdleTime: 300,
|
|
|
|
|
Compressors: []string{},
|
|
|
|
|
EnableStandardReadWriteSplitMode: false,
|
|
|
|
|
ConnectTimeoutMs: 3000,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cacheConf := cache.CacheConf{
|
|
|
|
|
cache.NodeConf{
|
|
|
|
|
RedisConf: redis.RedisConf{
|
|
|
|
|
Host: s.Addr(),
|
|
|
|
|
Type: redis.NodeType,
|
|
|
|
|
},
|
|
|
|
|
Weight: 100,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cacheOpts := []cache.Option{
|
|
|
|
|
cache.WithExpiry(1000 * time.Microsecond),
|
|
|
|
|
cache.WithNotFoundExpiry(1000 * time.Microsecond),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
param := KYCRepositoryParam{
|
|
|
|
|
Conf: conf,
|
|
|
|
|
CacheConf: cacheConf,
|
|
|
|
|
CacheOpts: cacheOpts,
|
|
|
|
|
DBOpts: []mon.Option{
|
|
|
|
|
mgo.SetCustomDecimalType(),
|
|
|
|
|
mgo.InitMongoOptions(*conf),
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repo := NewKYCRepository(param)
|
|
|
|
|
//_, _ = repo.Index20250317001UP(context.Background())
|
|
|
|
|
|
|
|
|
|
return repo, tearDown, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestInsertKYC(t *testing.T) {
|
|
|
|
|
model, tearDown, err := SetupTestKYCRepository("testDB")
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
defer tearDown()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// 建立多筆 KYC 資料
|
|
|
|
|
items := []entity.KYC{
|
|
|
|
|
{
|
|
|
|
|
UID: "user-a",
|
|
|
|
|
CountryRegion: "TW",
|
|
|
|
|
Name: "王小明",
|
|
|
|
|
Identification: "A123456789",
|
|
|
|
|
IdentificationType: "ID_CARD",
|
|
|
|
|
Address: "台北市信義區",
|
|
|
|
|
PostalCode: "110",
|
|
|
|
|
Status: "PENDING",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
UID: "user-b",
|
|
|
|
|
CountryRegion: "US",
|
|
|
|
|
Name: "John Smith",
|
|
|
|
|
Identification: "P987654321",
|
|
|
|
|
IdentificationType: "PASSPORT",
|
|
|
|
|
Address: "123 Main St, NY",
|
|
|
|
|
PostalCode: "10001",
|
|
|
|
|
Status: "PENDING",
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 插入資料
|
|
|
|
|
for i := range items {
|
|
|
|
|
err := model.Create(ctx, &items[i])
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.False(t, items[i].ID.IsZero(), "ID 應自動產生")
|
|
|
|
|
assert.NotZero(t, items[i].CreatedAt)
|
|
|
|
|
assert.NotZero(t, items[i].UpdatedAt)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 驗證插入資料是否正確
|
|
|
|
|
for _, item := range items {
|
|
|
|
|
kyc, err := model.FindByID(ctx, item.ID.Hex())
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, item.UID, kyc.UID)
|
|
|
|
|
assert.Equal(t, item.Name, kyc.Name)
|
|
|
|
|
assert.Equal(t, item.CountryRegion, kyc.CountryRegion)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestFindLatestByUID(t *testing.T) {
|
|
|
|
|
model, tearDown, err := SetupTestKYCRepository("testDB")
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
defer tearDown()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
now := time.Now().UnixNano()
|
|
|
|
|
|
|
|
|
|
// 測試資料準備
|
|
|
|
|
testData := []entity.KYC{
|
|
|
|
|
{
|
|
|
|
|
UID: "user-multi",
|
|
|
|
|
Name: "早期資料",
|
|
|
|
|
Status: "PENDING",
|
|
|
|
|
CreatedAt: now - 1000,
|
|
|
|
|
UpdatedAt: now - 1000,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
UID: "user-multi",
|
|
|
|
|
Name: "最新資料",
|
|
|
|
|
Status: "APPROVED",
|
|
|
|
|
CreatedAt: now + 1000,
|
|
|
|
|
UpdatedAt: now + 1000,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
UID: "user-single",
|
|
|
|
|
Name: "唯一資料",
|
|
|
|
|
Status: "PENDING",
|
|
|
|
|
CreatedAt: now,
|
|
|
|
|
UpdatedAt: now,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := range testData {
|
|
|
|
|
err := model.Create(ctx, &testData[i])
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// table-driven 測試表
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
uid string
|
|
|
|
|
expectNil bool
|
|
|
|
|
expect string // 預期的 Name
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "多筆資料,取最新",
|
|
|
|
|
uid: "user-multi",
|
|
|
|
|
expectNil: false,
|
|
|
|
|
expect: "最新資料",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "單筆資料",
|
|
|
|
|
uid: "user-single",
|
|
|
|
|
expectNil: false,
|
|
|
|
|
expect: "唯一資料",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "查無資料",
|
|
|
|
|
uid: "user-none",
|
|
|
|
|
expectNil: true,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
result, err := model.FindLatestByUID(ctx, tt.uid)
|
|
|
|
|
if tt.expectNil {
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
assert.Nil(t, result)
|
|
|
|
|
} else {
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.NotNil(t, result)
|
|
|
|
|
assert.Equal(t, tt.expect, result.Name)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestKYCRepository_List(t *testing.T) {
|
|
|
|
|
model, tearDown, err := SetupTestKYCRepository("testDB")
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
defer tearDown()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
now := time.Now().UnixNano()
|
|
|
|
|
|
|
|
|
|
seedData := []entity.KYC{
|
|
|
|
|
{
|
|
|
|
|
UID: "user-1",
|
|
|
|
|
CountryRegion: "TW",
|
|
|
|
|
Status: "PENDING",
|
|
|
|
|
Name: "Alice",
|
|
|
|
|
CreatedAt: now,
|
|
|
|
|
UpdatedAt: now,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
UID: "user-1",
|
|
|
|
|
CountryRegion: "JP",
|
|
|
|
|
Status: "APPROVED",
|
|
|
|
|
Name: "Bob",
|
|
|
|
|
CreatedAt: now + 1,
|
|
|
|
|
UpdatedAt: now + 1,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
UID: "user-2",
|
|
|
|
|
CountryRegion: "US",
|
|
|
|
|
Status: "REJECTED",
|
|
|
|
|
Name: "Charlie",
|
|
|
|
|
CreatedAt: now + 2,
|
|
|
|
|
UpdatedAt: now + 2,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for i := range seedData {
|
|
|
|
|
err := model.Create(ctx, &seedData[i])
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
params repository.KYCQueryParams
|
|
|
|
|
expectedCount int
|
|
|
|
|
expectedTotal int64
|
|
|
|
|
expectedFirst string
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "Query by UID",
|
|
|
|
|
params: repository.KYCQueryParams{
|
|
|
|
|
UID: ptr("user-1"),
|
|
|
|
|
PageSize: 10,
|
|
|
|
|
PageIndex: 1,
|
|
|
|
|
},
|
|
|
|
|
expectedCount: 2,
|
|
|
|
|
expectedTotal: 2,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Query by CountryRegion",
|
|
|
|
|
params: repository.KYCQueryParams{
|
|
|
|
|
Country: ptr("JP"),
|
|
|
|
|
PageSize: 10,
|
|
|
|
|
PageIndex: 1,
|
|
|
|
|
},
|
|
|
|
|
expectedCount: 1,
|
|
|
|
|
expectedTotal: 1,
|
|
|
|
|
expectedFirst: "Bob",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Query by Status",
|
|
|
|
|
params: repository.KYCQueryParams{
|
|
|
|
|
Status: ptr("REJECTED"),
|
|
|
|
|
PageSize: 10,
|
|
|
|
|
PageIndex: 1,
|
|
|
|
|
},
|
|
|
|
|
expectedCount: 1,
|
|
|
|
|
expectedTotal: 1,
|
|
|
|
|
expectedFirst: "Charlie",
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "Pagination test",
|
|
|
|
|
params: repository.KYCQueryParams{
|
|
|
|
|
PageSize: 1,
|
|
|
|
|
PageIndex: 1,
|
|
|
|
|
},
|
|
|
|
|
expectedCount: 1,
|
|
|
|
|
expectedTotal: 3,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "No match",
|
|
|
|
|
params: repository.KYCQueryParams{
|
|
|
|
|
UID: ptr("non-existent"),
|
|
|
|
|
PageSize: 10,
|
|
|
|
|
PageIndex: 1,
|
|
|
|
|
},
|
|
|
|
|
expectedCount: 0,
|
|
|
|
|
expectedTotal: 0,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
list, total, err := model.List(ctx, tt.params)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, tt.expectedCount, len(list))
|
|
|
|
|
assert.Equal(t, tt.expectedTotal, total)
|
|
|
|
|
if tt.expectedFirst != "" && len(list) > 0 {
|
|
|
|
|
assert.Equal(t, tt.expectedFirst, list[0].Name)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestKYCRepository_UpdateStatus(t *testing.T) {
|
|
|
|
|
model, tearDown, err := SetupTestKYCRepository("testDB")
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
defer tearDown()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
|
|
// 建立一筆 KYC 資料
|
2025-04-04 07:39:49 +00:00
|
|
|
|
k := &entity.KYC{
|
2025-04-03 07:32:14 +00:00
|
|
|
|
UID: "user-status",
|
|
|
|
|
Name: "測試用戶",
|
2025-04-04 07:39:49 +00:00
|
|
|
|
Status: kyc.StatusPending,
|
2025-04-03 07:32:14 +00:00
|
|
|
|
CreatedAt: time.Now().UnixNano(),
|
|
|
|
|
UpdatedAt: time.Now().UnixNano(),
|
|
|
|
|
}
|
2025-04-04 07:39:49 +00:00
|
|
|
|
err = model.Create(ctx, k)
|
2025-04-03 07:32:14 +00:00
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
id string
|
2025-04-04 07:39:49 +00:00
|
|
|
|
newStatus kyc.Status
|
2025-04-03 07:32:14 +00:00
|
|
|
|
reason string
|
|
|
|
|
expectErr bool
|
2025-04-04 07:39:49 +00:00
|
|
|
|
expectStatus kyc.Status
|
2025-04-03 07:32:14 +00:00
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "成功更新為 REJECTED",
|
2025-04-04 07:39:49 +00:00
|
|
|
|
id: k.ID.Hex(),
|
|
|
|
|
newStatus: kyc.StatusREJECTED,
|
2025-04-03 07:32:14 +00:00
|
|
|
|
reason: "文件不符",
|
|
|
|
|
expectErr: false,
|
2025-04-04 07:39:49 +00:00
|
|
|
|
expectStatus: kyc.StatusREJECTED,
|
2025-04-03 07:32:14 +00:00
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "失敗 - 無效 ID 格式",
|
|
|
|
|
id: "not-a-valid-id",
|
2025-04-04 07:39:49 +00:00
|
|
|
|
newStatus: kyc.StatusAPPROVED,
|
2025-04-03 07:32:14 +00:00
|
|
|
|
reason: "",
|
|
|
|
|
expectErr: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "失敗 - 找不到資料",
|
|
|
|
|
id: primitive.NewObjectID().Hex(), // 合法但不存在
|
2025-04-04 07:39:49 +00:00
|
|
|
|
newStatus: kyc.StatusAPPROVED,
|
2025-04-03 07:32:14 +00:00
|
|
|
|
reason: "",
|
|
|
|
|
expectErr: false, // Mongo 不會報錯,但更新筆數是 0,後面可以補強這邊驗證
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2025-04-04 07:39:49 +00:00
|
|
|
|
err := model.UpdateStatus(ctx, tt.id, tt.newStatus.ToString(), tt.reason)
|
2025-04-03 07:32:14 +00:00
|
|
|
|
if tt.expectErr {
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
} else {
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// 如果是第一個 case,檢查資料是否真的被更新
|
|
|
|
|
if tt.expectStatus != "" {
|
|
|
|
|
result, err := model.FindByID(ctx, tt.id)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, tt.expectStatus, result.Status)
|
|
|
|
|
assert.Equal(t, tt.reason, result.RejectReason)
|
|
|
|
|
assert.NotZero(t, result.UpdatedAt)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestKYCRepository_UpdateKYCInfo(t *testing.T) {
|
|
|
|
|
model, tearDown, err := SetupTestKYCRepository("testDB")
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
defer tearDown()
|
|
|
|
|
|
|
|
|
|
ctx := context.Background()
|
|
|
|
|
now := time.Now().UnixNano()
|
|
|
|
|
|
|
|
|
|
// 建立兩筆資料:一筆 PENDING、一筆 APPROVED
|
|
|
|
|
pendingKYC := &entity.KYC{
|
|
|
|
|
UID: "user-pending",
|
|
|
|
|
Name: "原名",
|
|
|
|
|
Address: "原地址",
|
|
|
|
|
Status: "PENDING",
|
|
|
|
|
CreatedAt: now,
|
|
|
|
|
UpdatedAt: now,
|
|
|
|
|
}
|
|
|
|
|
err = model.Create(ctx, pendingKYC)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
approvedKYC := &entity.KYC{
|
|
|
|
|
UID: "user-approved",
|
|
|
|
|
Name: "原名",
|
|
|
|
|
Address: "原地址",
|
|
|
|
|
Status: "APPROVED",
|
|
|
|
|
CreatedAt: now,
|
|
|
|
|
UpdatedAt: now,
|
|
|
|
|
}
|
|
|
|
|
err = model.Create(ctx, approvedKYC)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
name := "新名字"
|
|
|
|
|
address := "新地址"
|
|
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
|
name string
|
|
|
|
|
id string
|
|
|
|
|
update *repository.KYCUpdateParams
|
|
|
|
|
expectErr bool
|
|
|
|
|
expectApply bool // 預期更新是否應成功套用
|
|
|
|
|
}{
|
|
|
|
|
{
|
|
|
|
|
name: "成功更新 PENDING 狀態資料",
|
|
|
|
|
id: pendingKYC.ID.Hex(),
|
|
|
|
|
update: &repository.KYCUpdateParams{
|
|
|
|
|
Name: &name,
|
|
|
|
|
Address: &address,
|
|
|
|
|
},
|
|
|
|
|
expectErr: false,
|
|
|
|
|
expectApply: true,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "更新失敗 - 非 PENDING 狀態",
|
|
|
|
|
id: approvedKYC.ID.Hex(),
|
|
|
|
|
update: &repository.KYCUpdateParams{
|
|
|
|
|
Name: &name,
|
|
|
|
|
Address: &address,
|
|
|
|
|
},
|
|
|
|
|
expectErr: false,
|
|
|
|
|
expectApply: false,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
name: "更新失敗 - 非法 ID 格式",
|
|
|
|
|
id: "not-an-id",
|
|
|
|
|
update: &repository.KYCUpdateParams{
|
|
|
|
|
Name: &name,
|
|
|
|
|
},
|
|
|
|
|
expectErr: true,
|
|
|
|
|
expectApply: false,
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
|
err := model.UpdateKYCInfo(ctx, tt.id, tt.update)
|
|
|
|
|
if tt.expectErr {
|
|
|
|
|
assert.Error(t, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
|
|
// 若預期資料已被套用,驗證欄位是否更新
|
|
|
|
|
if tt.expectApply {
|
|
|
|
|
got, err := model.FindByID(ctx, tt.id)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.Equal(t, name, got.Name)
|
|
|
|
|
assert.Equal(t, address, got.Address)
|
|
|
|
|
} else {
|
|
|
|
|
got, err := model.FindByID(ctx, tt.id)
|
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
assert.NotEqual(t, name, got.Name)
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|