feat: 添加订单退款功能的测试用例及相关逻辑

This commit is contained in:
2025-12-23 09:26:22 +08:00
parent ead821ac2c
commit dd7bcdfb98
3 changed files with 104 additions and 43 deletions

View File

@@ -4,13 +4,17 @@ import (
"context"
"testing"
jobs_args "quyun/v2/app/jobs/args"
"quyun/v2/database"
"quyun/v2/providers/job"
"quyun/v2/providers/jwt"
"quyun/v2/providers/postgres"
"github.com/riverqueue/river"
"go.ipao.vip/atom"
"go.ipao.vip/atom/container"
"go.ipao.vip/atom/contracts"
"go.ipao.vip/atom/opt"
"go.uber.org/dig"
"github.com/rogeecn/fabfile"
@@ -22,10 +26,33 @@ func Default(providers ...container.ProviderContainer) container.Providers {
postgres.DefaultProvider(),
jwt.DefaultProvider(),
job.DefaultProvider(),
testJobWorkersProvider(),
database.DefaultProvider(),
}, providers...)
}
type orderRefundTestWorker struct {
river.WorkerDefaults[jobs_args.OrderRefundJob]
}
func (w *orderRefundTestWorker) Work(ctx context.Context, job *river.Job[jobs_args.OrderRefundJob]) error {
return nil
}
func testJobWorkersProvider() container.ProviderContainer {
return container.ProviderContainer{
Provider: func(opts ...opt.Option) error {
return container.Container.Provide(func(__job *job.Job) (contracts.Initial, error) {
obj := &orderRefundTestWorker{}
if err := river.AddWorkerSafely(__job.Workers, obj); err != nil {
return nil, err
}
return obj, nil
}, atom.GroupInitial)
},
}
}
func Serve(providers container.Providers, t *testing.T, invoke any) {
Convey("tests boot up", t, func() {
// 关键语义:测试用例可能会在同一进程内多次调用 Serve。

View File

@@ -135,23 +135,25 @@ func (*orderAdmin) adminOrderDetail(
return &dto.AdminOrderDetail{Order: m}, nil
}
// adminRefund
//
// @Summary 订单退款(租户管理)
// @Tags Tenant
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Param orderID path int64 true "OrderID"
// @Param form body dto.AdminOrderRefundForm true "Form"
// @Success 200 {object} models.Order
//
// @Router /t/:tenantCode/v1/admin/orders/:orderID/refund [post]
// @Bind tenant local key(tenant)
// @Bind tenantUser local key(tenant_user)
// @Bind orderID path
// @Bind form body
func (*orderAdmin) adminRefund(
// adminRefund
//
// @Summary 订单退款(租户管理)
// @Description 该接口只负责将订单从 paid 推进到 refunding并提交异步退款任务退款入账与权益回收由 worker 异步完成。
// @Description 重复请求幂等:订单处于 refunding/refunded 时会返回当前订单状态,不会重复入账/重复回收权益。
// @Tags Tenant
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Param orderID path int64 true "OrderID"
// @Param form body dto.AdminOrderRefundForm true "Form"
// @Success 200 {object} models.Order
//
// @Router /t/:tenantCode/v1/admin/orders/:orderID/refund [post]
// @Bind tenant local key(tenant)
// @Bind tenantUser local key(tenant_user)
// @Bind orderID path
// @Bind form body
func (*orderAdmin) adminRefund(
ctx fiber.Ctx,
tenant *models.Tenant,
tenantUser *models.TenantUser,

View File

@@ -1127,6 +1127,12 @@ func (s *OrderTestSuite) Test_AdminRefundOrder() {
So(refunding, ShouldNotBeNil)
So(refunding.Status, ShouldEqual, consts.OrderStatusRefunding)
// refunding 期间重复请求应幂等返回 refunding并允许重复触发入队不影响最终结果
refunding2, err := Order.AdminRefundOrder(ctx, tenantID, operatorUserID, orderModel.ID, false, "原因2", "", now.Add(90*time.Second))
So(err, ShouldBeNil)
So(refunding2, ShouldNotBeNil)
So(refunding2.Status, ShouldEqual, consts.OrderStatusRefunding)
refunded, err := Order.ProcessRefundingOrder(ctx, &ProcessRefundingOrderParams{
TenantID: tenantID,
OrderID: orderModel.ID,
@@ -1139,6 +1145,19 @@ func (s *OrderTestSuite) Test_AdminRefundOrder() {
So(refunded, ShouldNotBeNil)
So(refunded.Status, ShouldEqual, consts.OrderStatusRefunded)
// worker 重试/重复执行应幂等:不重复入账、不重复回收权益。
refundedRetry, err := Order.ProcessRefundingOrder(ctx, &ProcessRefundingOrderParams{
TenantID: tenantID,
OrderID: orderModel.ID,
OperatorUserID: operatorUserID,
Force: false,
Reason: "原因",
Now: now.Add(5 * time.Minute),
})
So(err, ShouldBeNil)
So(refundedRetry, ShouldNotBeNil)
So(refundedRetry.Status, ShouldEqual, consts.OrderStatusRefunded)
var tu models.TenantUser
So(_db.WithContext(ctx).Where("tenant_id = ? AND user_id = ?", tenantID, buyerUserID).First(&tu).Error, ShouldBeNil)
So(tu.Balance, ShouldEqual, 300)
@@ -1162,8 +1181,21 @@ func (s *OrderTestSuite) Test_AdminRefundOrder() {
Find(&ledgers).Error, ShouldBeNil)
So(len(ledgers), ShouldEqual, 1)
})
Convey("不可重试错误分类应稳定", func() {
So(IsRefundJobNonRetryableError(nil), ShouldBeFalse)
So(IsRefundJobNonRetryableError(errors.New("x")), ShouldBeFalse)
So(IsRefundJobNonRetryableError(errorx.ErrInvalidParameter), ShouldBeTrue)
So(IsRefundJobNonRetryableError(errorx.ErrRecordNotFound), ShouldBeTrue)
So(IsRefundJobNonRetryableError(errorx.ErrStatusConflict), ShouldBeTrue)
So(IsRefundJobNonRetryableError(errorx.ErrPreconditionFailed), ShouldBeTrue)
So(IsRefundJobNonRetryableError(errorx.ErrPermissionDenied), ShouldBeTrue)
So(IsRefundJobNonRetryableError(errorx.ErrInternalError), ShouldBeFalse)
})
}
})
}
func (s *OrderTestSuite) Test_PurchaseContent() {
Convey("Order.PurchaseContent", s.T(), func() {