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

564 lines
15 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package repository
import (
"context"
"fmt"
"testing"
"time"
"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)
}
}
})
}
}