feat: add category repository

This commit is contained in:
王性驊 2025-04-04 15:39:49 +08:00
parent cb7e9fb5bb
commit 39841cc168
11 changed files with 827 additions and 11 deletions

View File

@ -39,4 +39,6 @@ mock-gen: # 建立 mock 資料
mockgen -source=./pkg/domain/repository/product_item.go -destination=./pkg/mock/repository/product_item.go -package=mock mockgen -source=./pkg/domain/repository/product_item.go -destination=./pkg/mock/repository/product_item.go -package=mock
mockgen -source=./pkg/domain/repository/product_statistics.go -destination=./pkg/mock/repository/product_statistics.go -package=mock mockgen -source=./pkg/domain/repository/product_statistics.go -destination=./pkg/mock/repository/product_statistics.go -package=mock
mockgen -source=./pkg/domain/repository/tags.go -destination=./pkg/mock/repository/tags.go -package=mock mockgen -source=./pkg/domain/repository/tags.go -destination=./pkg/mock/repository/tags.go -package=mock
mockgen -source=./pkg/domain/repository/kyc.go -destination=./pkg/mock/repository/kyc.go -package=mock
mockgen -source=./pkg/domain/repository/category.go -destination=./pkg/mock/repository/category.go -package=mock
@echo "Generate mock files successfully" @echo "Generate mock files successfully"

View File

@ -0,0 +1,17 @@
package entity
import (
"go.mongodb.org/mongo-driver/bson/primitive"
)
// Category 類別以後獨立服務
type Category struct {
ID primitive.ObjectID `bson:"_id,omitempty"`
Name string `bson:"name"` // 服務類別
UpdatedAt int64 `bson:"updated_at"` // 更新時間
CreatedAt int64 `bson:"created_at"` // 創建時間
}
func (c *Category) CollectionName() string {
return "category"
}

View File

