feat: 实现套餐管理模块,包含套餐系列、双状态管理、废弃模型清理
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m24s

- 新增套餐系列管理 (CRUD + 状态切换)
- 新增套餐管理 (CRUD + 启用/禁用 + 上架/下架双状态)
- 清理 8 个废弃分佣模型及对应数据库表
- Package 模型新增建议成本价、建议售价、上架状态字段
- 完整的 Store/Service/Handler 三层实现
- 包含单元测试和集成测试
- 归档 add-package-module change
- 新增多个 OpenSpec changes (订单支付、店铺套餐分配、一次性分佣、卡设备系列绑定)
This commit is contained in:
2026-01-27 19:55:47 +08:00
parent 30a0717316
commit 79c061b6fa
70 changed files with 7554 additions and 244 deletions

View File

@@ -2,7 +2,9 @@ package postgres
import (
"context"
"fmt"
"testing"
"time"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store"
@@ -11,6 +13,10 @@ import (
"github.com/stretchr/testify/require"
)
func uniqueICCIDPrefix() string {
return fmt.Sprintf("T%d", time.Now().UnixNano()%1000000000)
}
func TestIotCardStore_Create(t *testing.T) {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
@@ -94,15 +100,16 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
s := NewIotCardStore(tx, rdb)
ctx := context.Background()
prefix := uniqueICCIDPrefix()
standaloneCards := []*model.IotCard{
{ICCID: "89860012345678903001", CardType: "data_card", CarrierID: 1, Status: 1},
{ICCID: "89860012345678903002", CardType: "data_card", CarrierID: 1, Status: 1},
{ICCID: "89860012345678903003", CardType: "data_card", CarrierID: 2, Status: 2},
{ICCID: prefix + "0001", CardType: "data_card", CarrierID: 1, Status: 1},
{ICCID: prefix + "0002", CardType: "data_card", CarrierID: 1, Status: 1},
{ICCID: prefix + "0003", CardType: "data_card", CarrierID: 2, Status: 2},
}
require.NoError(t, s.CreateBatch(ctx, standaloneCards))
boundCard := &model.IotCard{
ICCID: "89860012345678903004",
ICCID: prefix + "0004",
CardType: "data_card",
CarrierID: 1,
Status: 1,
@@ -117,7 +124,8 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
require.NoError(t, tx.Create(binding).Error)
t.Run("查询所有单卡", func(t *testing.T) {
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
filters := map[string]interface{}{"iccid": prefix}
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.Equal(t, int64(3), total)
assert.Len(t, cards, 3)
@@ -128,7 +136,7 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
})
t.Run("按运营商ID过滤", func(t *testing.T) {
filters := map[string]interface{}{"carrier_id": uint(1)}
filters := map[string]interface{}{"carrier_id": uint(1), "iccid": prefix}
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.Equal(t, int64(2), total)
@@ -138,7 +146,7 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
})
t.Run("按状态过滤", func(t *testing.T) {
filters := map[string]interface{}{"status": 2}
filters := map[string]interface{}{"status": 2, "iccid": prefix}
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
@@ -147,26 +155,28 @@ func TestIotCardStore_ListStandalone(t *testing.T) {
})
t.Run("按ICCID模糊查询", func(t *testing.T) {
filters := map[string]interface{}{"iccid": "903001"}
filters := map[string]interface{}{"iccid": prefix + "0001"}
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
assert.Contains(t, cards[0].ICCID, "903001")
assert.Contains(t, cards[0].ICCID, prefix+"0001")
})
t.Run("分页查询", func(t *testing.T) {
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
filters := map[string]interface{}{"iccid": prefix}
cards, total, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, filters)
require.NoError(t, err)
assert.Equal(t, int64(3), total)
assert.Len(t, cards, 2)
cards2, _, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 2, PageSize: 2}, nil)
cards2, _, err := s.ListStandalone(ctx, &store.QueryOptions{Page: 2, PageSize: 2}, filters)
require.NoError(t, err)
assert.Len(t, cards2, 1)
})
t.Run("默认分页选项", func(t *testing.T) {
cards, total, err := s.ListStandalone(ctx, nil, nil)
filters := map[string]interface{}{"iccid": prefix}
cards, total, err := s.ListStandalone(ctx, nil, filters)
require.NoError(t, err)
assert.Equal(t, int64(3), total)
assert.Len(t, cards, 3)
@@ -181,39 +191,43 @@ func TestIotCardStore_ListStandalone_Filters(t *testing.T) {
s := NewIotCardStore(tx, rdb)
ctx := context.Background()
shopID := uint(100)
prefix := uniqueICCIDPrefix()
batchPrefix := "B" + prefix
msisdnPrefix := "199" + prefix[1:8]
shopID := uint(time.Now().UnixNano() % 1000000)
cards := []*model.IotCard{
{ICCID: "89860012345678904001", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: &shopID, BatchNo: "BATCH001", MSISDN: "13800000001"},
{ICCID: "89860012345678904002", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: "BATCH001", MSISDN: "13800000002"},
{ICCID: "89860012345678904003", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: "BATCH002", MSISDN: "13800000003"},
{ICCID: prefix + "A001", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: &shopID, BatchNo: batchPrefix + "01", MSISDN: msisdnPrefix + "01"},
{ICCID: prefix + "A002", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: batchPrefix + "01", MSISDN: msisdnPrefix + "02"},
{ICCID: prefix + "A003", CardType: "data_card", CarrierID: 1, Status: 1, ShopID: nil, BatchNo: batchPrefix + "02", MSISDN: msisdnPrefix + "03"},
}
require.NoError(t, s.CreateBatch(ctx, cards))
t.Run("按店铺ID过滤", func(t *testing.T) {
filters := map[string]interface{}{"shop_id": shopID}
cards, total, err := s.ListStandalone(ctx, nil, filters)
filters := map[string]interface{}{"shop_id": shopID, "iccid": prefix}
result, total, err := s.ListStandalone(ctx, nil, filters)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
assert.Equal(t, shopID, *cards[0].ShopID)
assert.Equal(t, shopID, *result[0].ShopID)
})
t.Run("按批次号过滤", func(t *testing.T) {
filters := map[string]interface{}{"batch_no": "BATCH001"}
filters := map[string]interface{}{"batch_no": batchPrefix + "01", "iccid": prefix}
_, total, err := s.ListStandalone(ctx, nil, filters)
require.NoError(t, err)
assert.Equal(t, int64(2), total)
})
t.Run("按MSISDN模糊查询", func(t *testing.T) {
filters := map[string]interface{}{"msisdn": "000001"}
filters := map[string]interface{}{"msisdn": msisdnPrefix + "01"}
result, total, err := s.ListStandalone(ctx, nil, filters)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
assert.Contains(t, result[0].MSISDN, "000001")
assert.Contains(t, result[0].MSISDN, msisdnPrefix+"01")
})
t.Run("已分销过滤-true", func(t *testing.T) {
filters := map[string]interface{}{"is_distributed": true}
filters := map[string]interface{}{"is_distributed": true, "iccid": prefix}
result, total, err := s.ListStandalone(ctx, nil, filters)
require.NoError(t, err)
assert.Equal(t, int64(1), total)
@@ -221,7 +235,7 @@ func TestIotCardStore_ListStandalone_Filters(t *testing.T) {
})
t.Run("已分销过滤-false", func(t *testing.T) {
filters := map[string]interface{}{"is_distributed": false}
filters := map[string]interface{}{"is_distributed": false, "iccid": prefix}
result, total, err := s.ListStandalone(ctx, nil, filters)
require.NoError(t, err)
assert.Equal(t, int64(2), total)
@@ -232,8 +246,8 @@ func TestIotCardStore_ListStandalone_Filters(t *testing.T) {
t.Run("ICCID范围查询", func(t *testing.T) {
filters := map[string]interface{}{
"iccid_start": "89860012345678904001",
"iccid_end": "89860012345678904002",
"iccid_start": prefix + "A001",
"iccid_end": prefix + "A002",
}
_, total, err := s.ListStandalone(ctx, nil, filters)
require.NoError(t, err)

View File

@@ -0,0 +1,84 @@
package postgres
import (
"context"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store"
)
type PackageSeriesStore struct {
db *gorm.DB
}
func NewPackageSeriesStore(db *gorm.DB) *PackageSeriesStore {
return &PackageSeriesStore{db: db}
}
func (s *PackageSeriesStore) Create(ctx context.Context, series *model.PackageSeries) error {
return s.db.WithContext(ctx).Create(series).Error
}
func (s *PackageSeriesStore) GetByID(ctx context.Context, id uint) (*model.PackageSeries, error) {
var series model.PackageSeries
if err := s.db.WithContext(ctx).First(&series, id).Error; err != nil {
return nil, err
}
return &series, nil
}
func (s *PackageSeriesStore) GetByCode(ctx context.Context, code string) (*model.PackageSeries, error) {
var series model.PackageSeries
if err := s.db.WithContext(ctx).Where("series_code = ?", code).First(&series).Error; err != nil {
return nil, err
}
return &series, nil
}
func (s *PackageSeriesStore) Update(ctx context.Context, series *model.PackageSeries) error {
return s.db.WithContext(ctx).Save(series).Error
}
func (s *PackageSeriesStore) Delete(ctx context.Context, id uint) error {
return s.db.WithContext(ctx).Delete(&model.PackageSeries{}, id).Error
}
func (s *PackageSeriesStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.PackageSeries, int64, error) {
var seriesList []*model.PackageSeries
var total int64
query := s.db.WithContext(ctx).Model(&model.PackageSeries{})
if seriesName, ok := filters["series_name"].(string); ok && seriesName != "" {
query = query.Where("series_name LIKE ?", "%"+seriesName+"%")
}
if status, ok := filters["status"]; ok {
query = query.Where("status = ?", status)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if opts == nil {
opts = store.DefaultQueryOptions()
}
offset := (opts.Page - 1) * opts.PageSize
query = query.Offset(offset).Limit(opts.PageSize)
if opts.OrderBy != "" {
query = query.Order(opts.OrderBy)
}
if err := query.Find(&seriesList).Error; err != nil {
return nil, 0, err
}
return seriesList, total, nil
}
func (s *PackageSeriesStore) UpdateStatus(ctx context.Context, id uint, status int) error {
return s.db.WithContext(ctx).Model(&model.PackageSeries{}).Where("id = ?", id).Update("status", status).Error
}

View File

@@ -0,0 +1,191 @@
package postgres
import (
"context"
"testing"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/tests/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPackageSeriesStore_Create(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageSeriesStore(tx)
ctx := context.Background()
series := &model.PackageSeries{
SeriesCode: "SERIES_TEST_001",
SeriesName: "测试系列",
Description: "测试描述",
Status: constants.StatusEnabled,
}
err := s.Create(ctx, series)
require.NoError(t, err)
assert.NotZero(t, series.ID)
}
func TestPackageSeriesStore_GetByID(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageSeriesStore(tx)
ctx := context.Background()
series := &model.PackageSeries{
SeriesCode: "SERIES_TEST_002",
SeriesName: "测试系列2",
Status: constants.StatusEnabled,
}
require.NoError(t, s.Create(ctx, series))
t.Run("查询存在的系列", func(t *testing.T) {
result, err := s.GetByID(ctx, series.ID)
require.NoError(t, err)
assert.Equal(t, series.SeriesCode, result.SeriesCode)
})
t.Run("查询不存在的系列", func(t *testing.T) {
_, err := s.GetByID(ctx, 99999)
require.Error(t, err)
})
}
func TestPackageSeriesStore_GetByCode(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageSeriesStore(tx)
ctx := context.Background()
series := &model.PackageSeries{
SeriesCode: "SERIES_TEST_003",
SeriesName: "测试系列3",
Status: constants.StatusEnabled,
}
require.NoError(t, s.Create(ctx, series))
t.Run("查询存在的编码", func(t *testing.T) {
result, err := s.GetByCode(ctx, "SERIES_TEST_003")
require.NoError(t, err)
assert.Equal(t, series.ID, result.ID)
})
t.Run("查询不存在的编码", func(t *testing.T) {
_, err := s.GetByCode(ctx, "NOT_EXISTS")
require.Error(t, err)
})
}
func TestPackageSeriesStore_Update(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageSeriesStore(tx)
ctx := context.Background()
series := &model.PackageSeries{
SeriesCode: "SERIES_TEST_004",
SeriesName: "测试系列4",
Status: constants.StatusEnabled,
}
require.NoError(t, s.Create(ctx, series))
series.SeriesName = "测试系列4-更新"
series.Description = "更新后的描述"
err := s.Update(ctx, series)
require.NoError(t, err)
updated, err := s.GetByID(ctx, series.ID)
require.NoError(t, err)
assert.Equal(t, "测试系列4-更新", updated.SeriesName)
assert.Equal(t, "更新后的描述", updated.Description)
}
func TestPackageSeriesStore_Delete(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageSeriesStore(tx)
ctx := context.Background()
series := &model.PackageSeries{
SeriesCode: "SERIES_DEL_001",
SeriesName: "待删除系列",
Status: constants.StatusEnabled,
}
require.NoError(t, s.Create(ctx, series))
err := s.Delete(ctx, series.ID)
require.NoError(t, err)
_, err = s.GetByID(ctx, series.ID)
require.Error(t, err)
}
func TestPackageSeriesStore_List(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageSeriesStore(tx)
ctx := context.Background()
seriesList := []*model.PackageSeries{
{SeriesCode: "LIST_S_001", SeriesName: "基础套餐", Status: constants.StatusEnabled},
{SeriesCode: "LIST_S_002", SeriesName: "高级套餐", Status: constants.StatusEnabled},
{SeriesCode: "LIST_S_003", SeriesName: "企业套餐", Status: constants.StatusEnabled},
}
for _, series := range seriesList {
require.NoError(t, s.Create(ctx, series))
}
seriesList[2].Status = constants.StatusDisabled
require.NoError(t, s.Update(ctx, seriesList[2]))
t.Run("查询所有系列", func(t *testing.T) {
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(3))
assert.GreaterOrEqual(t, len(result), 3)
})
t.Run("按名称模糊搜索", func(t *testing.T) {
filters := map[string]interface{}{"series_name": "高级"}
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(1))
for _, series := range result {
assert.Contains(t, series.SeriesName, "高级")
}
})
t.Run("按状态过滤", func(t *testing.T) {
filters := map[string]interface{}{"status": constants.StatusDisabled}
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(1))
for _, series := range result {
assert.Equal(t, constants.StatusDisabled, series.Status)
}
})
t.Run("分页查询", func(t *testing.T) {
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(3))
assert.LessOrEqual(t, len(result), 2)
})
}
func TestPackageSeriesStore_UpdateStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageSeriesStore(tx)
ctx := context.Background()
series := &model.PackageSeries{
SeriesCode: "STATUS_TEST_001",
SeriesName: "状态测试系列",
Status: constants.StatusEnabled,
}
require.NoError(t, s.Create(ctx, series))
err := s.UpdateStatus(ctx, series.ID, constants.StatusDisabled)
require.NoError(t, err)
updated, err := s.GetByID(ctx, series.ID)
require.NoError(t, err)
assert.Equal(t, constants.StatusDisabled, updated.Status)
}

View File

@@ -0,0 +1,97 @@
package postgres
import (
"context"
"gorm.io/gorm"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store"
)
type PackageStore struct {
db *gorm.DB
}
func NewPackageStore(db *gorm.DB) *PackageStore {
return &PackageStore{db: db}
}
func (s *PackageStore) Create(ctx context.Context, pkg *model.Package) error {
return s.db.WithContext(ctx).Create(pkg).Error
}
func (s *PackageStore) GetByID(ctx context.Context, id uint) (*model.Package, error) {
var pkg model.Package
if err := s.db.WithContext(ctx).First(&pkg, id).Error; err != nil {
return nil, err
}
return &pkg, nil
}
func (s *PackageStore) GetByCode(ctx context.Context, code string) (*model.Package, error) {
var pkg model.Package
if err := s.db.WithContext(ctx).Where("package_code = ?", code).First(&pkg).Error; err != nil {
return nil, err
}
return &pkg, nil
}
func (s *PackageStore) Update(ctx context.Context, pkg *model.Package) error {
return s.db.WithContext(ctx).Save(pkg).Error
}
func (s *PackageStore) Delete(ctx context.Context, id uint) error {
return s.db.WithContext(ctx).Delete(&model.Package{}, id).Error
}
func (s *PackageStore) List(ctx context.Context, opts *store.QueryOptions, filters map[string]interface{}) ([]*model.Package, int64, error) {
var packages []*model.Package
var total int64
query := s.db.WithContext(ctx).Model(&model.Package{})
if packageName, ok := filters["package_name"].(string); ok && packageName != "" {
query = query.Where("package_name LIKE ?", "%"+packageName+"%")
}
if seriesID, ok := filters["series_id"].(uint); ok && seriesID > 0 {
query = query.Where("series_id = ?", seriesID)
}
if status, ok := filters["status"]; ok {
query = query.Where("status = ?", status)
}
if shelfStatus, ok := filters["shelf_status"]; ok {
query = query.Where("shelf_status = ?", shelfStatus)
}
if packageType, ok := filters["package_type"].(string); ok && packageType != "" {
query = query.Where("package_type = ?", packageType)
}
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
if opts == nil {
opts = store.DefaultQueryOptions()
}
offset := (opts.Page - 1) * opts.PageSize
query = query.Offset(offset).Limit(opts.PageSize)
if opts.OrderBy != "" {
query = query.Order(opts.OrderBy)
}
if err := query.Find(&packages).Error; err != nil {
return nil, 0, err
}
return packages, total, nil
}
func (s *PackageStore) UpdateStatus(ctx context.Context, id uint, status int) error {
return s.db.WithContext(ctx).Model(&model.Package{}).Where("id = ?", id).Update("status", status).Error
}
func (s *PackageStore) UpdateShelfStatus(ctx context.Context, id uint, shelfStatus int) error {
return s.db.WithContext(ctx).Model(&model.Package{}).Where("id = ?", id).Update("shelf_status", shelfStatus).Error
}

View File

@@ -0,0 +1,332 @@
package postgres
import (
"context"
"testing"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/store"
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/tests/testutils"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPackageStore_Create(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageStore(tx)
ctx := context.Background()
pkg := &model.Package{
PackageCode: "PKG_TEST_001",
PackageName: "测试套餐",
SeriesID: 1,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 1024,
DataAmountMB: 1024,
Price: 9900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
}
err := s.Create(ctx, pkg)
require.NoError(t, err)
assert.NotZero(t, pkg.ID)
}
func TestPackageStore_GetByID(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageStore(tx)
ctx := context.Background()
pkg := &model.Package{
PackageCode: "PKG_TEST_002",
PackageName: "测试套餐2",
SeriesID: 1,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 2048,
DataAmountMB: 2048,
Price: 19900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
}
require.NoError(t, s.Create(ctx, pkg))
t.Run("查询存在的套餐", func(t *testing.T) {
result, err := s.GetByID(ctx, pkg.ID)
require.NoError(t, err)
assert.Equal(t, pkg.PackageCode, result.PackageCode)
})
t.Run("查询不存在的套餐", func(t *testing.T) {
_, err := s.GetByID(ctx, 99999)
require.Error(t, err)
})
}
func TestPackageStore_GetByCode(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageStore(tx)
ctx := context.Background()
pkg := &model.Package{
PackageCode: "PKG_TEST_003",
PackageName: "测试套餐3",
SeriesID: 1,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 3072,
DataAmountMB: 3072,
Price: 29900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
}
require.NoError(t, s.Create(ctx, pkg))
t.Run("查询存在的编码", func(t *testing.T) {
result, err := s.GetByCode(ctx, "PKG_TEST_003")
require.NoError(t, err)
assert.Equal(t, pkg.ID, result.ID)
})
t.Run("查询不存在的编码", func(t *testing.T) {
_, err := s.GetByCode(ctx, "NOT_EXISTS")
require.Error(t, err)
})
}
func TestPackageStore_Update(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageStore(tx)
ctx := context.Background()
pkg := &model.Package{
PackageCode: "PKG_TEST_004",
PackageName: "测试套餐4",
SeriesID: 1,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 4096,
DataAmountMB: 4096,
Price: 39900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
}
require.NoError(t, s.Create(ctx, pkg))
pkg.PackageName = "测试套餐4-更新"
pkg.Price = 49900
err := s.Update(ctx, pkg)
require.NoError(t, err)
updated, err := s.GetByID(ctx, pkg.ID)
require.NoError(t, err)
assert.Equal(t, "测试套餐4-更新", updated.PackageName)
assert.Equal(t, int64(49900), updated.Price)
}
func TestPackageStore_Delete(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageStore(tx)
ctx := context.Background()
pkg := &model.Package{
PackageCode: "PKG_DEL_001",
PackageName: "待删除套餐",
SeriesID: 1,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 1024,
DataAmountMB: 1024,
Price: 9900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
}
require.NoError(t, s.Create(ctx, pkg))
err := s.Delete(ctx, pkg.ID)
require.NoError(t, err)
_, err = s.GetByID(ctx, pkg.ID)
require.Error(t, err)
}
func TestPackageStore_List(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageStore(tx)
ctx := context.Background()
pkgList := []*model.Package{
{
PackageCode: "LIST_P_001",
PackageName: "基础套餐",
SeriesID: 1,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 1024,
DataAmountMB: 1024,
Price: 9900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
},
{
PackageCode: "LIST_P_002",
PackageName: "高级套餐",
SeriesID: 2,
PackageType: "formal",
DurationMonths: 12,
DataType: "real",
RealDataMB: 10240,
DataAmountMB: 10240,
Price: 99900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
},
{
PackageCode: "LIST_P_003",
PackageName: "企业套餐",
SeriesID: 3,
PackageType: "addon",
DurationMonths: 1,
DataType: "virtual",
VirtualDataMB: 5120,
DataAmountMB: 5120,
Price: 4900,
Status: constants.StatusEnabled,
ShelfStatus: 2,
},
}
for _, pkg := range pkgList {
require.NoError(t, s.Create(ctx, pkg))
}
pkgList[2].Status = constants.StatusDisabled
require.NoError(t, s.Update(ctx, pkgList[2]))
t.Run("查询所有套餐", func(t *testing.T) {
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, nil)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(3))
assert.GreaterOrEqual(t, len(result), 3)
})
t.Run("按名称模糊搜索", func(t *testing.T) {
filters := map[string]interface{}{"package_name": "高级"}
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(1))
for _, pkg := range result {
assert.Contains(t, pkg.PackageName, "高级")
}
})
t.Run("按系列筛选", func(t *testing.T) {
filters := map[string]interface{}{"series_id": uint(2)}
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(1))
for _, pkg := range result {
assert.Equal(t, uint(2), pkg.SeriesID)
}
})
t.Run("按状态过滤", func(t *testing.T) {
filters := map[string]interface{}{"status": constants.StatusDisabled}
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(1))
for _, pkg := range result {
assert.Equal(t, constants.StatusDisabled, pkg.Status)
}
})
t.Run("按上架状态过滤", func(t *testing.T) {
filters := map[string]interface{}{"shelf_status": 2}
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(1))
for _, pkg := range result {
assert.Equal(t, 2, pkg.ShelfStatus)
}
})
t.Run("按类型过滤", func(t *testing.T) {
filters := map[string]interface{}{"package_type": "addon"}
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 20}, filters)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(1))
for _, pkg := range result {
assert.Equal(t, "addon", pkg.PackageType)
}
})
t.Run("分页查询", func(t *testing.T) {
result, total, err := s.List(ctx, &store.QueryOptions{Page: 1, PageSize: 2}, nil)
require.NoError(t, err)
assert.GreaterOrEqual(t, total, int64(3))
assert.LessOrEqual(t, len(result), 2)
})
}
func TestPackageStore_UpdateStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageStore(tx)
ctx := context.Background()
pkg := &model.Package{
PackageCode: "STATUS_TEST_001",
PackageName: "状态测试套餐",
SeriesID: 1,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 1024,
DataAmountMB: 1024,
Price: 9900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
}
require.NoError(t, s.Create(ctx, pkg))
err := s.UpdateStatus(ctx, pkg.ID, constants.StatusDisabled)
require.NoError(t, err)
updated, err := s.GetByID(ctx, pkg.ID)
require.NoError(t, err)
assert.Equal(t, constants.StatusDisabled, updated.Status)
}
func TestPackageStore_UpdateShelfStatus(t *testing.T) {
tx := testutils.NewTestTransaction(t)
s := NewPackageStore(tx)
ctx := context.Background()
pkg := &model.Package{
PackageCode: "SHELF_TEST_001",
PackageName: "上架测试套餐",
SeriesID: 1,
PackageType: "formal",
DurationMonths: 1,
DataType: "real",
RealDataMB: 1024,
DataAmountMB: 1024,
Price: 9900,
Status: constants.StatusEnabled,
ShelfStatus: 1,
}
require.NoError(t, s.Create(ctx, pkg))
err := s.UpdateShelfStatus(ctx, pkg.ID, 2)
require.NoError(t, err)
updated, err := s.GetByID(ctx, pkg.ID)
require.NoError(t, err)
assert.Equal(t, 2, updated.ShelfStatus)
}