app-cloudep-product-service/pkg/repository/product_statistics_test.go

564 lines
15 KiB
Go
Raw Permalink Normal View History

2025-03-19 11:28:07 +00:00
package repository
import (
2025-03-20 10:44:01 +00:00
"context"
"fmt"
"testing"
"time"
2025-03-19 11:28:07 +00:00
"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"
"github.com/alicebob/miniredis/v2"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zeromicro/go-zero/core/stores/cache"
"github.com/zeromicro/go-zero/core/stores/mon"
"github.com/zeromicro/go-zero/core/stores/redis"
)
func SetupTestProductStatisticsRepo(db string) (repository.ProductStatisticsRepo, 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 := ProductStatisticsRepositoryParam{
Conf: conf,
CacheConf: cacheConf,
CacheOpts: cacheOpts,
DBOpts: []mon.Option{
mgo.SetCustomDecimalType(),
mgo.InitMongoOptions(*conf),
},
}
repo := NewProductStatisticsRepository(param)
_, _ = repo.Index20250317001UP(context.Background())
return repo, tearDown, nil
}
func TestCreateProductStatistics(t *testing.T) {
// 假設有 SetupTestProductStatisticsRepository 可用來建立測試用的 ProductStatisticsRepository
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 定義多筆測試資料(不包含 ID、CreatedAt、UpdatedAt由 Create 自動填入)
statsList := []*entity.ProductStatistics{
{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
},
{
ProductID: "prod-002",
Orders: 50,
AverageRating: 3.8,
FansCount: 150,
},
}
// 逐筆呼叫 Create 新增資料
for _, ps := range statsList {
err := repo.Create(ctx, ps)
assert.NoError(t, err, "Create should not return error")
}
// 驗證每筆資料的自動欄位與內容
for _, ps := range statsList {
// 檢查 ID 與時間欄位是否有自動填入
assert.False(t, ps.ID.IsZero(), "ID should be generated")
assert.NotZero(t, ps.CreatedAt, "CreatedAt should be set")
assert.NotZero(t, ps.UpdatedAt, "UpdatedAt should be set")
// 查詢 DB 確認資料是否存在且欄位值正確
result, err := repo.GetByID(ctx, ps.ID.Hex())
assert.NoError(t, err, "GetByID should not return error")
assert.Equal(t, ps.ProductID, result.ProductID, "ProductID should match")
assert.Equal(t, ps.Orders, result.Orders, "Orders should match")
assert.Equal(t, ps.AverageRating, result.AverageRating, "AverageRating should match")
assert.Equal(t, ps.FansCount, result.FansCount, "FansCount should match")
}
}
func TestGetProductStatisticsByProductID(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
stats1 := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
stats2 := &entity.ProductStatistics{
ProductID: "prod-002",
Orders: 50,
AverageRating: 3.8,
FansCount: 150,
}
err = repo.Create(ctx, stats1)
require.NoError(t, err)
err = repo.Create(ctx, stats2)
require.NoError(t, err)
// 定義 table-driven 測試案例
tests := []struct {
name string
inputProductID string
expectedErr error
expectedStatistics *entity.ProductStatistics
}{
{
name: "record exists - prod-001",
inputProductID: "prod-001",
expectedErr: nil,
expectedStatistics: stats1,
},
{
name: "record exists - prod-002",
inputProductID: "prod-002",
expectedErr: nil,
expectedStatistics: stats2,
},
{
name: "record not found",
inputProductID: "non-existent",
expectedErr: ErrNotFound,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
result, err := repo.GetByProductID(ctx, tt.inputProductID)
if tt.expectedErr != nil {
require.Error(t, err)
assert.Equal(t, tt.expectedErr, err)
assert.Nil(t, result)
} else {
require.NoError(t, err)
// 驗證回傳的資料是否符合預期
assert.Equal(t, tt.expectedStatistics.ProductID, result.ProductID)
assert.Equal(t, tt.expectedStatistics.Orders, result.Orders)
assert.Equal(t, tt.expectedStatistics.AverageRating, result.AverageRating)
assert.Equal(t, tt.expectedStatistics.FansCount, result.FansCount)
// 其他自動產生欄位如 ID、CreatedAt、UpdatedAt 也可檢查非零
assert.False(t, result.ID.IsZero(), "ID should be generated")
assert.NotZero(t, result.CreatedAt, "CreatedAt should be set")
assert.NotZero(t, result.UpdatedAt, "UpdatedAt should be set")
}
})
}
}
func TestIncOrders(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料:初始 Orders 為 100
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
productID string
increment int64
expectedOrders uint64 // 預期的 Orders 值
expectErr bool
}{
{
name: "Valid increment",
productID: "prod-001",
increment: 10,
expectedOrders: 110,
expectErr: false,
},
{
name: "Non-existent product",
productID: "prod-not-exist",
increment: 5,
// 當產品不存在時,應回傳錯誤,不檢查 Orders
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.IncOrders(ctx, tt.productID, tt.increment)
if tt.expectErr {
require.Error(t, err)
// 可進一步檢查錯誤訊息中是否包含特定關鍵字
assert.Contains(t, err.Error(), "failed to get product_id by id")
} else {
require.NoError(t, err)
// 若成功,利用 GetByProductID 取得最新資料,檢查 Orders 是否正確更新
ps, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedOrders, ps.Orders)
}
})
}
}
func TestDecOrders(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料,初始 Orders 為 100
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
productID string
decrement int64
expectedOrders uint64 // 減少成功後預期的 Orders 值
expectErr bool
}{
{
name: "Valid decrease",
productID: "prod-001",
decrement: 30,
expectedOrders: 70, // 100 - 30
expectErr: false,
},
{
name: "Insufficient orders",
productID: "prod-001",
decrement: 150, // 超過現有數量
expectedOrders: 70, // 100 - 30
expectErr: false,
},
{
name: "Non-existent product",
productID: "prod-not-exist",
decrement: 10,
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.DecOrders(ctx, tt.productID, tt.decrement)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 成功減少後,利用 GetByProductID 取得最新資料
updated, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedOrders, updated.Orders)
}
})
}
}
func TestUpdateAverageRating(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料,初始 AverageRating 為 4.0
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.0,
FansCount: 50,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
productID string
newRating float64
expectErr bool
expectedValue float64 // 預期更新後的 AverageRating
}{
{
name: "Update existing product",
productID: "prod-001",
newRating: 4.8,
expectErr: false,
expectedValue: 4.8,
},
{
name: "Non-existent product",
productID: "prod-nonexist",
newRating: 3.5,
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.UpdateAverageRating(ctx, tt.productID, tt.newRating)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 取得更新後的資料
updated, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedValue, updated.AverageRating, "AverageRating should be updated")
// 驗證更新時間不為 0
assert.NotZero(t, updated.AverageRatingUpdateTime, "AverageRatingUpdateTime should be set")
assert.NotZero(t, updated.UpdatedAt, "UpdatedAt should be set")
}
})
}
}
func TestIncFansCount(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料,初始 FansCount 為 200
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.0,
FansCount: 200,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
productID string
incCount uint64
expectedCount uint64 // 預期更新後的 FansCount
expectErr bool
}{
{
name: "Valid increment",
productID: "prod-001",
incCount: 50,
expectedCount: 250, // 200 + 50
expectErr: false,
},
{
name: "Non-existent product",
productID: "non-existent",
incCount: 10,
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.IncFansCount(ctx, tt.productID, tt.incCount)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 取得更新後的資料,驗證 FansCount 是否正確更新
updated, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedCount, updated.FansCount, "FansCount should be incremented correctly")
assert.NotZero(t, updated.FansCountUpdateTime, "FansCountUpdateTime should be set")
assert.NotZero(t, updated.UpdatedAt, "UpdatedAt should be set")
}
})
}
}
func TestDecFansCount(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 先建立兩筆測試資料
// 測試 valid case初始 FansCount 為 200
statsValid := &entity.ProductStatistics{
ProductID: "prod-valid",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
err = repo.Create(ctx, statsValid)
require.NoError(t, err)
// 測試 insufficient case初始 FansCount 為 30
statsInsufficient := &entity.ProductStatistics{
ProductID: "prod-insufficient",
Orders: 50,
AverageRating: 3.8,
FansCount: 30,
}
err = repo.Create(ctx, statsInsufficient)
require.NoError(t, err)
tests := []struct {
name string
productID string
decrement uint64
expectedFans uint64 // 預期更新後的 FansCount (僅 valid case)
expectErr bool
}{
{
name: "Valid decrement",
productID: "prod-valid",
decrement: 50,
expectedFans: 150, // 200 - 50
expectErr: false,
},
{
name: "Insufficient fans",
productID: "prod-insufficient",
decrement: 50,
expectedFans: 30, // 扣超過就跟原本一樣不會變
expectErr: false,
},
{
name: "Non-existent product",
productID: "prod-nonexistent",
decrement: 10,
expectErr: true,
},
}
for _, tt := range tests {
tt := tt // capture range variable
t.Run(tt.name, func(t *testing.T) {
err := repo.DecFansCount(ctx, tt.productID, tt.decrement)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
// 取得更新後的資料,驗證 FansCount 是否正確更新
updated, err := repo.GetByProductID(ctx, tt.productID)
require.NoError(t, err)
assert.Equal(t, tt.expectedFans, updated.FansCount, "FansCount should be decremented correctly")
}
})
}
}
func TestDeleteProductStatistics(t *testing.T) {
repo, tearDown, err := SetupTestProductStatisticsRepo("testDB")
require.NoError(t, err)
defer tearDown()
ctx := context.Background()
// 插入一筆測試資料
stats := &entity.ProductStatistics{
ProductID: "prod-001",
Orders: 100,
AverageRating: 4.5,
FansCount: 200,
}
err = repo.Create(ctx, stats)
require.NoError(t, err)
tests := []struct {
name string
id string
expectErr error // 預期錯誤
checkDelete bool // 若為 true刪除成功後進行資料查詢驗證
}{
{
name: "Delete existing record",
id: stats.ID.Hex(),
expectErr: nil,
checkDelete: true,
},
{
name: "Invalid ObjectID format",
id: "invalid-id",
expectErr: ErrInvalidObjectID,
checkDelete: false,
},
}
for _, tc := range tests {
tc := tc // capture range variable
t.Run(tc.name, func(t *testing.T) {
err := repo.Delete(ctx, tc.id)
if tc.expectErr != nil {
require.Error(t, err)
assert.Equal(t, tc.expectErr, err)
} else {
require.NoError(t, err)
if tc.checkDelete {
// 刪除成功後,透過 GetByID 應查無資料
_, err := repo.GetByID(ctx, tc.id)
require.Error(t, err)
assert.Equal(t, ErrNotFound, err)
}
}
})
}
}