package repository import ( "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" "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 資料 kyc := &entity.KYC{ UID: "user-status", Name: "測試用戶", Status: "PENDING", CreatedAt: time.Now().UnixNano(), UpdatedAt: time.Now().UnixNano(), } err = model.Create(ctx, kyc) assert.NoError(t, err) tests := []struct { name string id string newStatus string reason string expectErr bool expectStatus string }{ { name: "成功更新為 REJECTED", id: kyc.ID.Hex(), newStatus: "REJECTED", reason: "文件不符", expectErr: false, expectStatus: "REJECTED", }, { name: "失敗 - 無效 ID 格式", id: "not-a-valid-id", newStatus: "APPROVED", reason: "", expectErr: true, }, { name: "失敗 - 找不到資料", id: primitive.NewObjectID().Hex(), // 合法但不存在 newStatus: "APPROVED", reason: "", expectErr: false, // Mongo 不會報錯,但更新筆數是 0,後面可以補強這邊驗證 }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := model.UpdateStatus(ctx, tt.id, tt.newStatus, tt.reason) 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) } }) } }