add ledger test

This commit is contained in:
2025-12-18 15:14:19 +08:00
parent 3c3cc81348
commit 7b95202a8b
2 changed files with 242 additions and 6 deletions

View File

@@ -0,0 +1,202 @@
package services
import (
"context"
"database/sql"
"errors"
"testing"
"time"
"quyun/v2/app/commands/testx"
"quyun/v2/app/errorx"
"quyun/v2/database"
"quyun/v2/database/models"
"quyun/v2/pkg/consts"
. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/suite"
_ "go.ipao.vip/atom"
"go.ipao.vip/atom/contracts"
"go.ipao.vip/gen/types"
"go.uber.org/dig"
)
type LedgerTestSuiteInjectParams struct {
dig.In
DB *sql.DB
Initials []contracts.Initial `group:"initials"` // nolint:structcheck
}
type LedgerTestSuite struct {
suite.Suite
LedgerTestSuiteInjectParams
}
func Test_Ledger(t *testing.T) {
providers := testx.Default().With(Provide)
testx.Serve(providers, t, func(p LedgerTestSuiteInjectParams) {
suite.Run(t, &LedgerTestSuite{LedgerTestSuiteInjectParams: p})
})
}
func (s *LedgerTestSuite) seedTenantUser(ctx context.Context, tenantID, userID, balance, frozen int64) {
database.Truncate(ctx, s.DB, models.TableNameTenantLedger, models.TableNameTenantUser)
tu := &models.TenantUser{
TenantID: tenantID,
UserID: userID,
Role: types.NewArray([]consts.TenantUserRole{consts.TenantUserRoleMember}),
Balance: balance,
BalanceFrozen: frozen,
Status: consts.UserStatusVerified,
}
So(tu.Create(ctx), ShouldBeNil)
}
func (s *LedgerTestSuite) Test_Freeze() {
Convey("Ledger.Freeze", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(1)
userID := int64(2)
now := time.Now().UTC()
s.seedTenantUser(ctx, tenantID, userID, 1000, 0)
Convey("成功冻结", func() {
res, err := Ledger.Freeze(ctx, tenantID, userID, 0, 300, "k_freeze_1", "freeze", now)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
So(res.Ledger, ShouldNotBeNil)
So(res.TenantUser, ShouldNotBeNil)
So(res.Ledger.Type, ShouldEqual, consts.TenantLedgerTypeFreeze)
So(res.Ledger.Amount, ShouldEqual, 300)
So(res.Ledger.BalanceBefore, ShouldEqual, 1000)
So(res.Ledger.BalanceAfter, ShouldEqual, 700)
So(res.Ledger.FrozenBefore, ShouldEqual, 0)
So(res.Ledger.FrozenAfter, ShouldEqual, 300)
So(res.TenantUser.Balance, ShouldEqual, 700)
So(res.TenantUser.BalanceFrozen, ShouldEqual, 300)
})
Convey("幂等键重复调用不应重复扣减", func() {
_, err := Ledger.Freeze(ctx, tenantID, userID, 0, 300, "k_freeze_idem", "freeze", now)
So(err, ShouldBeNil)
res2, err := Ledger.Freeze(ctx, tenantID, userID, 0, 300, "k_freeze_idem", "freeze", now.Add(time.Second))
So(err, ShouldBeNil)
So(res2, ShouldNotBeNil)
So(res2.Ledger, ShouldNotBeNil)
So(res2.Ledger.IdempotencyKey, ShouldEqual, "k_freeze_idem")
var tu2 models.TenantUser
So(_db.WithContext(ctx).Where("tenant_id = ? AND user_id = ?", tenantID, userID).First(&tu2).Error, ShouldBeNil)
So(tu2.Balance, ShouldEqual, 700)
So(tu2.BalanceFrozen, ShouldEqual, 300)
})
Convey("余额不足应返回前置条件失败", func() {
_, err := Ledger.Freeze(ctx, tenantID, userID, 0, 999999, "k_over", "freeze", now)
So(err, ShouldNotBeNil)
var appErr *errorx.AppError
So(errors.As(err, &appErr), ShouldBeTrue)
So(appErr.Code, ShouldEqual, errorx.CodePreconditionFailed)
})
})
}
func (s *LedgerTestSuite) Test_Unfreeze_InsufficientFrozen() {
Convey("Ledger.Unfreeze", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(1)
userID := int64(2)
now := time.Now().UTC()
s.seedTenantUser(ctx, tenantID, userID, 1000, 0)
Convey("冻结余额不足应返回前置条件失败", func() {
_, err := Ledger.Unfreeze(ctx, tenantID, userID, 0, 999999, "k_unfreeze_over", "unfreeze", now)
So(err, ShouldNotBeNil)
var appErr *errorx.AppError
So(errors.As(err, &appErr), ShouldBeTrue)
So(appErr.Code, ShouldEqual, errorx.CodePreconditionFailed)
})
Convey("成功解冻", func() {
_, err := Ledger.Freeze(ctx, tenantID, userID, 0, 300, "k_freeze_for_unfreeze", "freeze", now)
So(err, ShouldBeNil)
res, err := Ledger.Unfreeze(ctx, tenantID, userID, 0, 300, "k_unfreeze_ok", "unfreeze", now)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
So(res.Ledger.Type, ShouldEqual, consts.TenantLedgerTypeUnfreeze)
So(res.TenantUser.Balance, ShouldEqual, 1000)
So(res.TenantUser.BalanceFrozen, ShouldEqual, 0)
})
})
}
func (s *LedgerTestSuite) Test_DebitPurchaseTx() {
Convey("Ledger.DebitPurchaseTx 应减少冻结余额并保留可用余额不变", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(1)
userID := int64(2)
now := time.Now().UTC()
s.seedTenantUser(ctx, tenantID, userID, 1000, 0)
_, err := Ledger.Freeze(ctx, tenantID, userID, 0, 300, "k_freeze_for_debit", "freeze", now)
So(err, ShouldBeNil)
res, err := Ledger.DebitPurchaseTx(ctx, _db, tenantID, userID, 123, 300, "k_debit_1", "debit", now)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
So(res.Ledger.Type, ShouldEqual, consts.TenantLedgerTypeDebitPurchase)
So(res.TenantUser.Balance, ShouldEqual, 700)
So(res.TenantUser.BalanceFrozen, ShouldEqual, 0)
})
}
func (s *LedgerTestSuite) Test_CreditRefundTx() {
Convey("Ledger.CreditRefundTx 应回滚可用余额并保持冻结余额不变", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(1)
userID := int64(2)
now := time.Now().UTC()
s.seedTenantUser(ctx, tenantID, userID, 1000, 0)
_, err := Ledger.Freeze(ctx, tenantID, userID, 0, 300, "k_freeze_for_refund", "freeze", now)
So(err, ShouldBeNil)
_, err = Ledger.DebitPurchaseTx(ctx, _db, tenantID, userID, 123, 300, "k_debit_for_refund", "debit", now)
So(err, ShouldBeNil)
res, err := Ledger.CreditRefundTx(ctx, _db, tenantID, userID, 123, 300, "k_refund_1", "refund", now)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
So(res.Ledger.Type, ShouldEqual, consts.TenantLedgerTypeCreditRefund)
So(res.TenantUser.Balance, ShouldEqual, 1000)
So(res.TenantUser.BalanceFrozen, ShouldEqual, 0)
})
}
func (s *LedgerTestSuite) Test_CreditTopupTx() {
Convey("Ledger.CreditTopupTx 应增加可用余额并写入 credit_topup", s.T(), func() {
ctx := s.T().Context()
tenantID := int64(1)
userID := int64(2)
now := time.Now().UTC()
s.seedTenantUser(ctx, tenantID, userID, 1000, 0)
res, err := Ledger.CreditTopupTx(ctx, _db, tenantID, userID, 456, 200, "k_topup_1", "topup", now)
So(err, ShouldBeNil)
So(res, ShouldNotBeNil)
So(res.Ledger.Type, ShouldEqual, consts.TenantLedgerTypeCreditTopup)
So(res.TenantUser.Balance, ShouldEqual, 1200)
So(res.TenantUser.BalanceFrozen, ShouldEqual, 0)
})
}