@ -2,6 +2,10 @@ package kyc
type Status string type Status string
func (s *Status) ToString() string {
return string(*s)
}
const ( const (
StatusPending Status = "PENDING" StatusPending Status = "PENDING"
StatusAPPROVED Status = "APPROVED" StatusAPPROVED Status = "APPROVED"

View File

@ -19,6 +19,7 @@ const (
GetProductItemRedisKey RedisKey = "get_item" GetProductItemRedisKey RedisKey = "get_item"
GetProductStatisticsRedisKey RedisKey = "statistics" GetProductStatisticsRedisKey RedisKey = "statistics"
GetTagsRedisKey RedisKey = "tags" GetTagsRedisKey RedisKey = "tags"
CategoryRedisKey RedisKey = "category"
) )
func GetProductRK(id string) string { func GetProductRK(id string) string {
@ -36,3 +37,7 @@ func GetProductStatisticsRK(id string) string {
func GetTagsRK(id string) string { func GetTagsRK(id string) string {
return GetTagsRedisKey.With(id).ToString() return GetTagsRedisKey.With(id).ToString()
} }
func GetCategoryRedisKey(id string) string {
return CategoryRedisKey.With(id).ToString()
}

View File

@ -0,0 +1,25 @@
package repository
import (
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"context"
"go.mongodb.org/mongo-driver/mongo"
)
// CategoryRepository 操作更多信息
type CategoryRepository interface {
Insert(ctx context.Context, data *entity.Category) error
FindOneByID(ctx context.Context, id string) (*entity.Category, error)
Update(ctx context.Context, id string, data *entity.Category) (*mongo.UpdateResult, error)
Delete(ctx context.Context, id string) (int64, error)
ListCategory(ctx context.Context, params *CategoryQueryParams) ([]*entity.Category, int64, error)
// IsCategoryExists 丟進去存在的再拿出來
IsCategoryExists(ctx context.Context, ids []string) []string
}
type CategoryQueryParams struct {
PageSize int64 // 每頁顯示的專案數量
PageIndex int64 // 要查詢的頁數
ID []string
}

25
pkg/domain/usecase/category.go Executable file
View File

@ -0,0 +1,25 @@
package usecase
import (
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
"context"
)
type CategoryUseCase interface {
// Insert 創建新的類別
Insert(ctx context.Context, data *entity.Category) error
// FindOneByID 根據 ID 查詢單個類別
FindOneByID(ctx context.Context, id string) (*entity.Category, error)
// Update 更新類別信息
Update(ctx context.Context, id string, data *entity.Category) error
// Delete 刪除指定 ID 的類別
Delete(ctx context.Context, id string) error
// ListCategory 根據查詢參數獲取類別列表
ListCategory(ctx context.Context, params CategoryQueryParams) ([]*entity.Category, int64, error)
}
type CategoryQueryParams struct {
PageSize int64 // 每頁顯示的專案數量
PageIndex int64 // 要查詢的頁數
ID []string
}

View File

@ -0,0 +1,133 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/category.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/category.go -destination=./pkg/mock/repository/category.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
entity "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
repository "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
mongo "go.mongodb.org/mongo-driver/mongo"
gomock "go.uber.org/mock/gomock"
)
// MockCategoryRepository is a mock of CategoryRepository interface.
type MockCategoryRepository struct {
ctrl *gomock.Controller
recorder *MockCategoryRepositoryMockRecorder
isgomock struct{}
}
// MockCategoryRepositoryMockRecorder is the mock recorder for MockCategoryRepository.
type MockCategoryRepositoryMockRecorder struct {
mock *MockCategoryRepository
}
// NewMockCategoryRepository creates a new mock instance.
func NewMockCategoryRepository(ctrl *gomock.Controller) *MockCategoryRepository {
mock := &MockCategoryRepository{ctrl: ctrl}
mock.recorder = &MockCategoryRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCategoryRepository) EXPECT() *MockCategoryRepositoryMockRecorder {
return m.recorder
}
// Delete mocks base method.
func (m *MockCategoryRepository) Delete(ctx context.Context, id string) (int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Delete", ctx, id)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Delete indicates an expected call of Delete.
func (mr *MockCategoryRepositoryMockRecorder) Delete(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockCategoryRepository)(nil).Delete), ctx, id)
}
// FindOneByID mocks base method.
func (m *MockCategoryRepository) FindOneByID(ctx context.Context, id string) (*entity.Category, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindOneByID", ctx, id)
ret0, _ := ret[0].(*entity.Category)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindOneByID indicates an expected call of FindOneByID.
func (mr *MockCategoryRepositoryMockRecorder) FindOneByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindOneByID", reflect.TypeOf((*MockCategoryRepository)(nil).FindOneByID), ctx, id)
}
// Insert mocks base method.
func (m *MockCategoryRepository) Insert(ctx context.Context, data *entity.Category) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Insert", ctx, data)
ret0, _ := ret[0].(error)
return ret0
}
// Insert indicates an expected call of Insert.
func (mr *MockCategoryRepositoryMockRecorder) Insert(ctx, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockCategoryRepository)(nil).Insert), ctx, data)
}
// IsCategoryExists mocks base method.
func (m *MockCategoryRepository) IsCategoryExists(ctx context.Context, ids []string) []string {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "IsCategoryExists", ctx, ids)
ret0, _ := ret[0].([]string)
return ret0
}
// IsCategoryExists indicates an expected call of IsCategoryExists.
func (mr *MockCategoryRepositoryMockRecorder) IsCategoryExists(ctx, ids any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IsCategoryExists", reflect.TypeOf((*MockCategoryRepository)(nil).IsCategoryExists), ctx, ids)
}
// ListCategory mocks base method.
func (m *MockCategoryRepository) ListCategory(ctx context.Context, params *repository.CategoryQueryParams) ([]*entity.Category, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ListCategory", ctx, params)
ret0, _ := ret[0].([]*entity.Category)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ListCategory indicates an expected call of ListCategory.
func (mr *MockCategoryRepositoryMockRecorder) ListCategory(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListCategory", reflect.TypeOf((*MockCategoryRepository)(nil).ListCategory), ctx, params)
}
// Update mocks base method.
func (m *MockCategoryRepository) Update(ctx context.Context, id string, data *entity.Category) (*mongo.UpdateResult, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Update", ctx, id, data)
ret0, _ := ret[0].(*mongo.UpdateResult)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update.
func (mr *MockCategoryRepositoryMockRecorder) Update(ctx, id, data any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockCategoryRepository)(nil).Update), ctx, id, data)
}

131
pkg/mock/repository/kyc.go Normal file
View File

