2025-03-17 02:08:22 +00:00
|
|
|
package repository
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"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/zeromicro/go-zero/core/stores/cache"
|
|
|
|
"github.com/zeromicro/go-zero/core/stores/redis"
|
|
|
|
"go.mongodb.org/mongo-driver/bson/primitive"
|
|
|
|
"go.mongodb.org/mongo-driver/mongo"
|
|
|
|
)
|
|
|
|
|
|
|
|
func SetupTestProductRepository(db string) (repository.ProductRepository, 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 := ProductRepositoryParam{
|
|
|
|
Conf: conf,
|
|
|
|
CacheConf: cacheConf,
|
|
|
|
CacheOpts: cacheOpts,
|
|
|
|
}
|
|
|
|
|
|
|
|
repo := NewProductRepository(param)
|
2025-03-19 06:45:44 +00:00
|
|
|
_, _ = repo.Index20250317001UP(context.Background())
|
2025-03-17 02:08:22 +00:00
|
|
|
|
|
|
|
return repo, tearDown, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestListProduct(t *testing.T) {
|
|
|
|
model, tearDown, err := SetupTestProductRepository("testDB")
|
|
|
|
defer tearDown()
|
2025-03-25 07:58:02 +00:00
|
|
|
fmt.Println("ddddddddddddddddddddd", err.Error())
|
2025-03-17 02:08:22 +00:00
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
products := []*entity.Product{
|
|
|
|
{
|
|
|
|
UID: "user1",
|
|
|
|
Title: "Product 1",
|
|
|
|
IsPublished: true,
|
|
|
|
Amount: 100,
|
|
|
|
StartTime: ptr(now.Add(-24 * time.Hour).Unix()),
|
|
|
|
EndTime: ptr(now.Add(24 * time.Hour).Unix()),
|
|
|
|
Category: "tech",
|
|
|
|
CreatedAt: now.Unix(),
|
|
|
|
UpdatedAt: now.Unix(),
|
|
|
|
Slug: ptr("product-1"),
|
|
|
|
Details: ptr("details..."),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
UID: "user2",
|
|
|
|
Title: "Product 2",
|
|
|
|
IsPublished: false,
|
|
|
|
Amount: 200,
|
|
|
|
StartTime: ptr(now.Add(-48 * time.Hour).Unix()),
|
|
|
|
EndTime: ptr(now.Add(48 * time.Hour).Unix()),
|
|
|
|
Category: "health",
|
|
|
|
CreatedAt: now.Unix(),
|
|
|
|
UpdatedAt: now.Unix(),
|
|
|
|
Slug: ptr("product-2"),
|
|
|
|
Details: ptr("details..."),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
UID: "user1",
|
|
|
|
Title: "Product 3",
|
|
|
|
IsPublished: true,
|
|
|
|
Amount: 300,
|
|
|
|
StartTime: ptr(now.Add(-72 * time.Hour).Unix()),
|
|
|
|
EndTime: ptr(now.Add(72 * time.Hour).Unix()),
|
|
|
|
Category: "tech",
|
|
|
|
CreatedAt: now.Unix(),
|
|
|
|
UpdatedAt: now.Unix(),
|
|
|
|
Slug: ptr("product-3"),
|
|
|
|
Details: ptr("details..."),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, p := range products {
|
|
|
|
err = model.Insert(context.Background(), p)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
params *repository.ProductQueryParams
|
|
|
|
expectCount int64
|
|
|
|
expectIDs []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Filter by UID",
|
|
|
|
params: &repository.ProductQueryParams{
|
|
|
|
UID: ptr("user1"),
|
|
|
|
PageSize: 10,
|
|
|
|
PageIndex: 1,
|
|
|
|
},
|
|
|
|
expectCount: 2,
|
|
|
|
expectIDs: []string{products[2].ID.Hex(), products[0].ID.Hex()},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Filter by Published",
|
|
|
|
params: &repository.ProductQueryParams{
|
|
|
|
IsPublished: ptr(true),
|
|
|
|
PageSize: 10,
|
|
|
|
PageIndex: 1,
|
|
|
|
},
|
|
|
|
expectCount: 2,
|
|
|
|
expectIDs: []string{products[2].ID.Hex(), products[0].ID.Hex()},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Filter by Category",
|
|
|
|
params: &repository.ProductQueryParams{
|
|
|
|
Category: ptr("tech"),
|
|
|
|
PageSize: 10,
|
|
|
|
PageIndex: 1,
|
|
|
|
},
|
|
|
|
expectCount: 2,
|
|
|
|
expectIDs: []string{products[2].ID.Hex(), products[0].ID.Hex()},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Filter by StartTime >= now - 36h (should get P1 & P2)",
|
|
|
|
params: &repository.ProductQueryParams{
|
|
|
|
StartTime: ptr(now.Add(-36 * time.Hour).Unix()),
|
|
|
|
PageSize: 10,
|
|
|
|
PageIndex: 1,
|
|
|
|
},
|
|
|
|
expectCount: 1,
|
|
|
|
expectIDs: []string{products[0].ID.Hex()},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Filter by EndTime <= now + 30h (should get P1)",
|
|
|
|
params: &repository.ProductQueryParams{
|
|
|
|
EndTime: ptr(now.Add(30 * time.Hour).Unix()),
|
|
|
|
PageSize: 10,
|
|
|
|
PageIndex: 1,
|
|
|
|
},
|
|
|
|
expectCount: 1,
|
|
|
|
expectIDs: []string{products[0].ID.Hex()},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Filter by Slug = product-2",
|
|
|
|
params: &repository.ProductQueryParams{
|
|
|
|
Slug: ptr("product-2"),
|
|
|
|
PageSize: 10,
|
|
|
|
PageIndex: 1,
|
|
|
|
},
|
|
|
|
expectCount: 1,
|
|
|
|
expectIDs: []string{products[1].ID.Hex()},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Filter by UID + Category + Published",
|
|
|
|
params: &repository.ProductQueryParams{
|
|
|
|
UID: ptr("user1"),
|
|
|
|
Category: ptr("tech"),
|
|
|
|
IsPublished: ptr(true),
|
|
|
|
PageSize: 10,
|
|
|
|
PageIndex: 1,
|
|
|
|
},
|
|
|
|
expectCount: 2,
|
|
|
|
expectIDs: []string{products[2].ID.Hex(), products[0].ID.Hex()},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
result, count, err := model.ListProduct(context.Background(), tt.params)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, tt.expectCount, count)
|
|
|
|
|
|
|
|
var resultIDs []string
|
|
|
|
for _, r := range result {
|
|
|
|
resultIDs = append(resultIDs, r.ID.Hex())
|
|
|
|
}
|
|
|
|
assert.Equal(t, tt.expectIDs, resultIDs)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestFindOneBySlug(t *testing.T) {
|
|
|
|
model, tearDown, err := SetupTestProductRepository("testDB")
|
|
|
|
defer tearDown()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
products := []*entity.Product{
|
|
|
|
{
|
|
|
|
UID: "user1",
|
|
|
|
Title: "Product 1",
|
|
|
|
IsPublished: true,
|
|
|
|
Slug: ptr("product-1"),
|
|
|
|
CreatedAt: now.Unix(),
|
|
|
|
UpdatedAt: now.Unix(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
UID: "user2",
|
|
|
|
Title: "Product 2",
|
|
|
|
IsPublished: true,
|
|
|
|
Slug: ptr("product-2"),
|
|
|
|
CreatedAt: now.Unix(),
|
|
|
|
UpdatedAt: now.Unix(),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
// 插入測試資料
|
|
|
|
for _, p := range products {
|
|
|
|
err = model.Insert(context.Background(), p)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
slug string
|
|
|
|
expectFound bool
|
|
|
|
expectID string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Found: product-1",
|
|
|
|
slug: "product-1",
|
|
|
|
expectFound: true,
|
|
|
|
expectID: products[0].ID.Hex(),
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Not Found: unknown-slug",
|
|
|
|
slug: "unknown-slug",
|
|
|
|
expectFound: false,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
res, err := model.FindOneBySlug(context.Background(), tt.slug)
|
|
|
|
|
|
|
|
if tt.expectFound {
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.NotNil(t, res)
|
|
|
|
assert.Equal(t, tt.expectID, res.ID.Hex())
|
|
|
|
} else {
|
|
|
|
assert.Nil(t, res)
|
|
|
|
assert.ErrorIs(t, err, ErrNotFound)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestDeleteProduct(t *testing.T) {
|
|
|
|
model, tearDown, err := SetupTestProductRepository("testDB")
|
|
|
|
defer tearDown()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// 建立測試資料
|
|
|
|
now := time.Now()
|
|
|
|
product := &entity.Product{
|
|
|
|
UID: "user1",
|
|
|
|
Title: "Deletable Product",
|
|
|
|
IsPublished: true,
|
|
|
|
Slug: ptr("delete-slug"),
|
|
|
|
CreatedAt: now.Unix(),
|
|
|
|
UpdatedAt: now.Unix(),
|
|
|
|
}
|
|
|
|
err = model.Insert(context.Background(), product)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
t.Run("Delete existing product", func(t *testing.T) {
|
|
|
|
err := model.Delete(context.Background(), product.ID.Hex())
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
// 再查詢應該會是找不到
|
|
|
|
_, err = model.FindOneByID(context.Background(), product.ID.Hex())
|
|
|
|
assert.ErrorIs(t, err, ErrNotFound)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("Delete non-existing product", func(t *testing.T) {
|
|
|
|
invalidID := primitive.NewObjectID().Hex()
|
|
|
|
err := model.Delete(context.Background(), invalidID)
|
|
|
|
assert.ErrorIs(t, err, ErrNotFound)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestUpdateProduct(t *testing.T) {
|
|
|
|
model, tearDown, err := SetupTestProductRepository("testDB")
|
|
|
|
defer tearDown()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
product := &entity.Product{
|
|
|
|
UID: "user1",
|
|
|
|
Title: "Original Title",
|
|
|
|
IsPublished: false,
|
|
|
|
Slug: ptr("original-slug"),
|
|
|
|
CreatedAt: now.UnixNano(),
|
|
|
|
UpdatedAt: now.UnixNano(),
|
|
|
|
}
|
|
|
|
err = model.Insert(context.Background(), product)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
update *repository.ProductUpdateParams
|
|
|
|
validate func(t *testing.T, updated *entity.Product)
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Update title",
|
|
|
|
update: &repository.ProductUpdateParams{
|
|
|
|
Title: ptr("New Title"),
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, updated *entity.Product) {
|
|
|
|
assert.Equal(t, "New Title", updated.Title)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Update is_published",
|
|
|
|
update: &repository.ProductUpdateParams{
|
|
|
|
IsPublished: ptr(true),
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, updated *entity.Product) {
|
|
|
|
assert.True(t, updated.IsPublished)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Update start and end time",
|
|
|
|
update: &repository.ProductUpdateParams{
|
|
|
|
StartTime: ptr(now.Add(1 * time.Hour).UnixNano()),
|
|
|
|
EndTime: ptr(now.Add(2 * time.Hour).UnixNano()),
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, updated *entity.Product) {
|
|
|
|
assert.Equal(t, now.Add(1*time.Hour).UnixNano(), *updated.StartTime)
|
|
|
|
assert.Equal(t, now.Add(2*time.Hour).UnixNano(), *updated.EndTime)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Update short description",
|
|
|
|
update: &repository.ProductUpdateParams{
|
|
|
|
ShortDescription: "Short desc here",
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, updated *entity.Product) {
|
|
|
|
assert.Equal(t, "Short desc here", updated.ShortDescription)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Update details, short_title, slug",
|
|
|
|
update: &repository.ProductUpdateParams{
|
|
|
|
Details: ptr("Updated details"),
|
|
|
|
ShortTitle: ptr("ShortT"),
|
|
|
|
Slug: ptr("new-slug"),
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, updated *entity.Product) {
|
|
|
|
assert.Equal(t, "Updated details", *updated.Details)
|
|
|
|
assert.Equal(t, "ShortT", *updated.ShortTitle)
|
|
|
|
assert.Equal(t, "new-slug", *updated.Slug)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Update amount",
|
|
|
|
update: &repository.ProductUpdateParams{
|
|
|
|
Amount: ptr(uint64(500)),
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, updated *entity.Product) {
|
|
|
|
assert.Equal(t, uint64(500), updated.Amount)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Update media",
|
|
|
|
update: &repository.ProductUpdateParams{
|
|
|
|
Media: []entity.Media{
|
|
|
|
{Sort: 1, Type: "image", URL: "https://example.com/1.jpg"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, updated *entity.Product) {
|
|
|
|
assert.Len(t, updated.Media, 1)
|
|
|
|
assert.Equal(t, "image", updated.Media[0].Type)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Update custom fields",
|
|
|
|
update: &repository.ProductUpdateParams{
|
|
|
|
CustomFields: []entity.CustomFields{
|
|
|
|
{Key: "color", Value: "red"},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
validate: func(t *testing.T, updated *entity.Product) {
|
|
|
|
assert.Len(t, updated.CustomFields, 1)
|
|
|
|
assert.Equal(t, "color", updated.CustomFields[0].Key)
|
|
|
|
assert.Equal(t, "red", updated.CustomFields[0].Value)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
_, err := model.Update(context.Background(), product.ID.Hex(), tt.update)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
// 重新查詢確認資料已更新
|
|
|
|
updated, err := model.FindOneByID(context.Background(), product.ID.Hex())
|
|
|
|
assert.NoError(t, err)
|
|
|
|
tt.validate(t, updated)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestProductRepository_Transaction(t *testing.T) {
|
|
|
|
model, tearDown, err := SetupTestProductRepository("testDB")
|
|
|
|
defer tearDown()
|
|
|
|
assert.NoError(t, err)
|
|
|
|
ctx := context.Background()
|
|
|
|
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
transactionFunc func(sessCtx mongo.SessionContext) (any, error)
|
|
|
|
expectError bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "Successful Transaction",
|
|
|
|
transactionFunc: func(sessCtx mongo.SessionContext) (any, error) {
|
|
|
|
product := &entity.Product{
|
|
|
|
UID: "user123",
|
|
|
|
Title: "Test Transaction Product",
|
|
|
|
Amount: 1000,
|
|
|
|
IsPublished: true,
|
|
|
|
}
|
|
|
|
err := model.Insert(ctx, product)
|
|
|
|
return product, err
|
|
|
|
},
|
|
|
|
expectError: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "Transaction Rollback on Error",
|
|
|
|
transactionFunc: func(sessCtx mongo.SessionContext) (any, error) {
|
|
|
|
product := &entity.Product{
|
|
|
|
UID: "user123",
|
|
|
|
Title: "Rollback Test Product",
|
|
|
|
Amount: 5000,
|
|
|
|
IsPublished: true,
|
|
|
|
}
|
|
|
|
_ = model.Insert(ctx, product)
|
|
|
|
// 模擬交易內錯誤
|
|
|
|
return nil, errors.New("forced error")
|
|
|
|
},
|
|
|
|
expectError: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
|
|
err := model.Transaction(ctx, tt.transactionFunc)
|
|
|
|
if tt.expectError {
|
|
|
|
assert.Error(t, err, "交易應該返回錯誤")
|
|
|
|
} else {
|
|
|
|
assert.NoError(t, err, "交易應該成功完成")
|
|
|
|
product, _, err := model.ListProduct(ctx,
|
|
|
|
&repository.ProductQueryParams{
|
|
|
|
PageSize: 20, PageIndex: 1,
|
|
|
|
UID: ptr("user123")})
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, product[0].UID, "user123")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func ptr[T any](v T) *T {
|
|
|
|
return &v
|
|
|
|
}
|