app-cloudep-product-service/pkg/usecase/kyc_test.go

554 lines
14 KiB
Go

package usecase
import (
"context"
"errors"
"fmt"
"testing"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/kyc"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase"
mockRepository "code.30cm.net/digimon/app-cloudep-product-service/pkg/mock/repository"
repo "code.30cm.net/digimon/app-cloudep-product-service/pkg/repository"
"code.30cm.net/digimon/library-go/errs"
"github.com/stretchr/testify/assert"
"github.com/zeromicro/go-zero/core/logx"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.uber.org/mock/gomock"
"google.golang.org/protobuf/proto"
)
func TestKYCUseCase_Create(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
uid := "test-user"
tests := []struct {
name string
input *entity.KYC
mockSetup func()
expectedError error
}{
{
name: "查不到資料可以建立",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(nil, repo.ErrNotFound)
mockKYCRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
},
expectedError: nil,
},
{
name: "前一筆為 REJECTED 可建立",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(&entity.KYC{
UID: uid,
Status: kyc.StatusREJECTED,
}, nil)
mockKYCRepo.EXPECT().Create(ctx, gomock.Any()).Return(nil)
},
expectedError: nil,
},
{
name: "已存在未駁回資料,禁止建立",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(&entity.KYC{
UID: uid,
Status: kyc.StatusPending,
}, nil)
},
expectedError: errs.ForbiddenL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: &entity.KYC{UID: uid}},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "reason", Value: "KYC already in progress or approved"},
},
"不能重複送出 KYC 資料",
),
},
{
name: "查詢資料庫錯誤",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(nil, errors.New("database error"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: &entity.KYC{UID: uid}},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "err", Value: "database error"},
},
"failed to get latest kyc",
),
},
{
name: "建立時資料庫錯誤",
input: &entity.KYC{
UID: uid,
},
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(nil, repo.ErrNotFound)
mockKYCRepo.EXPECT().Create(ctx, gomock.Any()).Return(errors.New("insert failed"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "param", Value: &entity.KYC{UID: uid, Status: kyc.StatusPending}},
{Key: "func", Value: "KYCRepo.Create"},
{Key: "err", Value: "insert failed"},
},
"failed to create kyc review",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
err := useCase.Create(ctx, tt.input)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestKYCUseCase_FindLatestByUID(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
uid := "test-user"
tests := []struct {
name string
inputUID string
mockSetup func()
expectedResult *entity.KYC
expectedError error
}{
{
name: "查詢成功",
inputUID: uid,
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(&entity.KYC{
UID: uid,
Status: kyc.StatusPending,
}, nil)
},
expectedResult: &entity.KYC{
UID: uid,
Status: kyc.StatusPending,
},
expectedError: nil,
},
{
name: "資料庫錯誤",
inputUID: uid,
mockSetup: func() {
mockKYCRepo.EXPECT().FindLatestByUID(ctx, uid).Return(nil, errors.New("database error"))
},
expectedResult: nil,
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "uid", Value: uid},
{Key: "func", Value: "KYCRepo.FindLatestByUID"},
{Key: "err", Value: "database error"},
},
"failed to get latest kyc",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
result, err := useCase.FindLatestByUID(ctx, tt.inputUID)
if tt.expectedError == nil {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
assert.Nil(t, result)
}
})
}
}
func TestKYCUseCase_FindByID(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
objID := primitive.NewObjectID()
idStr := objID.Hex()
tests := []struct {
name string
inputID string
mockSetup func()
expectedResult *entity.KYC
expectedError error
}{
{
name: "查詢成功",
inputID: idStr,
mockSetup: func() {
mockKYCRepo.EXPECT().FindByID(ctx, idStr).Return(&entity.KYC{
ID: objID,
UID: "user-1",
Status: kyc.StatusAPPROVED,
}, nil)
},
expectedResult: &entity.KYC{
ID: objID,
UID: "user-1",
Status: kyc.StatusAPPROVED,
},
expectedError: nil,
},
{
name: "資料庫錯誤",
inputID: idStr,
mockSetup: func() {
mockKYCRepo.EXPECT().FindByID(ctx, idStr).Return(nil, errors.New("database error"))
},
expectedResult: nil,
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "id", Value: idStr},
{Key: "func", Value: "KYCRepo.FindByID"},
{Key: "err", Value: "database error"},
},
"failed to get kyc",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
result, err := useCase.FindByID(ctx, tt.inputID)
if tt.expectedError == nil {
assert.NoError(t, err)
assert.Equal(t, tt.expectedResult, result)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
assert.Nil(t, result)
}
})
}
}
func TestKYCUseCase_List(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
uid := "user-1"
country := "TW"
status := kyc.StatusAPPROVED
ss := status.ToString()
tests := []struct {
name string
inputParams usecase.KYCQueryParams
mockSetup func()
expectedList []*entity.KYC
expectedCount int64
expectedError error
}{
{
name: "查詢成功",
inputParams: usecase.KYCQueryParams{
PageIndex: 1,
PageSize: 10,
UID: &uid,
Country: &country,
Status: &ss,
},
mockSetup: func() {
mockKYCRepo.EXPECT().List(ctx, repository.KYCQueryParams{
PageIndex: 1,
PageSize: 10,
UID: &uid,
Country: &country,
Status: &ss,
SortByDate: true,
}).Return([]*entity.KYC{
{UID: uid, CountryRegion: country, Status: status},
}, int64(1), nil)
},
expectedList: []*entity.KYC{
{UID: uid, CountryRegion: country, Status: status},
},
expectedCount: 1,
expectedError: nil,
},
{
name: "查詢失敗 - 資料庫錯誤",
inputParams: usecase.KYCQueryParams{
PageIndex: 2,
PageSize: 20,
},
mockSetup: func() {
mockKYCRepo.EXPECT().List(ctx, repository.KYCQueryParams{
PageIndex: 2,
PageSize: 20,
SortByDate: true,
}).Return(nil, int64(0), errors.New("database error"))
},
expectedList: nil,
expectedCount: 0,
expectedError: errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "params", Value: usecase.KYCQueryParams{
PageIndex: 2,
PageSize: 20,
}},
{Key: "func", Value: "KYCRepo.List"},
{Key: "err", Value: "database error"},
}, "failed to list kyc"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
list, count, err := useCase.List(ctx, tt.inputParams)
assert.Equal(t, tt.expectedList, list)
assert.Equal(t, tt.expectedCount, count)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestKYCUseCase_UpdateStatus(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
id := primitive.NewObjectID().Hex()
status := kyc.StatusAPPROVED
reason := "all good"
tests := []struct {
name string
id string
status kyc.Status
reason string
mockSetup func()
expectedError error
}{
{
name: "更新成功",
id: id,
status: status,
reason: reason,
mockSetup: func() {
mockKYCRepo.EXPECT().UpdateStatus(ctx, id, status.ToString(), reason).Return(nil)
},
expectedError: nil,
},
{
name: "更新失敗 - DB 錯誤",
id: id,
status: status,
reason: reason,
mockSetup: func() {
mockKYCRepo.EXPECT().UpdateStatus(ctx, id, status.ToString(), reason).Return(errors.New("db error"))
},
expectedError: errs.DBErrorL(
logx.WithContext(ctx),
[]logx.LogField{
{Key: "params", Value: fmt.Sprintf("id:%s, status:%s, reason: %s", id, status, reason)},
{Key: "func", Value: "KYCRepo.UpdateStatus"},
{Key: "err", Value: "db error"},
},
"failed to update kyc status",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
err := useCase.UpdateStatus(ctx, tt.id, tt.status.ToString(), tt.reason)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}
func TestKYCUseCase_UpdateKYCInfo(t *testing.T) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockKYCRepo := mockRepository.NewMockKYCRepository(mockCtrl)
useCase := MustKYCUseCase(KYCUseCaseParam{
KYCRepo: mockKYCRepo,
})
ctx := context.Background()
id := primitive.NewObjectID().Hex()
updateParams := &usecase.KYCUpdateParams{
Name: proto.String("Daniel Wang"),
Identification: proto.String("A123456789"),
IdentificationType: proto.String("passport"),
Address: proto.String("Taipei City"),
PostalCode: proto.String("100"),
DateOfBirth: proto.String("1993-04-17"),
Gender: proto.String("M"),
IDFrontImage: proto.String("https://example.com/front.jpg"),
IDBackImage: proto.String("https://example.com/back.jpg"),
BankStatementImg: proto.String("https://example.com/bank.jpg"),
BankCode: proto.String("123"),
BankName: proto.String("ABC Bank"),
BranchCode: proto.String("001"),
BranchName: proto.String("Taipei Branch"),
BankAccount: proto.String("1234567890"),
}
tests := []struct {
name string
id string
input *usecase.KYCUpdateParams
mockSetup func()
expectedError error
}{
{
name: "更新成功",
id: id,
input: updateParams,
mockSetup: func() {
mockKYCRepo.EXPECT().UpdateKYCInfo(ctx, id, &repository.KYCUpdateParams{
Name: updateParams.Name,
Identification: updateParams.Identification,
IdentificationType: updateParams.IdentificationType,
Address: updateParams.Address,
PostalCode: updateParams.PostalCode,
DateOfBirth: updateParams.DateOfBirth,
Gender: updateParams.Gender,
IDFrontImage: updateParams.IDFrontImage,
IDBackImage: updateParams.IDBackImage,
BankStatementImg: updateParams.BankStatementImg,
BankCode: updateParams.BankCode,
BankName: updateParams.BankName,
BranchCode: updateParams.BranchCode,
BranchName: updateParams.BranchName,
BankAccount: updateParams.BankAccount,
}).Return(nil)
},
expectedError: nil,
},
{
name: "更新失敗 - DB 錯誤",
id: id,
input: updateParams,
mockSetup: func() {
mockKYCRepo.EXPECT().UpdateKYCInfo(ctx, id, gomock.Any()).Return(errors.New("db error"))
},
expectedError: errs.DBErrorL(logx.WithContext(ctx),
[]logx.LogField{
{Key: "id", Value: id},
{Key: "params", Value: updateParams},
{Key: "func", Value: "KYCRepo.UpdateKYCInfo"},
{Key: "err", Value: "db error"},
},
"failed to update kyc",
),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
tt.mockSetup()
err := useCase.UpdateKYCInfo(ctx, tt.id, tt.input)
if tt.expectedError == nil {
assert.NoError(t, err)
} else {
assert.Error(t, err)
assert.Equal(t, tt.expectedError.Error(), err.Error())
}
})
}
}