2026-07-09 18:15:57 +08:00

175 lines
7.1 KiB
Go

package financeorder
import (
"context"
"database/sql"
"errors"
"strings"
"testing"
"time"
"github.com/DATA-DOG/go-sqlmock"
"hyapp-admin-server/internal/config"
)
func TestLegacyCoinSellerRechargeWriterGrantUpdatesBalanceAndRunningWater(t *testing.T) {
walletDB, walletMock, cleanup := newLegacyWriterSQLMock(t)
defer cleanup()
writer := NewMySQLLegacyCoinSellerRechargeWriter(config.LegacyCoinSellerRechargeSourceConfig{
AppCode: "aslan",
SysOrigin: "ATYOU",
RequestTimeout: 5 * time.Second,
}, nil, walletDB)
grantedAt := time.Date(2026, 7, 9, 9, 30, 0, 0, time.UTC)
walletMock.ExpectBegin()
expectLegacyBalanceLock(walletMock, "ATYOU", 10001).
WillReturnRows(sqlmock.NewRows([]string{"id", "earn_points", "consumption_points"}).AddRow(int64(77), "5000", "1000"))
walletMock.ExpectQuery(`(?s)SELECT id\s+FROM user_freight_balance_running_water\s+WHERE sys_origin = \? AND remark = \?\s+LIMIT 1`).
WithArgs("ATYOU", "MIFA-001").
WillReturnError(sql.ErrNoRows)
walletMock.ExpectExec(`(?s)UPDATE user_freight_balance\s+SET earn_points = COALESCE\(earn_points, 0\) \+ \?, update_user = \?, update_time = \?\s+WHERE id = \?`).
WithArgs(int64(1200), int64(10001), grantedAt, int64(77)).
WillReturnResult(sqlmock.NewResult(0, 1))
walletMock.ExpectExec(`(?s)INSERT INTO user_freight_balance_running_water`).
WithArgs(sqlmock.AnyArg(), "ATYOU", int64(10001), int64(10001), nil, int64(1200), int64(5200), "12.34", "MIFAPAY-进货", "MIFA-001", int64(10001), int64(10001), grantedAt, grantedAt).
WillReturnResult(sqlmock.NewResult(0, 1))
walletMock.ExpectCommit()
result, err := writer.GrantCoinSellerRecharge(context.Background(), legacyRechargeGrantInput{
UserID: 10001,
ExternalOrderNo: "MIFA-001",
ProviderCode: "mifapay",
USDAmount: "12.34",
CoinAmount: 1200,
GrantedAt: grantedAt,
})
if err != nil {
t.Fatalf("legacy grant failed: %v", err)
}
if !strings.HasPrefix(result.TransactionID, "legacy-freight-purchase-") || result.BalanceAfter != 5200 {
t.Fatalf("legacy grant result mismatch: %+v", result)
}
if err := walletMock.ExpectationsWereMet(); err != nil {
t.Fatalf("wallet sql expectations: %v", err)
}
}
func TestLegacyCoinSellerRechargeWriterGrantRejectsDuplicateOrder(t *testing.T) {
walletDB, walletMock, cleanup := newLegacyWriterSQLMock(t)
defer cleanup()
writer := NewMySQLLegacyCoinSellerRechargeWriter(config.LegacyCoinSellerRechargeSourceConfig{
AppCode: "yumi",
SysOrigin: "LIKEI",
RequestTimeout: 5 * time.Second,
}, nil, walletDB)
walletMock.ExpectBegin()
expectLegacyBalanceLock(walletMock, "LIKEI", 20002).
WillReturnRows(sqlmock.NewRows([]string{"id", "earn_points", "consumption_points"}).AddRow(int64(88), "7000", "0"))
walletMock.ExpectQuery(`(?s)SELECT id\s+FROM user_freight_balance_running_water\s+WHERE sys_origin = \? AND remark = \?\s+LIMIT 1`).
WithArgs("LIKEI", "V5-001").
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(int64(9001)))
walletMock.ExpectRollback()
_, err := writer.GrantCoinSellerRecharge(context.Background(), legacyRechargeGrantInput{
UserID: 20002,
ExternalOrderNo: "V5-001",
ProviderCode: "v5pay",
USDAmount: "10.00",
CoinAmount: 1000,
GrantedAt: time.Date(2026, 7, 9, 10, 0, 0, 0, time.UTC),
})
if !errors.Is(err, errLegacyCoinSellerOrderDuplicate) {
t.Fatalf("expected duplicate order error, got %v", err)
}
if err := walletMock.ExpectationsWereMet(); err != nil {
t.Fatalf("wallet sql expectations: %v", err)
}
}
func TestLegacyCoinSellerRechargeWriterResolveCoinSellerByAccountRequiresDealer(t *testing.T) {
appDB, appMock, cleanupApp := newLegacyWriterSQLMock(t)
defer cleanupApp()
walletDB, walletMock, cleanupWallet := newLegacyWriterSQLMock(t)
defer cleanupWallet()
writer := NewMySQLLegacyCoinSellerRechargeWriter(config.LegacyCoinSellerRechargeSourceConfig{
AppCode: "aslan",
SysOrigin: "ATYOU",
RequestTimeout: 5 * time.Second,
}, appDB, walletDB)
expectLegacyUserLookup(appMock, "ATYOU", "agent001").
WillReturnRows(sqlmock.NewRows([]string{"id", "account", "country_id"}).AddRow(int64(10001), "agent001", int64(63)))
expectLegacyCoinSellerCheck(walletMock, "ATYOU", 10001).
WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(int64(77)))
target, err := writer.ResolveCoinSeller(context.Background(), " agent001 ")
if err != nil {
t.Fatalf("resolve legacy coin seller failed: %v", err)
}
if target.UserID != 10001 || target.DisplayUserID != "agent001" || target.CountryID != 63 {
t.Fatalf("legacy coin seller target mismatch: %+v", target)
}
if err := appMock.ExpectationsWereMet(); err != nil {
t.Fatalf("app sql expectations: %v", err)
}
if err := walletMock.ExpectationsWereMet(); err != nil {
t.Fatalf("wallet sql expectations: %v", err)
}
}
func TestLegacyCoinSellerRechargeWriterResolveCoinSellerRejectsNonDealer(t *testing.T) {
appDB, appMock, cleanupApp := newLegacyWriterSQLMock(t)
defer cleanupApp()
walletDB, walletMock, cleanupWallet := newLegacyWriterSQLMock(t)
defer cleanupWallet()
writer := NewMySQLLegacyCoinSellerRechargeWriter(config.LegacyCoinSellerRechargeSourceConfig{
AppCode: "yumi",
SysOrigin: "LIKEI",
RequestTimeout: 5 * time.Second,
}, appDB, walletDB)
expectLegacyUserLookup(appMock, "LIKEI", "visitor001").
WillReturnRows(sqlmock.NewRows([]string{"id", "account", "country_id"}).AddRow(int64(20002), "visitor001", int64(86)))
expectLegacyCoinSellerCheck(walletMock, "LIKEI", 20002).
WillReturnError(sql.ErrNoRows)
_, err := writer.ResolveCoinSeller(context.Background(), "visitor001")
if !errors.Is(err, errLegacyCoinSellerNotFound) {
t.Fatalf("expected non-dealer legacy user to be rejected, got %v", err)
}
if err := appMock.ExpectationsWereMet(); err != nil {
t.Fatalf("app sql expectations: %v", err)
}
if err := walletMock.ExpectationsWereMet(); err != nil {
t.Fatalf("wallet sql expectations: %v", err)
}
}
func expectLegacyBalanceLock(mock sqlmock.Sqlmock, sysOrigin string, userID int64) *sqlmock.ExpectedQuery {
return mock.ExpectQuery(`(?s)SELECT id, CAST\(COALESCE\(earn_points, 0\) AS CHAR\), CAST\(COALESCE\(consumption_points, 0\) AS CHAR\)\s+FROM user_freight_balance\s+WHERE sys_origin = \? AND user_id = \?.*FOR UPDATE`).
WithArgs(sysOrigin, userID)
}
func expectLegacyUserLookup(mock sqlmock.Sqlmock, sysOrigin string, keyword string) *sqlmock.ExpectedQuery {
return mock.ExpectQuery(`(?s)SELECT id, account, COALESCE\(country_id, 0\)\s+FROM user_base_info\s+WHERE origin_sys = \? AND \(account = \? OR CAST\(id AS CHAR\) = \?\).*LIMIT 1`).
WithArgs(sysOrigin, keyword, keyword)
}
func expectLegacyCoinSellerCheck(mock sqlmock.Sqlmock, sysOrigin string, userID int64) *sqlmock.ExpectedQuery {
return mock.ExpectQuery(`(?s)SELECT id\s+FROM user_freight_balance\s+WHERE sys_origin = \? AND user_id = \? AND COALESCE\(is_close, 0\) = 0 AND \(COALESCE\(dealer, 0\) = 1 OR COALESCE\(super_dealer, 0\) = 1\)\s+LIMIT 1`).
WithArgs(sysOrigin, userID)
}
func newLegacyWriterSQLMock(t *testing.T) (*sql.DB, sqlmock.Sqlmock, func()) {
t.Helper()
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("new sqlmock: %v", err)
}
return db, mock, func() {
_ = db.Close()
}
}