From c4617956e5a2f2e78dd489b807e5f58833be39e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E6=80=A7=E9=A9=8A?= Date: Sat, 5 Apr 2025 22:29:53 +0800 Subject: [PATCH] feat: usecast test --- pkg/usecase/product_item_test.go | 309 +++++++++++++++++++++++++++++++ pkg/usecase/tags.go | 3 +- pkg/usecase/tags_test.go | 244 ++++++++++++++++++++++++ 3 files changed, 554 insertions(+), 2 deletions(-) create mode 100644 pkg/usecase/product_item_test.go create mode 100644 pkg/usecase/tags_test.go diff --git a/pkg/usecase/product_item_test.go b/pkg/usecase/product_item_test.go new file mode 100644 index 0000000..0b5c9d2 --- /dev/null +++ b/pkg/usecase/product_item_test.go @@ -0,0 +1,309 @@ +package usecase + +import ( + "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" + "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product" + "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" + "context" + "errors" + "github.com/shopspring/decimal" + "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.uber.org/mock/gomock" + "testing" +) + +func TestProductItemUseCase_Create(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockProductItemRepo := mockRepository.NewMockProductItemRepository(mockCtrl) + + useCase := MustProductItemUseCase(ProductItemUseCaseParam{ + ProductItems: mockProductItemRepo, + }) + + ctx := context.Background() + input := &usecase.ProductItems{ + ReferenceID: "p-id", + Name: "Item A", + Description: "desc", + ShortDescription: "short", + IsUnLimit: false, + IsFree: false, + Stock: 100, + Price: "123.45", + SKU: "SKU001", + TimeSeries: product.TimeSeriesTenMinutes, + Status: product.StatusUnderReview, + } + + t.Run("建立成功", func(t *testing.T) { + mockProductItemRepo.EXPECT().Insert(ctx, gomock.Any()).Return(nil) + err := useCase.Create(ctx, input) + assert.NoError(t, err) + }) + + t.Run("轉換失敗 - 價格格式錯誤", func(t *testing.T) { + badInput := *input + badInput.Price = "invalid-price" + err := useCase.Create(ctx, &badInput) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to change use case into entity") + }) + + t.Run("建立失敗 - DB 錯誤", func(t *testing.T) { + mockProductItemRepo.EXPECT().Insert(ctx, gomock.Any()).Return(errors.New("db error")) + err := useCase.Create(ctx, input) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to insert product item") + }) +} + +func TestProductItemUseCase_Get(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockProductItemRepo := mockRepository.NewMockProductItemRepository(mockCtrl) + + useCase := MustProductItemUseCase(ProductItemUseCaseParam{ + ProductItems: mockProductItemRepo, + }) + + ctx := context.Background() + id := primitive.NewObjectID().Hex() + + ent := &entity.ProductItems{ + ID: primitive.NewObjectID(), + ReferenceID: "ref-id", + Name: "Item A", + Description: "desc", + ShortDescription: "short", + IsUnLimit: true, + IsFree: false, + Stock: 50, + Price: decimal.NewFromInt(999), + SKU: "sku-01", + TimeSeries: product.TimeSeriesHalfHour, + Status: product.StatusUnderReview, + SalesCount: 10, + UpdatedAt: 1700000000, + CreatedAt: 1600000000, + } + + t.Run("查詢成功", func(t *testing.T) { + mockProductItemRepo.EXPECT().FindByID(ctx, id).Return(ent, nil) + resp, err := useCase.Get(ctx, id) + assert.NoError(t, err) + assert.Equal(t, ent.Name, resp.Name) + assert.Equal(t, ent.Price.String(), resp.Price) + }) + + t.Run("查詢失敗 - DB 錯誤", func(t *testing.T) { + mockProductItemRepo.EXPECT().FindByID(ctx, id).Return(nil, errors.New("db error")) + resp, err := useCase.Get(ctx, id) + assert.Error(t, err) + assert.Nil(t, resp) + assert.Contains(t, err.Error(), "failed to create product items") + }) +} + +func TestProductItemUseCase_Update(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockRepo := mockRepository.NewMockProductItemRepository(mockCtrl) + useCase := MustProductItemUseCase(ProductItemUseCaseParam{ + ProductItems: mockRepo, + }) + + ctx := context.Background() + id := primitive.NewObjectID().Hex() + + t.Run("更新成功", func(t *testing.T) { + stock := uint64(100) + price := "123.45" + param := &usecase.UpdateProductItems{ + Name: ptr("New Name"), + Stock: &stock, + Price: &price, + IsFree: ptr(true), + } + mockRepo.EXPECT().Update(ctx, id, gomock.Any()).Return(nil) + + err := useCase.Update(ctx, id, param) + assert.NoError(t, err) + }) + + t.Run("更新失敗 - 價格格式錯誤", func(t *testing.T) { + price := "invalid-price" + param := &usecase.UpdateProductItems{ + Price: &price, + } + err := useCase.Update(ctx, id, param) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to convert price") + }) + + t.Run("更新失敗 - DB 錯誤", func(t *testing.T) { + price := "456.78" + param := &usecase.UpdateProductItems{ + Price: &price, + } + mockRepo.EXPECT().Update(ctx, id, gomock.Any()).Return(errors.New("db error")) + err := useCase.Update(ctx, id, param) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to update product item") + }) +} + +func TestProductItemUseCase_IncSalesCount(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockItemRepo := mockRepository.NewMockProductItemRepository(mockCtrl) + useCase := MustProductItemUseCase(ProductItemUseCaseParam{ + ProductItems: mockItemRepo, + }) + + ctx := context.Background() + id := "item-123" + saleCount := uint64(10) + + t.Run("成功增加銷售數量", func(t *testing.T) { + mockItemRepo.EXPECT().IncSalesCount(ctx, id, int64(saleCount)).Return(nil) + err := useCase.IncSalesCount(ctx, id, saleCount) + assert.NoError(t, err) + }) + + t.Run("資料庫錯誤", func(t *testing.T) { + mockItemRepo.EXPECT().IncSalesCount(ctx, id, int64(saleCount)).Return(errors.New("db error")) + err := useCase.IncSalesCount(ctx, id, saleCount) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to insert product item") + }) +} + +func TestProductItemUseCase_DecSalesCount(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockItemRepo := mockRepository.NewMockProductItemRepository(mockCtrl) + useCase := MustProductItemUseCase(ProductItemUseCaseParam{ + ProductItems: mockItemRepo, + }) + + ctx := context.Background() + id := "item-123" + saleCount := uint64(5) + + t.Run("成功減少銷售數量", func(t *testing.T) { + mockItemRepo.EXPECT().DecSalesCount(ctx, id, int64(saleCount)).Return(nil) + err := useCase.DecSalesCount(ctx, id, saleCount) + assert.NoError(t, err) + }) + + t.Run("資料庫錯誤", func(t *testing.T) { + mockItemRepo.EXPECT().DecSalesCount(ctx, id, int64(saleCount)).Return(errors.New("db error")) + err := useCase.DecSalesCount(ctx, id, saleCount) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to Dec product item") + }) +} + +func TestProductItemUseCase_Delete(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockItemRepo := mockRepository.NewMockProductItemRepository(mockCtrl) + useCase := MustProductItemUseCase(ProductItemUseCaseParam{ + ProductItems: mockItemRepo, + }) + + ctx := context.Background() + id := "item-456" + + t.Run("刪除成功", func(t *testing.T) { + mockItemRepo.EXPECT().Delete(ctx, []string{id}).Return(nil) + err := useCase.Delete(ctx, id) + assert.NoError(t, err) + }) + + t.Run("資料庫錯誤", func(t *testing.T) { + mockItemRepo.EXPECT().Delete(ctx, []string{id}).Return(errors.New("db error")) + err := useCase.Delete(ctx, id) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to delete product item") + }) +} + +func TestProductItemUseCase_List(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockItemRepo := mockRepository.NewMockProductItemRepository(mockCtrl) + useCase := MustProductItemUseCase(ProductItemUseCaseParam{ + ProductItems: mockItemRepo, + }) + + ctx := context.Background() + refID := "ref-123" + params := usecase.QueryProductItemParam{ + PageSize: 10, + PageIndex: 1, + ReferenceID: &refID, + } + repoParams := repository.ProductItemQueryParams{ + PageSize: 10, + PageIndex: 1, + ReferenceID: &refID, + } + + t.Run("成功列出項目", func(t *testing.T) { + mockItemRepo.EXPECT().ListProductItem(ctx, repoParams).Return([]entity.ProductItems{{ReferenceID: refID, Name: "Item A"}}, int64(1), nil) + items, total, err := useCase.List(ctx, params) + assert.NoError(t, err) + assert.Equal(t, int64(1), total) + assert.Equal(t, 1, len(items)) + }) + + t.Run("查詢失敗 - DB 錯誤", func(t *testing.T) { + mockItemRepo.EXPECT().ListProductItem(ctx, repoParams).Return(nil, int64(0), errors.New("db error")) + items, total, err := useCase.List(ctx, params) + assert.Error(t, err) + assert.Nil(t, items) + assert.Equal(t, int64(0), total) + assert.Contains(t, err.Error(), "failed to list product item") + }) +} + +func TestProductItemUseCase_UpdateStatus(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockRepo := mockRepository.NewMockProductItemRepository(mockCtrl) + useCase := MustProductItemUseCase(ProductItemUseCaseParam{ + ProductItems: mockRepo, + }) + + ctx := context.Background() + id := primitive.NewObjectID().Hex() + status := product.StatusUnderReview + + t.Run("更新成功", func(t *testing.T) { + mockRepo.EXPECT().UpdateStatus(ctx, id, status).Return(nil) + + err := useCase.UpdateStatus(ctx, id, status) + assert.NoError(t, err) + }) + + t.Run("更新失敗 - 資料庫錯誤", func(t *testing.T) { + mockRepo.EXPECT().UpdateStatus(ctx, id, status).Return(errors.New("db error")) + + err := useCase.UpdateStatus(ctx, id, status) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to update product item status") + }) +} diff --git a/pkg/usecase/tags.go b/pkg/usecase/tags.go index bdb0c38..48635b4 100644 --- a/pkg/usecase/tags.go +++ b/pkg/usecase/tags.go @@ -4,14 +4,13 @@ import ( "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" repo "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/repository" "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/usecase" - "code.30cm.net/digimon/app-cloudep-product-service/pkg/repository" "code.30cm.net/digimon/library-go/errs" "context" "github.com/zeromicro/go-zero/core/logx" ) type TagsUseCaseParam struct { - TagsRepo repository.TagsRepository + TagsRepo repo.TagRepo } type TagsUseCase struct { diff --git a/pkg/usecase/tags_test.go b/pkg/usecase/tags_test.go new file mode 100644 index 0000000..c155e5f --- /dev/null +++ b/pkg/usecase/tags_test.go @@ -0,0 +1,244 @@ +package usecase + +import ( + "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/entity" + "code.30cm.net/digimon/app-cloudep-product-service/pkg/domain/product" + repo "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" + "context" + "errors" + "github.com/stretchr/testify/assert" + "go.mongodb.org/mongo-driver/bson/primitive" + "go.uber.org/mock/gomock" + "testing" +) + +func TestTagsUseCase_Create(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTagsRepo := mockRepository.NewMockTagRepo(mockCtrl) + + useCase := MustTagsUseCase(TagsUseCaseParam{ + TagsRepo: mockTagsRepo, + }) + + ctx := context.Background() + input := &entity.Tags{ + Name: "Test Tag", + Types: product.ItemTypeProduct, + ShowType: product.ShowTypeNormal, + } + + t.Run("建立成功", func(t *testing.T) { + mockTagsRepo.EXPECT().Create(ctx, input).Return(nil) + + err := useCase.Create(ctx, input) + assert.NoError(t, err) + }) + + t.Run("建立失敗 - DB 錯誤", func(t *testing.T) { + mockTagsRepo.EXPECT().Create(ctx, input).Return(errors.New("db error")) + + err := useCase.Create(ctx, input) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to create tags") + }) +} + +func TestTagsUseCase_GetByID(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTagsRepo := mockRepository.NewMockTagRepo(mockCtrl) + + useCase := MustTagsUseCase(TagsUseCaseParam{ + TagsRepo: mockTagsRepo, + }) + + ctx := context.Background() + id := primitive.NewObjectID().Hex() + + t.Run("查詢成功", func(t *testing.T) { + expected := &entity.Tags{ + ID: primitive.NewObjectID(), + Name: "Test Tag", + Types: product.ItemTypeProduct, + ShowType: product.ShowTypeNormal, + } + mockTagsRepo.EXPECT().GetByID(ctx, id).Return(expected, nil) + + tag, err := useCase.GetByID(ctx, id) + assert.NoError(t, err) + assert.Equal(t, expected, tag) + }) + + t.Run("查詢失敗 - DB 錯誤", func(t *testing.T) { + mockTagsRepo.EXPECT().GetByID(ctx, id).Return(nil, errors.New("db error")) + + tag, err := useCase.GetByID(ctx, id) + assert.Error(t, err) + assert.Nil(t, tag) + assert.Contains(t, err.Error(), "failed to get tags") + }) +} + +func TestTagsUseCase_GetByIDs(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTagsRepo := mockRepository.NewMockTagRepo(mockCtrl) + + useCase := MustTagsUseCase(TagsUseCaseParam{ + TagsRepo: mockTagsRepo, + }) + + ctx := context.Background() + ids := []string{"id1", "id2"} + + t.Run("查詢成功", func(t *testing.T) { + expected := []*entity.Tags{ + {Name: "Tag1", Types: product.ItemTypeProduct, ShowType: product.ShowTypeNormal}, + {Name: "Tag2", Types: product.ItemTypeProduct, ShowType: product.ShowTypeNormal}, + } + mockTagsRepo.EXPECT().GetByIDs(ctx, ids).Return(expected, nil) + + tags, err := useCase.GetByIDs(ctx, ids) + assert.NoError(t, err) + assert.Equal(t, expected, tags) + }) + + t.Run("查詢失敗 - DB 錯誤", func(t *testing.T) { + mockTagsRepo.EXPECT().GetByIDs(ctx, ids).Return(nil, errors.New("db error")) + + tags, err := useCase.GetByIDs(ctx, ids) + assert.Error(t, err) + assert.Nil(t, tags) + assert.Contains(t, err.Error(), "failed to get tags") + }) +} + +func TestTagsUseCase_Delete(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTagsRepo := mockRepository.NewMockTagRepo(mockCtrl) + + useCase := MustTagsUseCase(TagsUseCaseParam{ + TagsRepo: mockTagsRepo, + }) + + ctx := context.Background() + id := primitive.NewObjectID().Hex() + + t.Run("刪除成功", func(t *testing.T) { + mockTagsRepo.EXPECT().Delete(ctx, id).Return(nil) + err := useCase.Delete(ctx, id) + assert.NoError(t, err) + }) + + t.Run("刪除失敗 - DB 錯誤", func(t *testing.T) { + mockTagsRepo.EXPECT().Delete(ctx, id).Return(errors.New("db error")) + err := useCase.Delete(ctx, id) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to delete tags") + }) +} + +func TestTagsUseCase_List(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTagsRepo := mockRepository.NewMockTagRepo(mockCtrl) + useCase := MustTagsUseCase(TagsUseCaseParam{ + TagsRepo: mockTagsRepo, + }) + + ctx := context.Background() + params := usecase.TagQueryParams{ + PageSize: 10, + PageIndex: 1, + Types: ptr(product.ItemTypeProduct), + Name: ptr("AI"), + ShowType: ptr(product.ShowTypeNormal), + } + + t.Run("查詢成功", func(t *testing.T) { + expected := []*entity.Tags{ + {Name: "AI", Types: product.ItemTypeProduct, ShowType: product.ShowTypeNormal}, + } + mockTagsRepo.EXPECT().List(ctx, repo.TagQueryParams{ + PageSize: 10, + PageIndex: 1, + Types: ptr(product.ItemTypeProduct), + Name: ptr("AI"), + ShowType: ptr(product.ShowTypeNormal), + }).Return(expected, int64(1), nil) + + res, total, err := useCase.List(ctx, params) + assert.NoError(t, err) + assert.Equal(t, expected, res) + assert.Equal(t, int64(1), total) + }) + + t.Run("查詢失敗 - DB 錯誤", func(t *testing.T) { + dbErr := errors.New("db error") + mockTagsRepo.EXPECT().List(ctx, repo.TagQueryParams{ + PageSize: 10, + PageIndex: 1, + Types: ptr(product.ItemTypeProduct), + Name: ptr("AI"), + ShowType: ptr(product.ShowTypeNormal), + }).Return(nil, int64(0), dbErr) + + res, total, err := useCase.List(ctx, params) + assert.Error(t, err) + assert.Nil(t, res) + assert.Equal(t, int64(0), total) + assert.Contains(t, err.Error(), "failed to list tags") + }) +} + +func TestTagsUseCase_Update(t *testing.T) { + mockCtrl := gomock.NewController(t) + defer mockCtrl.Finish() + + mockTagsRepo := mockRepository.NewMockTagRepo(mockCtrl) + useCase := MustTagsUseCase(TagsUseCaseParam{ + TagsRepo: mockTagsRepo, + }) + + ctx := context.Background() + id := primitive.NewObjectID().Hex() + tagParams := usecase.TagModifyParams{ + Name: ptr("New Name"), + Types: ptr(product.ItemTypeProduct), + ShowType: ptr(product.ShowTypeNormal), + Cover: ptr("https://image.jpg"), + } + + t.Run("更新成功", func(t *testing.T) { + mockTagsRepo.EXPECT().Update(ctx, id, repo.TagModifyParams{ + Name: tagParams.Name, + Types: tagParams.Types, + ShowType: tagParams.ShowType, + Cover: tagParams.Cover, + }).Return(nil) + err := useCase.Update(ctx, id, tagParams) + assert.NoError(t, err) + }) + + t.Run("更新失敗 - DB 錯誤", func(t *testing.T) { + dbErr := errors.New("db error") + mockTagsRepo.EXPECT().Update(ctx, id, repo.TagModifyParams{ + Name: tagParams.Name, + Types: tagParams.Types, + ShowType: tagParams.ShowType, + Cover: tagParams.Cover, + }).Return(dbErr) + err := useCase.Update(ctx, id, tagParams) + assert.Error(t, err) + assert.Contains(t, err.Error(), "failed to update tags") + }) +}