Files
junhong_cmp_fiber/internal/service/order/service_test.go
huang dfcf16f548
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 5m36s
feat: 实现订单支付功能模块
- 新增订单管理、支付回调、购买验证等核心服务
- 实现订单、订单项目的数据存储层和 API 接口
- 添加订单数据库迁移和 DTO 定义
- 更新 API 文档和路由配置
- 同步 3 个新规范到主规范库(订单管理、订单支付、套餐购买验证)
- 完成 OpenSpec 变更归档

Ultraworked with Sisyphus
2026-01-28 22:12:15 +08:00

431 lines
13 KiB
Go

package order
import (
"context"
"testing"
"github.com/break/junhong_cmp_fiber/internal/model"
"github.com/break/junhong_cmp_fiber/internal/model/dto"
"github.com/break/junhong_cmp_fiber/internal/service/purchase_validation"
"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"
)
type testEnv struct {
ctx context.Context
svc *Service
card *model.IotCard
device *model.Device
pkg *model.Package
shop *model.Shop
wallet *model.Wallet
allocation *model.ShopSeriesAllocation
}
func setupOrderTestEnv(t *testing.T) *testEnv {
tx := testutils.NewTestTransaction(t)
rdb := testutils.GetTestRedis(t)
testutils.CleanTestRedisKeys(t, rdb)
iotCardStore := postgres.NewIotCardStore(tx, rdb)
deviceStore := postgres.NewDeviceStore(tx, rdb)
packageStore := postgres.NewPackageStore(tx)
seriesAllocationStore := postgres.NewShopSeriesAllocationStore(tx)
packageSeriesStore := postgres.NewPackageSeriesStore(tx)
carrierStore := postgres.NewCarrierStore(tx)
shopStore := postgres.NewShopStore(tx, rdb)
orderStore := postgres.NewOrderStore(tx, rdb)
orderItemStore := postgres.NewOrderItemStore(tx, rdb)
walletStore := postgres.NewWalletStore(tx, rdb)
ctx := context.Background()
carrier := &model.Carrier{
CarrierCode: "TEST_CARRIER_ORDER",
CarrierName: "测试运营商",
CarrierType: constants.CarrierTypeCMCC,
Status: constants.StatusEnabled,
}
require.NoError(t, carrierStore.Create(ctx, carrier))
shop := &model.Shop{
ShopName: "测试店铺ORDER",
ShopCode: "TEST_SHOP_ORDER",
Level: 1,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{Creator: 1, Updater: 1},
}
require.NoError(t, shopStore.Create(ctx, shop))
series := &model.PackageSeries{
SeriesCode: "TEST_SERIES_ORDER",
SeriesName: "测试套餐系列",
Description: "测试用",
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{Creator: 1, Updater: 1},
}
require.NoError(t, packageSeriesStore.Create(ctx, series))
allocation := &model.ShopSeriesAllocation{
ShopID: shop.ID,
SeriesID: series.ID,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{Creator: 1, Updater: 1},
}
require.NoError(t, seriesAllocationStore.Create(ctx, allocation))
pkg := &model.Package{
PackageCode: "TEST_PKG_ORDER",
PackageName: "测试套餐",
SeriesID: series.ID,
PackageType: "formal",
DurationMonths: 1,
DataAmountMB: 1024,
SuggestedRetailPrice: 9900,
Status: constants.StatusEnabled,
ShelfStatus: constants.ShelfStatusOn,
BaseModel: model.BaseModel{Creator: 1, Updater: 1},
}
require.NoError(t, packageStore.Create(ctx, pkg))
shopIDPtr := &shop.ID
card := &model.IotCard{
ICCID: "89860000000000000002",
ShopID: shopIDPtr,
CarrierID: carrier.ID,
SeriesAllocationID: &allocation.ID,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{Creator: 1, Updater: 1},
}
require.NoError(t, iotCardStore.Create(ctx, card))
device := &model.Device{
DeviceNo: "DEV_TEST_ORDER_001",
ShopID: shopIDPtr,
SeriesAllocationID: &allocation.ID,
Status: constants.StatusEnabled,
BaseModel: model.BaseModel{Creator: 1, Updater: 1},
}
require.NoError(t, deviceStore.Create(ctx, device))
wallet := &model.Wallet{
ResourceType: "shop",
ResourceID: shop.ID,
WalletType: "main",
Balance: 100000,
Version: 1,
BaseModel: model.BaseModel{Creator: 1, Updater: 1},
}
require.NoError(t, tx.Create(wallet).Error)
purchaseValidationSvc := purchase_validation.New(tx, iotCardStore, deviceStore, packageStore, seriesAllocationStore)
orderSvc := New(tx, orderStore, orderItemStore, walletStore, purchaseValidationSvc, nil)
userCtx := middleware.SetUserContext(ctx, &middleware.UserContextInfo{
UserID: 1,
UserType: constants.UserTypeAgent,
ShopID: shop.ID,
})
return &testEnv{
ctx: userCtx,
svc: orderSvc,
card: card,
device: device,
pkg: pkg,
shop: shop,
wallet: wallet,
allocation: allocation,
}
}
func TestOrderService_Create(t *testing.T) {
env := setupOrderTestEnv(t)
t.Run("创建单卡订单成功", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
resp, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
assert.NotZero(t, resp.ID)
assert.Contains(t, resp.OrderNo, "ORD")
assert.Equal(t, model.OrderTypeSingleCard, resp.OrderType)
assert.Equal(t, model.BuyerTypeAgent, resp.BuyerType)
assert.Equal(t, env.shop.ID, resp.BuyerID)
assert.Equal(t, env.pkg.SuggestedRetailPrice, resp.TotalAmount)
assert.Equal(t, model.PaymentStatusPending, resp.PaymentStatus)
assert.Len(t, resp.Items, 1)
})
t.Run("创建设备订单成功", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeDevice,
DeviceID: &env.device.ID,
PackageIDs: []uint{env.pkg.ID},
}
resp, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
assert.NotZero(t, resp.ID)
assert.Equal(t, model.OrderTypeDevice, resp.OrderType)
})
t.Run("单卡订单缺少卡ID", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
PackageIDs: []uint{env.pkg.ID},
}
_, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeInvalidParam, appErr.Code)
})
t.Run("设备订单缺少设备ID", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeDevice,
PackageIDs: []uint{env.pkg.ID},
}
_, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeInvalidParam, appErr.Code)
})
}
func TestOrderService_Get(t *testing.T) {
env := setupOrderTestEnv(t)
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
t.Run("获取订单成功", func(t *testing.T) {
resp, err := env.svc.Get(env.ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, created.OrderNo, resp.OrderNo)
assert.Len(t, resp.Items, 1)
})
t.Run("订单不存在", func(t *testing.T) {
_, err := env.svc.Get(env.ctx, 99999)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeNotFound, appErr.Code)
})
}
func TestOrderService_List(t *testing.T) {
env := setupOrderTestEnv(t)
for i := 0; i < 3; i++ {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
_, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
}
t.Run("列表查询", func(t *testing.T) {
listReq := &dto.OrderListRequest{
Page: 1,
PageSize: 10,
}
resp, err := env.svc.List(env.ctx, listReq, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
assert.GreaterOrEqual(t, resp.Total, int64(3))
assert.GreaterOrEqual(t, len(resp.List), 3)
})
t.Run("按支付状态过滤", func(t *testing.T) {
status := model.PaymentStatusPending
listReq := &dto.OrderListRequest{
Page: 1,
PageSize: 10,
PaymentStatus: &status,
}
resp, err := env.svc.List(env.ctx, listReq, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
for _, o := range resp.List {
assert.Equal(t, model.PaymentStatusPending, o.PaymentStatus)
}
})
}
func TestOrderService_Cancel(t *testing.T) {
env := setupOrderTestEnv(t)
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
t.Run("取消订单成功", func(t *testing.T) {
err := env.svc.Cancel(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
order, err := env.svc.Get(env.ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, model.PaymentStatusCancelled, order.PaymentStatus)
})
t.Run("订单不存在", func(t *testing.T) {
err := env.svc.Cancel(env.ctx, 99999, model.BuyerTypeAgent, env.shop.ID)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeNotFound, appErr.Code)
})
t.Run("无权操作", func(t *testing.T) {
newReq := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
newOrder, err := env.svc.Create(env.ctx, newReq, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
err = env.svc.Cancel(env.ctx, newOrder.ID, model.BuyerTypeAgent, 99999)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeForbidden, appErr.Code)
})
}
func TestOrderService_WalletPay(t *testing.T) {
env := setupOrderTestEnv(t)
t.Run("钱包支付成功", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
order, err := env.svc.Get(env.ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, model.PaymentStatusPaid, order.PaymentStatus)
assert.Equal(t, model.PaymentMethodWallet, order.PaymentMethod)
assert.NotNil(t, order.PaidAt)
})
t.Run("订单不存在", func(t *testing.T) {
err := env.svc.WalletPay(env.ctx, 99999, model.BuyerTypeAgent, env.shop.ID)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeNotFound, appErr.Code)
})
t.Run("无权操作", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, 99999)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeForbidden, appErr.Code)
})
t.Run("重复支付", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
err = env.svc.WalletPay(env.ctx, created.ID, model.BuyerTypeAgent, env.shop.ID)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeInvalidStatus, appErr.Code)
})
}
func TestOrderService_HandlePaymentCallback(t *testing.T) {
env := setupOrderTestEnv(t)
t.Run("支付回调成功", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
err = env.svc.HandlePaymentCallback(env.ctx, created.OrderNo, model.PaymentMethodWechat)
require.NoError(t, err)
order, err := env.svc.Get(env.ctx, created.ID)
require.NoError(t, err)
assert.Equal(t, model.PaymentStatusPaid, order.PaymentStatus)
assert.Equal(t, model.PaymentMethodWechat, order.PaymentMethod)
})
t.Run("幂等处理-已支付订单", func(t *testing.T) {
req := &dto.CreateOrderRequest{
OrderType: model.OrderTypeSingleCard,
IotCardID: &env.card.ID,
PackageIDs: []uint{env.pkg.ID},
}
created, err := env.svc.Create(env.ctx, req, model.BuyerTypeAgent, env.shop.ID)
require.NoError(t, err)
err = env.svc.HandlePaymentCallback(env.ctx, created.OrderNo, model.PaymentMethodAlipay)
require.NoError(t, err)
err = env.svc.HandlePaymentCallback(env.ctx, created.OrderNo, model.PaymentMethodAlipay)
require.NoError(t, err)
})
t.Run("订单不存在", func(t *testing.T) {
err := env.svc.HandlePaymentCallback(env.ctx, "NOT_EXISTS_ORDER_NO", model.PaymentMethodWechat)
require.Error(t, err)
appErr, ok := err.(*errors.AppError)
require.True(t, ok)
assert.Equal(t, errors.CodeNotFound, appErr.Code)
})
}