@ -0,0 +1,131 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: ./pkg/domain/repository/kyc.go
//
// Generated by this command:
//
// mockgen -source=./pkg/domain/repository/kyc.go -destination=./pkg/mock/repository/kyc.go -package=mock
//
// Package mock is a generated GoMock package.
package mock
import (
context "context"
reflect "reflect"
entity "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity"
repository "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository"
gomock "go.uber.org/mock/gomock"
)
// MockKYCRepository is a mock of KYCRepository interface.
type MockKYCRepository struct {
ctrl *gomock.Controller
recorder *MockKYCRepositoryMockRecorder
isgomock struct{}
}
// MockKYCRepositoryMockRecorder is the mock recorder for MockKYCRepository.
type MockKYCRepositoryMockRecorder struct {
mock *MockKYCRepository
}
// NewMockKYCRepository creates a new mock instance.
func NewMockKYCRepository(ctrl *gomock.Controller) *MockKYCRepository {
mock := &MockKYCRepository{ctrl: ctrl}
mock.recorder = &MockKYCRepositoryMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockKYCRepository) EXPECT() *MockKYCRepositoryMockRecorder {
return m.recorder
}
// Create mocks base method.
func (m *MockKYCRepository) Create(ctx context.Context, kyc *entity.KYC) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Create", ctx, kyc)
ret0, _ := ret[0].(error)
return ret0
}
// Create indicates an expected call of Create.
func (mr *MockKYCRepositoryMockRecorder) Create(ctx, kyc any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Create", reflect.TypeOf((*MockKYCRepository)(nil).Create), ctx, kyc)
}
// FindByID mocks base method.
func (m *MockKYCRepository) FindByID(ctx context.Context, id string) (*entity.KYC, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindByID", ctx, id)
ret0, _ := ret[0].(*entity.KYC)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindByID indicates an expected call of FindByID.
func (mr *MockKYCRepositoryMockRecorder) FindByID(ctx, id any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindByID", reflect.TypeOf((*MockKYCRepository)(nil).FindByID), ctx, id)
}
// FindLatestByUID mocks base method.
func (m *MockKYCRepository) FindLatestByUID(ctx context.Context, uid string) (*entity.KYC, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "FindLatestByUID", ctx, uid)
ret0, _ := ret[0].(*entity.KYC)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// FindLatestByUID indicates an expected call of FindLatestByUID.
func (mr *MockKYCRepositoryMockRecorder) FindLatestByUID(ctx, uid any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FindLatestByUID", reflect.TypeOf((*MockKYCRepository)(nil).FindLatestByUID), ctx, uid)
}
// List mocks base method.
func (m *MockKYCRepository) List(ctx context.Context, params repository.KYCQueryParams) ([]*entity.KYC, int64, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "List", ctx, params)
ret0, _ := ret[0].([]*entity.KYC)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// List indicates an expected call of List.
func (mr *MockKYCRepositoryMockRecorder) List(ctx, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "List", reflect.TypeOf((*MockKYCRepository)(nil).List), ctx, params)
}
// UpdateKYCInfo mocks base method.
func (m *MockKYCRepository) UpdateKYCInfo(ctx context.Context, id string, update *repository.KYCUpdateParams) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateKYCInfo", ctx, id, update)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateKYCInfo indicates an expected call of UpdateKYCInfo.
func (mr *MockKYCRepositoryMockRecorder) UpdateKYCInfo(ctx, id, update any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateKYCInfo", reflect.TypeOf((*MockKYCRepository)(nil).UpdateKYCInfo), ctx, id, update)
}
// UpdateStatus mocks base method.
func (m *MockKYCRepository) UpdateStatus(ctx context.Context, id, status, reason string) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "UpdateStatus", ctx, id, status, reason)
ret0, _ := ret[0].(error)
return ret0
}
// UpdateStatus indicates an expected call of UpdateStatus.
func (mr *MockKYCRepositoryMockRecorder) UpdateStatus(ctx, id, status, reason any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateStatus", reflect.TypeOf((*MockKYCRepository)(nil).UpdateStatus), ctx, id, status, reason)
}

191
pkg/repository/category.go Normal file
View File

