package usecase import ( "context" "errors" "testing" "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" "github.com/shopspring/decimal" "github.com/stretchr/testify/assert" "go.mongodb.org/mongo-driver/bson/primitive" "go.uber.org/mock/gomock" ) 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") }) }