Files
junhong_cmp_fiber/internal/service/package/service_test.go
huang 1da680a790
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m18s
重构: 店铺套餐分配系统从加价模式改为返佣模式
主要变更:
- 重构分配模型:从加价模式(pricing_mode/pricing_value)改为返佣模式(base_commission + tier_commission)
- 删除独立的 my_package 接口,统一到 /api/admin/packages(通过数据权限自动过滤)
- 新增批量分配和批量调价功能,支持事务和性能优化
- 新增配置版本管理,订单创建时锁定返佣配置
- 新增成本价历史记录,支持审计和纠纷处理
- 新增统计缓存系统(Redis + 异步任务),优化梯度返佣计算性能
- 删除冗余的梯度佣金独立 CRUD 接口(合并到分配配置中)
- 归档 3 个已完成的 OpenSpec changes 并同步 8 个新 capabilities 到 main specs

技术细节:
- 数据库迁移:000026_refactor_shop_package_allocation
- 新增 Store:AllocationConfigStore, PriceHistoryStore, CommissionStatsStore
- 新增 Service:BatchAllocationService, BatchPricingService, CommissionStatsService
- 新增异步任务:统计更新、定时同步、周期归档
- 测试覆盖:批量操作集成测试、梯度佣金 CRUD 清理验证

影响:
- API 变更:删除 4 个梯度 CRUD 接口(POST/GET/PUT/DELETE /:id/tiers)
- API 新增:批量分配、批量调价接口
- 数据模型:重构 shop_series_allocation 表结构
- 性能优化:批量操作使用 CreateInBatches,统计使用 Redis 缓存

相关文档:
- openspec/changes/archive/2026-01-28-refactor-shop-package-allocation/
- openspec/specs/agent-available-packages/
- openspec/specs/allocation-config-versioning/
- 等 8 个新 capability specs
2026-01-28 17:11:55 +08:00

590 lines
16 KiB
Go