@ -0,0 +1,191 @@
package repository
import (
"code.30cm.net/digimon/app-cloudep-product-service/pkg/domain"
"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"
"errors"
"time"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
)
type CategoryRepositoryParam struct {
Conf *mgo.Conf
CacheConf cache.CacheConf
DBOpts []mon.Option
CacheOpts []cache.Option
}
type CategoryRepository struct {
DB mgo.DocumentDBWithCacheUseCase
}
func MustCategoryRepository(param CategoryRepositoryParam) repository.CategoryRepository {
e := entity.Category{}
documentDB, err := mgo.MustDocumentDBWithCache(
param.Conf,
e.CollectionName(),
param.CacheConf,
param.DBOpts,
param.CacheOpts,
)
if err != nil {
panic(err)
}
return &CategoryRepository{
DB: documentDB,
}
}
func (repo *CategoryRepository) IsCategoryExists(ctx context.Context, ids []string) []string {
// 將傳入的 string id 轉換為 ObjectID
objectIDs := make([]primitive.ObjectID, 0, len(ids))
for _, id := range ids {
objID, err := primitive.ObjectIDFromHex(id)
if err == nil { // 如果轉換失敗,忽略該 id
objectIDs = append(objectIDs, objID)
}
}
// 查詢存在的標籤
filter := bson.M{"_id": bson.M{"$in": objectIDs}}
// find 並沒有快取要快取要去其他地方做
opts := options.Find().SetProjection(bson.M{"_id": 1}) // 只返回 _id 欄位
// 執行查詢並獲取結果
var category []*entity.Category
err := repo.DB.GetClient().Find(ctx, &category, filter, opts)
if err != nil {
return []string{}
}
// 將存在的標籤ID轉換回 string並加入結果列表
existingIDs := make([]string, 0, len(category))
for _, item := range category {
existingIDs = append(existingIDs, item.ID.Hex())
}
return existingIDs
}
func (repo *CategoryRepository) Insert(ctx context.Context, data *entity.Category) error {
now := time.Now().UTC().UnixNano()
if data.ID.IsZero() {
data.ID = primitive.NewObjectID()
data.CreatedAt = now
data.UpdatedAt = now
}
_, err := repo.DB.GetClient().InsertOne(ctx, data)
return err
}
func (repo *CategoryRepository) FindOneByID(ctx context.Context, id string) (*entity.Category, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, ErrInvalidObjectID
}
var data entity.Category
rk := domain.GetCategoryRedisKey(id)
err = repo.DB.FindOne(ctx, rk, &data, bson.M{"_id": oid})
switch {
case err == nil:
return &data, nil
case errors.Is(err, mon.ErrNotFound):
return nil, ErrNotFound
default:
return nil, err
}
}
func (repo *CategoryRepository) Update(ctx context.Context, id string, data *entity.Category) (*mongo.UpdateResult, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return nil, ErrInvalidObjectID
}
updateFields := bson.M{}
if data.Name != "" {
updateFields["name"] = data.Name
}
updateFields["updated_at"] = time.Now().UTC().UnixNano()
// 構建查找條件
filter := bson.M{"_id": oid}
update := bson.M{"$set": updateFields}
opt := options.Update().SetUpsert(false)
rk := domain.GetCategoryRedisKey(id)
res, err := repo.DB.UpdateOne(ctx, rk, filter, update, opt)
if err != nil {
return nil, err
}
// 檢查更新結果,若沒有匹配的文檔,則返回錯誤
if res.MatchedCount == 0 {
return nil, ErrNotFound // 自定義的錯誤表示未找到記錄
}
return res, err
}
func (repo *CategoryRepository) Delete(ctx context.Context, id string) (int64, error) {
oid, err := primitive.ObjectIDFromHex(id)
if err != nil {
return 0, ErrInvalidObjectID
}
rk := domain.GetCategoryRedisKey(id)
res, err := repo.DB.DeleteOne(ctx, rk, bson.M{"_id": oid})
return res, err
}
func (repo *CategoryRepository) ListCategory(ctx context.Context, params *repository.CategoryQueryParams) ([]*entity.Category, int64, error) {
// TODO 有需要列表快取實在取列表快取
// 構建查詢過濾器
filter := bson.M{}
if len(params.ID) > 0 {
objectIDs := make([]primitive.ObjectID, 0, len(params.ID))
for _, id := range params.ID {
objID, err := primitive.ObjectIDFromHex(id)
if err != nil {
continue
}
objectIDs = append(objectIDs, objID)
}
filter["_id"] = bson.M{"$in": objectIDs}
}
// 設置排序選項
opts := options.Find().SetSkip((params.PageIndex - 1) * params.PageSize).SetLimit(params.PageSize)
opts.SetSort(bson.D{{Key: "created_at", Value: -1}})
// 查詢符合條件的總數
count, err := repo.DB.GetClient().CountDocuments(ctx, filter)
if err != nil {
return nil, 0, err
}
// 執行查詢並獲取結果
var category []*entity.Category
err = repo.DB.GetClient().Find(ctx, &category, filter, opts)
if err != nil {
return nil, 0, err
}
return category, count, nil
}

View File

@ -0,0 +1,282 @@
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 SetupTestCategoryRepository(db string) (repository.CategoryRepository, 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 := CategoryRepositoryParam{
Conf: conf,
CacheConf: cacheConf,
CacheOpts: cacheOpts,
DBOpts: []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
},
}
repo := MustCategoryRepository(param)
//_, _ = repo.Index20250317001UP(context.Background())
return repo, tearDown, nil
}
func TestCategoryRepository_Insert(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
testData := &entity.Category{
Name: "Test Category",
}
err = repo.Insert(ctx, testData)
assert.NoError(t, err, "插入操作應該成功")
assert.NotZero(t, testData.ID, "ID 應該被生成")
assert.NotNil(t, testData.CreatedAt, "CreateAt 應該被設置")
assert.NotNil(t, testData.UpdatedAt, "UpdateAt 應該被設置")
}
func TestCategoryRepository_FindOneByID(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
testData := &entity.Category{
Name: "Test Category Find",
}
_ = repo.Insert(ctx, testData)
tests := []struct {
name string
id string
expectError bool
}{
{
name: "Valid ID - Find Record",
id: testData.ID.Hex(),
expectError: false,
},
{
name: "Invalid ID Format",
id: "invalid_id",
expectError: true,
},
{
name: "Nonexistent ID",
id: primitive.NewObjectID().Hex(),
expectError: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result, err := repo.FindOneByID(ctx, tt.id)
if tt.expectError {
assert.Error(t, err)
assert.Nil(t, result)
} else {
assert.NoError(t, err)
assert.Equal(t, testData.Name, result.Name)
}
})
}
}
func TestCategoryRepository_Update(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
testData := &entity.Category{
Name: "Test Category Update",
}
_ = repo.Insert(ctx, testData)
updatedData := &entity.Category{
ID: testData.ID,
Name: "Updated Category Name",
}
result, err := repo.Update(ctx, testData.ID.Hex(), updatedData)
assert.NoError(t, err, "更新操作應該成功")
assert.Equal(t, int64(1), result.ModifiedCount, "更新的文件數量應為 1")
// 檢查更新後的數據
updatedRecord, err := repo.FindOneByID(ctx, testData.ID.Hex())
assert.NoError(t, err, "應該可以找到更新的記錄")
assert.Equal(t, "Updated Category Name", updatedRecord.Name, "Category 名稱應該被更新")
}
func TestCategoryRepository_Delete(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
testData := &entity.Category{
Name: "Test Category Delete",
}
_ = repo.Insert(ctx, testData)
tests := []struct {
name string
id string
expectError bool
expectCount int64
}{
{
name: "Valid ID - Successful Deletion",
id: testData.ID.Hex(),
expectError: false,
expectCount: 1,
},
{
name: "Invalid ID Format",
id: "invalid_id",
expectError: true,
expectCount: 0,
},
{
name: "Nonexistent ID",
id: primitive.NewObjectID().Hex(),
expectError: false,
expectCount: 0,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
count, err := repo.Delete(ctx, tt.id)
if tt.expectError {
assert.Error(t, err)
assert.Equal(t, int64(0), count)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expectCount, count)
}
})
}
}
func TestCategoryRepository_ListProduct(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
// 插入測試數據
for i := 0; i < 5; i++ {
_ = repo.Insert(ctx, &entity.Category{
Name: "Test Category List",
})
}
params := &repository.CategoryQueryParams{
PageSize: 2,
PageIndex: 1,
}
categories, count, err := repo.ListCategory(ctx, params)
assert.NoError(t, err, "查詢應該成功")
assert.Equal(t, int64(5), count, "總數應該為 5")
assert.Len(t, categories, int(params.PageSize), "返回的數據應符合頁面大小")
}
func TestCategoryRepository_IsCategoryExists(t *testing.T) {
repo, tearDown, err := SetupTestCategoryRepository("testDB")
defer tearDown()
assert.NoError(t, err)
ctx := context.Background()
// 插入一些測試分類數據
category1 := &entity.Category{ID: primitive.NewObjectID()}
category2 := &entity.Category{ID: primitive.NewObjectID()}
category3 := &entity.Category{ID: primitive.NewObjectID()}
_ = repo.Insert(ctx, category1)
_ = repo.Insert(ctx, category2)
// 定義測試案例
tests := []struct {
name string
ids []string
expectedIDs []string
}{
{
name: "部分分類存在",
ids: []string{category1.ID.Hex(), category2.ID.Hex(), category3.ID.Hex()}, // category3 不存在
expectedIDs: []string{category1.ID.Hex(), category2.ID.Hex()},
},
{
name: "所有分類存在",
ids: []string{category1.ID.Hex(), category2.ID.Hex()},
expectedIDs: []string{category1.ID.Hex(), category2.ID.Hex()},
},
{
name: "無分類存在",
ids: []string{category3.ID.Hex()}, // category3 不存在
expectedIDs: []string{},
},
{
name: "包含無效 ID",
ids: []string{category1.ID.Hex(), "invalid_id"}, // "invalid_id" 是無效 ID 格式
expectedIDs: []string{category1.ID.Hex()},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := repo.IsCategoryExists(ctx, tt.ids)
assert.ElementsMatch(t, tt.expectedIDs, result, "返回的存在ID應該與期望值匹配")
})
}
}

View File

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