package packagepkg
import (
"context"
"fmt"
"testing"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/store/postgres"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/middleware"
"github.com/break/junhong_cmp_fiber/tests/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func generateUniquePackageCode(prefix string) string {
return fmt.Sprintf("%s_%d", prefix, time.Now().UnixNano())
}
func TestPackageService_Create(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypePlatform,
})
t.Run("创建成功", func(t *testing.T) {
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_CREATE"),
PackageName: "创建测试套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
resp, err := svc.Create(ctx, req)
require.NoError(t, err)
assert.NotZero(t, resp.ID)
assert.Equal(t, req.PackageCode, resp.PackageCode)
assert.Equal(t, req.PackageName, resp.PackageName)
assert.Equal(t, constants.StatusEnabled, resp.Status)
assert.Equal(t, 2, resp.ShelfStatus)
})
t.Run("编码重复失败", func(t *testing.T) {
code := generateUniquePackageCode("PKG_DUP")
req1 := &dto.CreatePackageRequest{
PackageCode: code,
PackageName: "第一个套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
_, err := svc.Create(ctx, req1)
require.NoError(t, err)
req2 := &dto.CreatePackageRequest{
PackageCode: code,
PackageName: "第二个套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
_, err = svc.Create(ctx, req2)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeConflict, appErr.Code)
})
t.Run("系列不存在失败", func(t *testing.T) {
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_SERIES"),
PackageName: "系列测试套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
SeriesID: func() *uint { id := uint(99999); return &id }(),
}
_, err := svc.Create(ctx, req)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeNotFound, appErr.Code)
})
}
func TestPackageService_UpdateStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypePlatform,
})
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_STATUS"),
PackageName: "状态测试套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
t.Run("禁用套餐时自动强制下架", func(t *testing.T) {
err := svc.UpdateShelfStatus(ctx, created.ID, 1)
require.NoError(t, err)
pkg, err := svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, 1, pkg.ShelfStatus)
err = svc.UpdateStatus(ctx, created.ID, constants.StatusDisabled)
require.NoError(t, err)
pkg, err = svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, constants.StatusDisabled, pkg.Status)
assert.Equal(t, 2, pkg.ShelfStatus)
})
t.Run("启用套餐时保持原上架状态", func(t *testing.T) {
req2 := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_ENABLE"),
PackageName: "启用测试套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created2, err := svc.Create(ctx, req2)
require.NoError(t, err)
err = svc.UpdateShelfStatus(ctx, created2.ID, 1)
require.NoError(t, err)
err = svc.UpdateStatus(ctx, created2.ID, constants.StatusDisabled)
require.NoError(t, err)
pkg, err := svc.Get(ctx, created2.ID)
require.NoError(t, err)
assert.Equal(t, constants.StatusDisabled, pkg.Status)
assert.Equal(t, 2, pkg.ShelfStatus)
err = svc.UpdateStatus(ctx, created2.ID, constants.StatusEnabled)
require.NoError(t, err)
pkg, err = svc.Get(ctx, created2.ID)
require.NoError(t, err)
assert.Equal(t, constants.StatusEnabled, pkg.Status)
assert.Equal(t, 2, pkg.ShelfStatus)
})
}
func TestPackageService_UpdateShelfStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypePlatform,
})
t.Run("启用状态的套餐可以上架", func(t *testing.T) {
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_SHELF_ENABLE"),
PackageName: "上架测试-启用",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
pkg, err := svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, 1, pkg.Status)
assert.Equal(t, 2, pkg.ShelfStatus)
err = svc.UpdateShelfStatus(ctx, created.ID, 1)
require.NoError(t, err)
pkg, err = svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, 1, pkg.ShelfStatus)
})
t.Run("禁用状态的套餐不能上架", func(t *testing.T) {
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_SHELF_DISABLE"),
PackageName: "上架测试-禁用",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
err = svc.UpdateStatus(ctx, created.ID, constants.StatusDisabled)
require.NoError(t, err)
err = svc.UpdateShelfStatus(ctx, created.ID, 1)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeInvalidStatus, appErr.Code)
pkg, err := svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, 2, pkg.ShelfStatus)
})
t.Run("下架成功", func(t *testing.T) {
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_SHELF_OFF"),
PackageName: "下架测试",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
err = svc.UpdateShelfStatus(ctx, created.ID, 1)
require.NoError(t, err)
pkg, err := svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, 1, pkg.ShelfStatus)
err = svc.UpdateShelfStatus(ctx, created.ID, 2)
require.NoError(t, err)
pkg, err = svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, 2, pkg.ShelfStatus)
})
}
func TestPackageService_Get(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypePlatform,
})
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_GET"),
PackageName: "查询测试套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
t.Run("获取成功", func(t *testing.T) {
resp, err := svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, created.PackageCode, resp.PackageCode)
assert.Equal(t, created.PackageName, resp.PackageName)
assert.Equal(t, created.ID, resp.ID)
})
t.Run("不存在返回错误", func(t *testing.T) {
_, err := svc.Get(ctx, 99999)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeNotFound, appErr.Code)
})
}
func TestPackageService_Update(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypePlatform,
})
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_UPDATE"),
PackageName: "更新测试套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
t.Run("更新成功", func(t *testing.T) {
newName := "更新后的套餐名称"
newPrice := int64(19900)
updateReq := &dto.UpdatePackageRequest{
PackageName: &newName,
Price: &newPrice,
}
resp, err := svc.Update(ctx, created.ID, updateReq)
require.NoError(t, err)
assert.Equal(t, newName, resp.PackageName)
assert.Equal(t, newPrice, resp.Price)
})
t.Run("更新不存在的套餐", func(t *testing.T) {
newName := "test"
updateReq := &dto.UpdatePackageRequest{
PackageName: &newName,
}
_, err := svc.Update(ctx, 99999, updateReq)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeNotFound, appErr.Code)
})
}
func TestPackageService_Delete(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypePlatform,
})
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_DELETE"),
PackageName: "删除测试套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
t.Run("删除成功", func(t *testing.T) {
err := svc.Delete(ctx, created.ID)
require.NoError(t, err)
_, err = svc.Get(ctx, created.ID)
require.Error(t, err)
})
t.Run("删除不存在的套餐", func(t *testing.T) {
err := svc.Delete(ctx, 99999)
require.Error(t, err)
})
}
func TestPackageService_List(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypePlatform,
})
packages := []dto.CreatePackageRequest{
{
PackageCode: generateUniquePackageCode("PKG_LIST_001"),
PackageName: "列表测试套餐1",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
},
{
PackageCode: generateUniquePackageCode("PKG_LIST_002"),
PackageName: "列表测试套餐2",
PackageType: "addon",
DurationMonths: 1,
Price: 4900,
},
{
PackageCode: generateUniquePackageCode("PKG_LIST_003"),
PackageName: "列表测试套餐3",
PackageType: "formal",
DurationMonths: 12,
Price: 99900,
},
}
for _, p := range packages {
_, err := svc.Create(ctx, &p)
require.NoError(t, err)
}
t.Run("列表查询", func(t *testing.T) {
req := &dto.PackageListRequest{
Page: 1,
PageSize: 10,
}
resp, total, err := svc.List(ctx, req)
require.NoError(t, err)
assert.Greater(t, total, int64(0))
assert.Greater(t, len(resp), 0)
})
t.Run("按套餐类型过滤", func(t *testing.T) {
packageType := "formal"
req := &dto.PackageListRequest{
Page: 1,
PageSize: 10,
PackageType: &packageType,
}
resp, _, err := svc.List(ctx, req)
require.NoError(t, err)
for _, p := range resp {
assert.Equal(t, packageType, p.PackageType)
}
})
t.Run("按状态过滤", func(t *testing.T) {
status := constants.StatusEnabled
req := &dto.PackageListRequest{
Page: 1,
PageSize: 10,
Status: &status,
}
resp, _, err := svc.List(ctx, req)
require.NoError(t, err)
for _, p := range resp {
assert.Equal(t, status, p.Status)
}
})
}
func TestPackageService_SeriesNameInResponse(t *testing.T) {
tx := testutils.NewTestTransaction(t)
packageStore := postgres.NewPackageStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
svc := New(packageStore, packageSeriesStore, nil, nil, nil)
ctx := middleware.SetUserContext(context.Background(), &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypePlatform,
})
// 创建套餐系列
series := &model.PackageSeries{
SeriesCode: fmt.Sprintf("SERIES_%d", time.Now().UnixNano()),
SeriesName: "测试套餐系列",
Description: "用于测试系列名称字段",
Status: constants.StatusEnabled,
}
series.Creator = 1
err := packageSeriesStore.Create(ctx, series)
require.NoError(t, err)
t.Run("创建套餐时返回系列名称", func(t *testing.T) {
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_SERIES"),
PackageName: "带系列的套餐",
SeriesID: &series.ID,
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
resp, err := svc.Create(ctx, req)
require.NoError(t, err)
assert.NotNil(t, resp.SeriesName)
assert.Equal(t, series.SeriesName, *resp.SeriesName)
})
t.Run("获取套餐时返回系列名称", func(t *testing.T) {
// 先创建一个套餐
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_GET_SERIES"),
PackageName: "获取测试套餐",
SeriesID: &series.ID,
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
// 获取套餐
resp, err := svc.Get(ctx, created.ID)
require.NoError(t, err)
assert.NotNil(t, resp.SeriesName)
assert.Equal(t, series.SeriesName, *resp.SeriesName)
})
t.Run("更新套餐时返回系列名称", func(t *testing.T) {
// 先创建一个套餐
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_UPDATE_SERIES"),
PackageName: "更新测试套餐",
SeriesID: &series.ID,
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
created, err := svc.Create(ctx, req)
require.NoError(t, err)
// 更新套餐
newName := "更新后的套餐"
updateReq := &dto.UpdatePackageRequest{
PackageName: &newName,
}
resp, err := svc.Update(ctx, created.ID, updateReq)
require.NoError(t, err)
assert.NotNil(t, resp.SeriesName)
assert.Equal(t, series.SeriesName, *resp.SeriesName)
})
t.Run("列表查询时返回系列名称", func(t *testing.T) {
// 创建多个带系列的套餐
for i := 0; i < 3; i++ {
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode(fmt.Sprintf("PKG_LIST_SERIES_%d", i)),
PackageName: fmt.Sprintf("列表测试套餐%d", i),
SeriesID: &series.ID,
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
_, err := svc.Create(ctx, req)
require.NoError(t, err)
}
// 查询列表
listReq := &dto.PackageListRequest{
Page: 1,
PageSize: 10,
SeriesID: &series.ID,
}
resp, _, err := svc.List(ctx, listReq)
require.NoError(t, err)
assert.Greater(t, len(resp), 0)
// 验证所有套餐都有系列名称
for _, pkg := range resp {
if pkg.SeriesID != nil && *pkg.SeriesID == series.ID {
assert.NotNil(t, pkg.SeriesName)
assert.Equal(t, series.SeriesName, *pkg.SeriesName)
}
}
})
t.Run("没有系列的套餐SeriesName为空", func(t *testing.T) {
req := &dto.CreatePackageRequest{
PackageCode: generateUniquePackageCode("PKG_NO_SERIES"),
PackageName: "无系列套餐",
PackageType: "formal",
DurationMonths: 1,
Price: 9900,
}
resp, err := svc.Create(ctx, req)
require.NoError(t, err)
assert.Nil(t, resp.SeriesID)
assert.Nil(t, resp.SeriesName)
})
}