1232 lines
35 KiB
Go
1232 lines
35 KiB
Go
package repository
|
||
|
||
import (
|
||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/entity"
|
||
"code.30cm.net/digimon/app-cloudep-wallet-service/pkg/domain/wallet"
|
||
"context"
|
||
"github.com/shopspring/decimal"
|
||
"github.com/stretchr/testify/assert"
|
||
"gorm.io/gorm"
|
||
"testing"
|
||
)
|
||
|
||
func buildSchema(t *testing.T, db *gorm.DB) {
|
||
// 先手動建表
|
||
createTable := `
|
||
CREATE TABLE IF NOT EXISTS wallet (
|
||
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
brand VARCHAR(50) NOT NULL,
|
||
uid VARCHAR(64) NOT NULL,
|
||
asset VARCHAR(32) NOT NULL,
|
||
balance DECIMAL(30,18) NOT NULL DEFAULT 0,
|
||
type TINYINT NOT NULL,
|
||
create_at INTEGER NOT NULL DEFAULT 0,
|
||
update_at INTEGER NOT NULL DEFAULT 0,
|
||
PRIMARY KEY (id),
|
||
UNIQUE KEY uq_brand_uid_asset_type (brand, uid, asset, type)
|
||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
|
||
assert.NoError(t, db.Exec(createTable).Error)
|
||
}
|
||
|
||
func TestUserWalletManager_InitWallets(t *testing.T) {
|
||
_, db, teardown, err := SetupTestWalletRepository()
|
||
assert.NoError(t, err)
|
||
defer teardown()
|
||
|
||
buildSchema(t, db)
|
||
type args struct {
|
||
uid string
|
||
asset string
|
||
brand string
|
||
}
|
||
tests := []struct {
|
||
name string
|
||
args args
|
||
wantCount int
|
||
wantErr bool
|
||
validateDB bool
|
||
}{
|
||
{
|
||
name: "正常初始化一次",
|
||
args: args{uid: "user1", asset: "BTC", brand: "brandA"},
|
||
wantCount: len(wallet.AllTypes),
|
||
},
|
||
{
|
||
name: "再次初始化同一 UID/asset/brand,應因 UNIQUE KEY 失敗",
|
||
args: args{uid: "user1", asset: "BTC", brand: "brandA"},
|
||
wantErr: true,
|
||
validateDB: false,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
service := NewUserWalletManager(db, tt.args.uid, tt.args.asset)
|
||
ctx := context.Background()
|
||
|
||
got, err := service.InitWallets(ctx, tt.args.brand)
|
||
if tt.wantErr {
|
||
assert.Error(t, err)
|
||
return
|
||
}
|
||
assert.NoError(t, err)
|
||
// 回傳 slice 長度應等於 AllTypes
|
||
assert.Len(t, got, tt.wantCount)
|
||
|
||
if tt.validateDB {
|
||
// 再查一次 DB,確認確實寫入
|
||
var dbRows []entity.Wallet
|
||
err := db.WithContext(ctx).
|
||
Where("uid = ? AND asset = ? AND brand = ?", tt.args.uid, tt.args.asset, tt.args.brand).
|
||
Find(&dbRows).Error
|
||
assert.NoError(t, err)
|
||
assert.Len(t, dbRows, tt.wantCount)
|
||
// 檢查每一筆都初始為零
|
||
for _, w := range dbRows {
|
||
assert.Equal(t, decimal.Zero, w.Balance)
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
//
|
||
//func TestUserWalletManager_FetchAllWallets(t *testing.T) {
|
||
// _, db, teardown, err := SetupTestWalletRepository()
|
||
// assert.NoError(t, err)
|
||
// defer teardown()
|
||
//
|
||
// // 建表
|
||
// createTable := `
|
||
// CREATE TABLE IF NOT EXISTS wallet (
|
||
// id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
// brand VARCHAR(50) NOT NULL,
|
||
// uid VARCHAR(64) NOT NULL,
|
||
// asset VARCHAR(32) NOT NULL,
|
||
// balance DECIMAL(30,18) NOT NULL DEFAULT 0,
|
||
// type TINYINT NOT NULL,
|
||
// create_at INTEGER NOT NULL DEFAULT 0,
|
||
// update_at INTEGER NOT NULL DEFAULT 0,
|
||
// PRIMARY KEY (id),
|
||
// UNIQUE KEY uq_brand_uid_asset_type (brand, uid, asset, type)
|
||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
|
||
// assert.NoError(t, db.Exec(createTable).Error)
|
||
//
|
||
// type seedRow struct {
|
||
// Brand string
|
||
// UID string
|
||
// Asset string
|
||
// Balance decimal.Decimal
|
||
// Type wallet.Types
|
||
// }
|
||
//
|
||
// type args struct {
|
||
// uid string
|
||
// asset string
|
||
// brand string
|
||
// }
|
||
// tests := []struct {
|
||
// name string
|
||
// args args
|
||
// seed []seedRow
|
||
// wantCount int
|
||
// }{
|
||
// {
|
||
// name: "no data returns empty",
|
||
// args: args{"u1", "BTC", "b1"},
|
||
// seed: nil,
|
||
// wantCount: 0,
|
||
// },
|
||
// {
|
||
// name: "single user many types",
|
||
// args: args{"u1", "BTC", "b1"},
|
||
// seed: []seedRow{
|
||
// {"b1", "u1", "BTC", decimal.NewFromInt(10), wallet.AllTypes[0]},
|
||
// {"b1", "u1", "BTC", decimal.NewFromInt(20), wallet.AllTypes[1]},
|
||
// {"b1", "u1", "BTC", decimal.NewFromInt(30), wallet.AllTypes[2]},
|
||
// },
|
||
// wantCount: 3,
|
||
// },
|
||
// {
|
||
// name: "mixed users and assets",
|
||
// args: args{"u2", "ETH", "b2"},
|
||
// seed: []seedRow{
|
||
// {"b1", "u1", "BTC", decimal.NewFromInt(10), wallet.AllTypes[0]},
|
||
// {"b2", "u2", "ETH", decimal.NewFromInt(15), wallet.AllTypes[1]},
|
||
// {"b2", "u2", "ETH", decimal.NewFromInt(25), wallet.AllTypes[2]},
|
||
// },
|
||
// wantCount: 2,
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tt := range tests {
|
||
// t.Run(tt.name, func(t *testing.T) {
|
||
// ctx := context.Background()
|
||
// // 清空表
|
||
// assert.NoError(t, db.Exec(`DELETE FROM wallet`).Error)
|
||
//
|
||
// // 插入 seed
|
||
// for _, row := range tt.seed {
|
||
// err := db.WithContext(ctx).Exec(
|
||
// `INSERT INTO wallet (brand, uid, asset, balance, type, create_at, update_at)
|
||
// VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||
// row.Brand, row.UID, row.Asset, row.Balance, row.Type,
|
||
// time.Now().Unix(), time.Now().Unix(),
|
||
// ).Error
|
||
// assert.NoError(t, err)
|
||
// }
|
||
//
|
||
// // 建立 service
|
||
// svc := NewWalletService(db, tt.args.uid, tt.args.asset)
|
||
//
|
||
// // 呼叫 GetAllBalances
|
||
// got, err := svc.GetAllBalances(ctx)
|
||
// assert.NoError(t, err)
|
||
// assert.Len(t, got, tt.wantCount)
|
||
//
|
||
// // 檢查回傳資料與本地快取一致
|
||
// ws := svc.(*WalletService)
|
||
// for _, w := range got {
|
||
// // 回傳的每筆都應該存在於 localBalances
|
||
// cached, ok := ws.localBalances[w.Type]
|
||
// assert.True(t, ok)
|
||
// assert.Equal(t, w.Balance, cached.Balance)
|
||
// assert.Equal(t, w.Asset, cached.Asset)
|
||
// assert.Equal(t, w.Type, cached.Type)
|
||
// }
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//func TestUserWalletManager_GetBalancesForTypes(t *testing.T) {
|
||
// _, db, teardown, err := SetupTestWalletRepository()
|
||
// assert.NoError(t, err)
|
||
// defer teardown()
|
||
//
|
||
// // 建表
|
||
// createTable := `
|
||
// CREATE TABLE IF NOT EXISTS wallet (
|
||
// id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
// brand VARCHAR(50) NOT NULL,
|
||
// uid VARCHAR(64) NOT NULL,
|
||
// asset VARCHAR(32) NOT NULL,
|
||
// balance DECIMAL(30,18) NOT NULL DEFAULT 0,
|
||
// type TINYINT NOT NULL,
|
||
// create_at INTEGER NOT NULL DEFAULT 0,
|
||
// update_at INTEGER NOT NULL DEFAULT 0,
|
||
// PRIMARY KEY (id),
|
||
// UNIQUE KEY uq_brand_uid_asset_type (brand, uid, asset, type)
|
||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
|
||
// assert.NoError(t, db.Exec(createTable).Error)
|
||
//
|
||
// type seedRow struct {
|
||
// Brand string
|
||
// UID string
|
||
// Asset string
|
||
// Balance decimal.Decimal
|
||
// Type wallet.Types
|
||
// }
|
||
//
|
||
// tests := []struct {
|
||
// name string
|
||
// uid string
|
||
// asset string
|
||
// brand string
|
||
// seed []seedRow
|
||
// kinds []wallet.Types
|
||
// wantCount int
|
||
// }{
|
||
// {
|
||
// name: "no matching types returns empty",
|
||
// uid: "user1", asset: "BTC", brand: "b1",
|
||
// seed: []seedRow{
|
||
// {"b1", "user1", "BTC", decimal.NewFromInt(5), wallet.AllTypes[0]},
|
||
// },
|
||
// kinds: []wallet.Types{wallet.AllTypes[1]},
|
||
// wantCount: 0,
|
||
// },
|
||
// {
|
||
// name: "single type match",
|
||
// uid: "user1", asset: "BTC", brand: "b1",
|
||
// seed: []seedRow{
|
||
// {"b1", "user1", "BTC", decimal.NewFromInt(5), wallet.AllTypes[0]},
|
||
// {"b1", "user1", "BTC", decimal.NewFromInt(7), wallet.AllTypes[1]},
|
||
// },
|
||
// kinds: []wallet.Types{wallet.AllTypes[1]},
|
||
// wantCount: 1,
|
||
// },
|
||
// {
|
||
// name: "multiple type matches",
|
||
// uid: "user2", asset: "ETH", brand: "b2",
|
||
// seed: []seedRow{
|
||
// {"b2", "user2", "ETH", decimal.NewFromInt(3), wallet.AllTypes[0]},
|
||
// {"b2", "user2", "ETH", decimal.NewFromInt(8), wallet.AllTypes[2]},
|
||
// {"b2", "user2", "ETH", decimal.NewFromInt(10), wallet.AllTypes[1]},
|
||
// },
|
||
// kinds: []wallet.Types{wallet.AllTypes[0], wallet.AllTypes[2]},
|
||
// wantCount: 2,
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tt := range tests {
|
||
// t.Run(tt.name, func(t *testing.T) {
|
||
// ctx := context.Background()
|
||
// // 清表
|
||
// assert.NoError(t, db.Exec(`DELETE FROM wallet`).Error)
|
||
//
|
||
// // 插入種子資料
|
||
// for _, r := range tt.seed {
|
||
// assert.NoError(t, db.Exec(
|
||
// `INSERT INTO wallet (brand, uid, asset, balance, type, create_at, update_at)
|
||
// VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||
// r.Brand, r.UID, r.Asset, r.Balance, r.Type,
|
||
// time.Now().Unix(), time.Now().Unix(),
|
||
// ).Error)
|
||
// }
|
||
//
|
||
// // 建立 service
|
||
// svc := NewWalletService(db, tt.uid, tt.asset)
|
||
//
|
||
// // 呼叫 GetBalancesForTypes
|
||
// got, err := svc.GetBalancesForTypes(ctx, tt.kinds)
|
||
// assert.NoError(t, err)
|
||
// assert.Len(t, got, tt.wantCount)
|
||
//
|
||
// // 檢查每筆結果皆正確緩存
|
||
// ws := svc.(*WalletService)
|
||
// for _, w := range got {
|
||
// cached, ok := ws.localBalances[w.Type]
|
||
// assert.True(t, ok)
|
||
// assert.Equal(t, w.Balance, cached.Balance)
|
||
// assert.Equal(t, w.Asset, cached.Asset)
|
||
// }
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//func TestForUpdateLockBehavior(t *testing.T) {
|
||
// // 建立測試環境
|
||
// _, db, teardown, err := SetupTestWalletRepository()
|
||
// assert.NoError(t, err)
|
||
// defer teardown()
|
||
//
|
||
// // 建表
|
||
// createTable := `
|
||
// CREATE TABLE IF NOT EXISTS wallet (
|
||
// id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
// brand VARCHAR(50) NOT NULL,
|
||
// uid VARCHAR(64) NOT NULL,
|
||
// asset VARCHAR(32) NOT NULL,
|
||
// balance DECIMAL(30,18) NOT NULL DEFAULT 0,
|
||
// type TINYINT NOT NULL,
|
||
// create_at INTEGER NOT NULL DEFAULT 0,
|
||
// update_at INTEGER NOT NULL DEFAULT 0,
|
||
// PRIMARY KEY (id),
|
||
// UNIQUE KEY uq_brand_uid_asset_type (brand, uid, asset, type)
|
||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
|
||
// assert.NoError(t, db.Exec(createTable).Error)
|
||
//
|
||
// // 種子資料:一筆 wallet
|
||
// ctx := context.Background()
|
||
// initial := entity.Wallet{
|
||
// Brand: "b1",
|
||
// UID: "user1",
|
||
// Asset: "BTC",
|
||
// Balance: decimal.NewFromInt(100),
|
||
// Type: wallet.AllTypes[0],
|
||
// }
|
||
// err = db.WithContext(ctx).Create(&initial).Error
|
||
// assert.NoError(t, err)
|
||
//
|
||
// // 讀回自動產生的 ID
|
||
// var seeded entity.Wallet
|
||
// assert.NoError(t, db.Where("uid = ? AND asset = ?", initial.UID, initial.Asset).
|
||
// First(&seeded).Error)
|
||
//
|
||
// // 開啟第一個 transaction 並 SELECT … FOR UPDATE
|
||
// tx1 := db.Begin()
|
||
// var locked entity.Wallet
|
||
// err = tx1.Clauses(clause.Locking{Strength: "UPDATE"}).
|
||
// WithContext(ctx).
|
||
// Where("id = ?", seeded.ID).
|
||
// Take(&locked).Error
|
||
// assert.NoError(t, err)
|
||
//
|
||
// // 啟動 goroutine 嘗試在第二 transaction 更新該 row
|
||
// done := make(chan error, 1)
|
||
// go func() {
|
||
// tx2 := db.Begin()
|
||
// // 試圖更新,被 FOR UPDATE 鎖住前應該會 block
|
||
// err := tx2.WithContext(ctx).
|
||
// Model(&entity.Wallet{}).
|
||
// Where("id = ?", seeded.ID).
|
||
// Update("balance", seeded.Balance.Add(decimal.NewFromInt(50))).Error
|
||
// done <- err
|
||
// }()
|
||
//
|
||
// // 等 100ms 確認尚未完成
|
||
// time.Sleep(100 * time.Millisecond)
|
||
// select {
|
||
// case err2 := <-done:
|
||
// t.Fatalf("expected update to be blocked, but completed with err=%v", err2)
|
||
// default:
|
||
// // 正在等待鎖
|
||
// }
|
||
//
|
||
// // 釋放鎖
|
||
// assert.NoError(t, tx1.Commit().Error)
|
||
//
|
||
// // 現在第二 transaction 應該很快完成
|
||
// select {
|
||
// case err2 := <-done:
|
||
// assert.NoError(t, err2)
|
||
// case <-time.After(500 * time.Millisecond):
|
||
// t.Fatal("update did not complete after lock released")
|
||
// }
|
||
//}
|
||
//
|
||
//func TestWalletService_IncreaseBalance(t *testing.T) {
|
||
// uid := "user1"
|
||
// asset := "BTC"
|
||
//
|
||
// type testCase struct {
|
||
// name string
|
||
// kind wallet.Types
|
||
// initial *entity.Wallet // nil 表示不存在
|
||
// orderID string
|
||
// amount decimal.Decimal
|
||
// wantErr error
|
||
// wantBalance decimal.Decimal
|
||
// wantTxCount int
|
||
// }
|
||
// tests := []testCase{
|
||
// {
|
||
// name: "missing wallet type",
|
||
// kind: wallet.AllTypes[0],
|
||
// initial: nil,
|
||
// orderID: "ord1",
|
||
// amount: decimal.NewFromInt(10),
|
||
// wantErr: repository.ErrRecordNotFound,
|
||
// wantBalance: decimal.Zero,
|
||
// wantTxCount: 0,
|
||
// },
|
||
// {
|
||
// name: "increase from zero",
|
||
// kind: wallet.AllTypes[1],
|
||
// initial: &entity.Wallet{Balance: decimal.Zero},
|
||
// orderID: "ord2",
|
||
// amount: decimal.NewFromInt(15),
|
||
// wantErr: nil,
|
||
// wantBalance: decimal.NewFromInt(15),
|
||
// wantTxCount: 1,
|
||
// },
|
||
// {
|
||
// name: "successful increment on non-zero",
|
||
// kind: wallet.AllTypes[2],
|
||
// initial: &entity.Wallet{Balance: decimal.NewFromInt(5)},
|
||
// orderID: "ord3",
|
||
// amount: decimal.NewFromInt(7),
|
||
// wantErr: nil,
|
||
// wantBalance: decimal.NewFromInt(12),
|
||
// wantTxCount: 1,
|
||
// },
|
||
// {
|
||
// name: "insufficient leads to error",
|
||
// kind: wallet.AllTypes[2],
|
||
// initial: &entity.Wallet{Balance: decimal.NewFromInt(3)},
|
||
// orderID: "ord4",
|
||
// amount: decimal.NewFromInt(-5),
|
||
// wantErr: repository.ErrBalanceInsufficient,
|
||
// wantBalance: decimal.NewFromInt(3),
|
||
// wantTxCount: 0,
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tc := range tests {
|
||
// t.Run(tc.name, func(t *testing.T) {
|
||
// // 準備 WalletService
|
||
// svc := NewWalletService(nil, uid, asset).(*WalletService)
|
||
// // 初始化本地快取
|
||
// if tc.initial != nil {
|
||
// svc.localBalances = map[wallet.Types]entity.Wallet{tc.kind: *tc.initial}
|
||
// } else {
|
||
// svc.localBalances = map[wallet.Types]entity.Wallet{}
|
||
// }
|
||
// // 清空交易紀錄
|
||
// svc.transactions = nil
|
||
//
|
||
// // 執行
|
||
// err := svc.IncreaseBalance(tc.kind, tc.orderID, tc.amount)
|
||
//
|
||
// // 驗證錯誤
|
||
// if tc.wantErr != nil {
|
||
// assert.ErrorIs(t, err, tc.wantErr)
|
||
// } else {
|
||
// assert.NoError(t, err)
|
||
// }
|
||
//
|
||
// // 驗證快取餘額
|
||
// gotBal := svc.CurrentBalance(tc.kind)
|
||
// assert.True(t, gotBal.Equal(tc.wantBalance), "balance = %s, want %s", gotBal, tc.wantBalance)
|
||
//
|
||
// // 驗證交易筆數
|
||
// assert.Len(t, svc.transactions, tc.wantTxCount)
|
||
// if tc.wantTxCount > 0 {
|
||
// tx := svc.transactions[0]
|
||
// assert.Equal(t, tc.orderID, tx.OrderID)
|
||
// assert.Equal(t, uid, tx.UID)
|
||
// assert.Equal(t, asset, tx.Asset)
|
||
// assert.True(t, tx.Amount.Equal(tc.amount))
|
||
// assert.True(t, tx.Balance.Equal(tc.wantBalance))
|
||
// }
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//func TestWalletService_PrepareTransactions(t *testing.T) {
|
||
// const (
|
||
// txID = int64(42)
|
||
// orderID = "order-123"
|
||
// brand = "brandX"
|
||
// bizNameStr = "business-test"
|
||
// )
|
||
// biz := wallet.BusinessName(bizNameStr)
|
||
//
|
||
// tests := []struct {
|
||
// name string
|
||
// initialTxs []entity.WalletTransaction
|
||
// wantCount int
|
||
// }{
|
||
// {
|
||
// name: "no transactions returns empty slice",
|
||
// initialTxs: nil,
|
||
// wantCount: 0,
|
||
// },
|
||
// {
|
||
// name: "single transaction is populated",
|
||
// initialTxs: []entity.WalletTransaction{
|
||
// {
|
||
// OrderID: "placeholder",
|
||
// UID: "u1",
|
||
// WalletType: wallet.AllTypes[0],
|
||
// Asset: "BTC",
|
||
// Amount: decimal.NewFromInt(5),
|
||
// Balance: decimal.NewFromInt(10),
|
||
// },
|
||
// },
|
||
// wantCount: 1,
|
||
// },
|
||
// {
|
||
// name: "multiple transactions are all populated",
|
||
// initialTxs: []entity.WalletTransaction{
|
||
// {UID: "u1", WalletType: wallet.AllTypes[1], Asset: "ETH", Amount: decimal.NewFromInt(1), Balance: decimal.NewFromInt(1)},
|
||
// {UID: "u2", WalletType: wallet.AllTypes[2], Asset: "TWD", Amount: decimal.NewFromInt(2), Balance: decimal.NewFromInt(2)},
|
||
// {UID: "u3", WalletType: wallet.AllTypes[3%len(wallet.AllTypes)], Asset: "USD", Amount: decimal.NewFromInt(3), Balance: decimal.NewFromInt(3)},
|
||
// },
|
||
// wantCount: 3,
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tc := range tests {
|
||
// t.Run(tc.name, func(t *testing.T) {
|
||
// // 建立 WalletService 並注入初始交易
|
||
// svc := &WalletService{
|
||
// transactions: make([]entity.WalletTransaction, len(tc.initialTxs)),
|
||
// }
|
||
// copy(svc.transactions, tc.initialTxs)
|
||
//
|
||
// // 執行 PrepareTransactions
|
||
// result := svc.PrepareTransactions(txID, orderID, brand, biz)
|
||
//
|
||
// // 檢查回傳長度
|
||
// assert.Len(t, result, tc.wantCount)
|
||
// assert.Len(t, svc.transactions, tc.wantCount)
|
||
//
|
||
// // 驗證每筆交易的共用欄位是否正確
|
||
// for i := 0; i < tc.wantCount; i++ {
|
||
// tx := result[i]
|
||
// assert.Equal(t, txID, tx.TransactionID, "tx[%d].TransactionID", i)
|
||
// assert.Equal(t, orderID, tx.OrderID, "tx[%d].OrderID", i)
|
||
// assert.Equal(t, brand, tx.Brand, "tx[%d].Brand", i)
|
||
// assert.Equal(t, biz.ToInt8(), tx.BusinessType, "tx[%d].BusinessType", i)
|
||
// // 原有欄位保持不變
|
||
// assert.Equal(t, tc.initialTxs[i].UID, tx.UID, "tx[%d].UID unchanged", i)
|
||
// assert.Equal(t, tc.initialTxs[i].WalletType, tx.WalletType, "tx[%d].WalletType unchanged", i)
|
||
// assert.Equal(t, tc.initialTxs[i].Asset, tx.Asset, "tx[%d].Asset unchanged", i)
|
||
// assert.True(t, tx.Amount.Equal(tc.initialTxs[i].Amount), "tx[%d].Amount unchanged", i)
|
||
// assert.True(t, tx.Balance.Equal(tc.initialTxs[i].Balance), "tx[%d].Balance unchanged", i)
|
||
// }
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//func TestWalletService_PersistBalances(t *testing.T) {
|
||
// _, db, teardown, err := SetupTestWalletRepository()
|
||
// assert.NoError(t, err)
|
||
// defer teardown()
|
||
//
|
||
// // helper:删除表、重建表、插入两笔 seed 数据,返回这两笔带 ID 的 slice
|
||
// seedWallets := func() []entity.Wallet {
|
||
// // DROP + AutoMigrate
|
||
// assert.NoError(t, db.Migrator().DropTable(&entity.Wallet{}))
|
||
// assert.NoError(t, db.AutoMigrate(&entity.Wallet{}))
|
||
//
|
||
// base := []entity.Wallet{
|
||
// {UID: "u1", Asset: "BTC", Brand: "b", Balance: decimal.NewFromInt(10), Type: wallet.AllTypes[0]},
|
||
// {UID: "u1", Asset: "BTC", Brand: "b", Balance: decimal.NewFromInt(20), Type: wallet.AllTypes[1]},
|
||
// }
|
||
// assert.NoError(t, db.Create(&base).Error)
|
||
// return base
|
||
// }
|
||
//
|
||
// type fields struct {
|
||
// updates map[wallet.Types]decimal.Decimal
|
||
// }
|
||
// type want struct {
|
||
// final []decimal.Decimal
|
||
// err bool
|
||
// }
|
||
//
|
||
// tests := []struct {
|
||
// name string
|
||
// fields fields
|
||
// want want
|
||
// }{
|
||
// {
|
||
// name: "no local balances → no change",
|
||
// fields: fields{updates: map[wallet.Types]decimal.Decimal{}},
|
||
// want: want{
|
||
// final: []decimal.Decimal{decimal.NewFromInt(10), decimal.NewFromInt(20)},
|
||
// err: false,
|
||
// },
|
||
// },
|
||
// {
|
||
// name: "update both balances",
|
||
// fields: fields{updates: map[wallet.Types]decimal.Decimal{
|
||
// wallet.AllTypes[0]: decimal.NewFromInt(15),
|
||
// wallet.AllTypes[1]: decimal.NewFromInt(5),
|
||
// }},
|
||
// want: want{
|
||
// final: []decimal.Decimal{decimal.NewFromInt(15), decimal.NewFromInt(5)},
|
||
// err: false,
|
||
// },
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tc := range tests {
|
||
// t.Run(tc.name, func(t *testing.T) {
|
||
// // seed 并拿到带 ID 的记录
|
||
// seed := seedWallets()
|
||
//
|
||
// // 构造 localBalances:只有 Type 在 updates 里的才覆盖
|
||
// localMap := make(map[wallet.Types]entity.Wallet, len(tc.fields.updates))
|
||
// for _, w := range seed {
|
||
// if nb, ok := tc.fields.updates[w.Type]; ok {
|
||
// localMap[w.Type] = entity.Wallet{
|
||
// ID: w.ID,
|
||
// Balance: nb,
|
||
// }
|
||
// }
|
||
// }
|
||
//
|
||
// // 初始化 service 并注入 localBalances
|
||
// svc := NewWalletService(db, "u1", "BTC").(*WalletService)
|
||
// svc.localBalances = localMap
|
||
//
|
||
// // 执行 PersistBalances
|
||
// err := svc.PersistBalances(context.Background())
|
||
// if tc.want.err {
|
||
// assert.Error(t, err)
|
||
// return
|
||
// }
|
||
// assert.NoError(t, err)
|
||
//
|
||
// // 重新查 DB,按 ID 顺序比较余额
|
||
// var got []entity.Wallet
|
||
// assert.NoError(t, db.
|
||
// Where("uid = ?", "u1").
|
||
// Order("id").
|
||
// Find(&got).Error)
|
||
//
|
||
// assert.Len(t, got, len(seed))
|
||
// for i, w := range got {
|
||
// assert.Truef(t,
|
||
// w.Balance.Equal(tc.want.final[i]),
|
||
// "第 %d 条记录 (id=%d) 余额 = %s, 期望 %s",
|
||
// i, w.ID, w.Balance, tc.want.final[i],
|
||
// )
|
||
// }
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//func TestWalletService_PersistOrderBalances(t *testing.T) {
|
||
// _, db, teardown, err := SetupTestWalletRepository()
|
||
// assert.NoError(t, err)
|
||
// defer teardown()
|
||
//
|
||
// // helper:重建 transaction 表並 seed 資料
|
||
// seedTransactions := func() []entity.Transaction {
|
||
// // DROP + AutoMigrate
|
||
// _ = db.Migrator().DropTable(&entity.Transaction{})
|
||
// assert.NoError(t, db.AutoMigrate(&entity.Transaction{}))
|
||
//
|
||
// // 插入兩筆 transaction
|
||
// base := []entity.Transaction{
|
||
// {PostTransferBalance: decimal.NewFromInt(100)},
|
||
// {PostTransferBalance: decimal.NewFromInt(200)},
|
||
// }
|
||
// assert.NoError(t, db.Create(&base).Error)
|
||
// return base
|
||
// }
|
||
//
|
||
// type fields struct {
|
||
// updates map[int64]decimal.Decimal
|
||
// }
|
||
// type want struct {
|
||
// final []decimal.Decimal
|
||
// err bool
|
||
// }
|
||
//
|
||
// tests := []struct {
|
||
// name string
|
||
// fields fields
|
||
// want want
|
||
// }{
|
||
// {
|
||
// name: "no local order balances → no change",
|
||
// fields: fields{updates: map[int64]decimal.Decimal{}},
|
||
// want: want{
|
||
// final: []decimal.Decimal{decimal.NewFromInt(100), decimal.NewFromInt(200)},
|
||
// err: false,
|
||
// },
|
||
// },
|
||
// {
|
||
// name: "update first order balance only",
|
||
// fields: fields{updates: map[int64]decimal.Decimal{
|
||
// 1: decimal.NewFromInt(150),
|
||
// }},
|
||
// want: want{
|
||
// final: []decimal.Decimal{decimal.NewFromInt(150), decimal.NewFromInt(200)},
|
||
// err: false,
|
||
// },
|
||
// },
|
||
// {
|
||
// name: "update both order balances",
|
||
// fields: fields{updates: map[int64]decimal.Decimal{
|
||
// 1: decimal.NewFromInt(110),
|
||
// 2: decimal.NewFromInt(220),
|
||
// }},
|
||
// want: want{
|
||
// final: []decimal.Decimal{decimal.NewFromInt(110), decimal.NewFromInt(220)},
|
||
// err: false,
|
||
// },
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tc := range tests {
|
||
// t.Run(tc.name, func(t *testing.T) {
|
||
// // seed 資料並拿回 slice(包含自動產生的 ID)
|
||
// seed := seedTransactions()
|
||
//
|
||
// // 建構 localOrderBalances:key 是 seed[i].ID
|
||
// localMap := make(map[int64]decimal.Decimal, len(tc.fields.updates))
|
||
// for id, newBal := range tc.fields.updates {
|
||
// localMap[id] = newBal
|
||
// }
|
||
//
|
||
// // 初始化 service、注入 localOrderBalances
|
||
// svc := NewWalletService(db, "u1", "BTC").(*WalletService)
|
||
// svc.localOrderBalances = localMap
|
||
//
|
||
// // 執行 PersistOrderBalances
|
||
// err := svc.PersistOrderBalances(context.Background())
|
||
// if tc.want.err {
|
||
// assert.Error(t, err)
|
||
// return
|
||
// }
|
||
// assert.NoError(t, err)
|
||
//
|
||
// // 依照 seed 的 ID 順序讀回資料庫
|
||
// var got []entity.Transaction
|
||
// assert.NoError(t, db.
|
||
// Where("1 = 1").
|
||
// Order("id").
|
||
// Find(&got).Error)
|
||
// // 長度要和 seed 相同
|
||
// assert.Len(t, got, len(seed))
|
||
// // 比對每一筆 balance
|
||
// for i, tr := range got {
|
||
// assert.Truef(t,
|
||
// tr.PostTransferBalance.Equal(tc.want.final[i]),
|
||
// "第 %d 筆交易(id=%d) balance = %s, want %s",
|
||
// i, tr.ID, tr.PostTransferBalance, tc.want.final[i],
|
||
// )
|
||
// }
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//func TestWalletService_HasAvailableBalance(t *testing.T) {
|
||
// _, db, teardown, err := SetupTestWalletRepository()
|
||
// assert.NoError(t, err)
|
||
// defer teardown()
|
||
//
|
||
// // 建表
|
||
// createTable := `
|
||
// CREATE TABLE IF NOT EXISTS wallet (
|
||
// id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
|
||
// brand VARCHAR(50) NOT NULL,
|
||
// uid VARCHAR(64) NOT NULL,
|
||
// asset VARCHAR(32) NOT NULL,
|
||
// balance DECIMAL(30,18) NOT NULL DEFAULT 0,
|
||
// type TINYINT NOT NULL,
|
||
// create_at INTEGER NOT NULL DEFAULT 0,
|
||
// update_at INTEGER NOT NULL DEFAULT 0,
|
||
// PRIMARY KEY (id),
|
||
// UNIQUE KEY uq_brand_uid_asset_type (brand, uid, asset, type)
|
||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
|
||
// assert.NoError(t, db.Exec(createTable).Error)
|
||
//
|
||
// type seedRow struct {
|
||
// Brand string
|
||
// UID string
|
||
// Asset string
|
||
// Type wallet.Types
|
||
// Balance decimal.Decimal
|
||
// }
|
||
//
|
||
// tests := []struct {
|
||
// name string
|
||
// uid string
|
||
// asset string
|
||
// seed []seedRow
|
||
// wantExist bool
|
||
// }{
|
||
// {
|
||
// name: "無任何紀錄",
|
||
// uid: "user1",
|
||
// asset: "BTC",
|
||
// seed: nil,
|
||
// wantExist: false,
|
||
// },
|
||
// {
|
||
// name: "只有其他類型錢包",
|
||
// uid: "user1",
|
||
// asset: "BTC",
|
||
// seed: []seedRow{
|
||
// {"", "user1", "BTC", wallet.TypeFreeze, decimal.Zero},
|
||
// {"", "user1", "BTC", wallet.TypeUnconfirmed, decimal.Zero},
|
||
// },
|
||
// wantExist: false,
|
||
// },
|
||
// {
|
||
// name: "已有可用錢包",
|
||
// uid: "user1",
|
||
// asset: "BTC",
|
||
// seed: []seedRow{
|
||
// {"", "user1", "BTC", wallet.TypeAvailable, decimal.NewFromInt(10)},
|
||
// },
|
||
// wantExist: true,
|
||
// },
|
||
// {
|
||
// name: "不同 UID 不算",
|
||
// uid: "user2",
|
||
// asset: "BTC",
|
||
// seed: []seedRow{
|
||
// {"", "user1", "BTC", wallet.TypeAvailable, decimal.NewFromInt(5)},
|
||
// },
|
||
// wantExist: false,
|
||
// },
|
||
// {
|
||
// name: "不同 Asset 不算",
|
||
// uid: "user1",
|
||
// asset: "ETH",
|
||
// seed: []seedRow{
|
||
// {"", "user1", "BTC", wallet.TypeAvailable, decimal.NewFromInt(5)},
|
||
// },
|
||
// wantExist: false,
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tc := range tests {
|
||
// t.Run(tc.name, func(t *testing.T) {
|
||
// // 每個子測試前都清空 wallet table
|
||
// assert.NoError(t, db.Exec("DELETE FROM wallet").Error)
|
||
//
|
||
// // seed 資料到 wallet
|
||
// for _, r := range tc.seed {
|
||
// w := entity.Wallet{
|
||
// Brand: r.Brand,
|
||
// UID: r.UID,
|
||
// Asset: r.Asset,
|
||
// Type: r.Type,
|
||
// Balance: r.Balance,
|
||
// }
|
||
// assert.NoError(t, db.Create(&w).Error)
|
||
// }
|
||
//
|
||
// // 建 service
|
||
// svc := NewWalletService(db, tc.uid, tc.asset).(*WalletService)
|
||
// got, err := svc.HasAvailableBalance(context.Background())
|
||
// assert.NoError(t, err)
|
||
// assert.Equal(t, tc.wantExist, got)
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//
|
||
//
|
||
//func TestWalletService_GetBalancesForUpdate(t *testing.T) {
|
||
// db, tearDown := SetupTestDB(t)
|
||
// defer tearDown()
|
||
//
|
||
// type seedRow struct {
|
||
// Brand string
|
||
// UID string
|
||
// Asset string
|
||
// Type wallet.Types
|
||
// Balance decimal.Decimal
|
||
// }
|
||
//
|
||
// tests := []struct {
|
||
// name string
|
||
// uid string
|
||
// asset string
|
||
// seed []seedRow
|
||
// kinds []wallet.Types
|
||
// wantIDs []int64
|
||
// expectErr bool
|
||
// }{
|
||
// {
|
||
// name: "查詢空結果",
|
||
// uid: "u1",
|
||
// asset: "BTC",
|
||
// seed: nil,
|
||
// kinds: []wallet.Types{wallet.TypeAvailable},
|
||
// wantIDs: nil,
|
||
// expectErr: false,
|
||
// },
|
||
// {
|
||
// name: "單一類型查詢",
|
||
// uid: "u1",
|
||
// asset: "BTC",
|
||
// seed: []seedRow{
|
||
// {"", "u1", "BTC", wallet.TypeAvailable, decimal.NewFromInt(5)},
|
||
// {"", "u1", "BTC", wallet.TypeFreeze, decimal.NewFromInt(2)},
|
||
// },
|
||
// kinds: []wallet.Types{wallet.TypeFreeze},
|
||
// wantIDs: []int64{2},
|
||
// expectErr: false,
|
||
// },
|
||
// {
|
||
// name: "多類型查詢",
|
||
// uid: "u1",
|
||
// asset: "BTC",
|
||
// seed: []seedRow{
|
||
// {"", "u1", "BTC", wallet.TypeAvailable, decimal.NewFromInt(5)},
|
||
// {"", "u1", "BTC", wallet.TypeFreeze, decimal.NewFromInt(2)},
|
||
// {"", "u1", "BTC", wallet.TypeUnconfirmed, decimal.NewFromInt(3)},
|
||
// },
|
||
// kinds: []wallet.Types{wallet.TypeAvailable, wallet.TypeUnconfirmed},
|
||
// wantIDs: []int64{3, 5},
|
||
// expectErr: false,
|
||
// },
|
||
// {
|
||
// name: "不同 UID 不列入",
|
||
// uid: "u2",
|
||
// asset: "BTC",
|
||
// seed: []seedRow{
|
||
// {"", "u1", "BTC", wallet.TypeAvailable, decimal.NewFromInt(5)},
|
||
// },
|
||
// kinds: []wallet.Types{wallet.TypeAvailable},
|
||
// wantIDs: nil,
|
||
// expectErr: false,
|
||
// },
|
||
// {
|
||
// name: "不同 Asset 不列入",
|
||
// uid: "u1",
|
||
// asset: "ETH",
|
||
// seed: []seedRow{
|
||
// {"", "u1", "BTC", wallet.TypeAvailable, decimal.NewFromInt(5)},
|
||
// },
|
||
// kinds: []wallet.Types{wallet.TypeAvailable},
|
||
// wantIDs: nil,
|
||
// expectErr: false,
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tc := range tests {
|
||
// t.Run(tc.name, func(t *testing.T) {
|
||
// // 清空資料
|
||
// assert.NoError(t, db.Exec("DELETE FROM wallet").Error)
|
||
// // Seed
|
||
// for _, r := range tc.seed {
|
||
// w := entity.Wallet{
|
||
// Brand: r.Brand,
|
||
// UID: r.UID,
|
||
// Asset: r.Asset,
|
||
// Type: r.Type,
|
||
// Balance: r.Balance,
|
||
// }
|
||
// // Create will auto-assign incremental IDs
|
||
// assert.NoError(t, db.Create(&w).Error)
|
||
// }
|
||
//
|
||
// // 建 Service
|
||
// svc := NewWalletService(db, tc.uid, tc.asset).(*WalletService)
|
||
// got, err := svc.GetBalancesForUpdate(context.Background(), tc.kinds)
|
||
// if tc.expectErr {
|
||
// assert.Error(t, err)
|
||
// return
|
||
// }
|
||
// assert.NoError(t, err)
|
||
//
|
||
// // 取回 IDs 並排序
|
||
// var gotIDs []int64
|
||
// for _, w := range got {
|
||
// gotIDs = append(gotIDs, w.ID)
|
||
// // localBalances 應該被更新
|
||
// assert.Equal(t, w, svc.localBalances[w.Type])
|
||
// }
|
||
// assert.Equal(t, tc.wantIDs, gotIDs)
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//func TestWalletService_GetOrderBalance(t *testing.T) {
|
||
// _, db, tearDown, err := SetupTestWalletRepository()
|
||
// assert.NoError(t, err)
|
||
// defer tearDown()
|
||
//
|
||
// // create transaction table
|
||
// create := `
|
||
// CREATE TABLE IF NOT EXISTS transaction (
|
||
// id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
// order_id VARCHAR(64),
|
||
// transaction_id VARCHAR(64),
|
||
// brand VARCHAR(32),
|
||
// uid VARCHAR(64),
|
||
// to_uid VARCHAR(64),
|
||
// type TINYINT,
|
||
// business_type TINYINT,
|
||
// asset VARCHAR(32),
|
||
// amount DECIMAL(30,18),
|
||
// before_balance DECIMAL(30,18),
|
||
// post_transfer_balance DECIMAL(30,18),
|
||
// status TINYINT,
|
||
// create_at BIGINT,
|
||
// due_time BIGINT
|
||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
|
||
// assert.NoError(t, db.Exec(create).Error)
|
||
//
|
||
// // seed a row
|
||
// now := time.Now().Unix()
|
||
// seed := `
|
||
// INSERT INTO transaction
|
||
// (order_id, transaction_id, brand, uid, to_uid,
|
||
// type, business_type, asset, amount,
|
||
// before_balance, post_transfer_balance,
|
||
// status, create_at, due_time)
|
||
// VALUES
|
||
// (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||
// _, err = db.Exec(seed,
|
||
// "order1", "tx-uuid-1", "brandA", "user1", "user2",
|
||
// 1, 2, "BTC", "42.5",
|
||
// "10.0", "52.5",
|
||
// 1, now, now+3600,
|
||
// ).RowsAffected, db.Error
|
||
// assert.NoError(t, err)
|
||
//
|
||
// svc := NewWalletService(db, "user1", "BTC")
|
||
//
|
||
// type want struct {
|
||
// tx entity.Transaction
|
||
// errIsNF bool
|
||
// }
|
||
//
|
||
// tests := []struct {
|
||
// name string
|
||
// orderID string
|
||
// want want
|
||
// }{
|
||
// {
|
||
// name: "found existing transaction",
|
||
// orderID: "order1",
|
||
// want: want{
|
||
// tx: entity.Transaction{
|
||
// ID: 1,
|
||
// OrderID: "order1",
|
||
// TransactionID: "tx-uuid-1",
|
||
// Brand: "brandA",
|
||
// UID: "user1",
|
||
// ToUID: "user2",
|
||
// TxType: wallet.TxType(1),
|
||
// BusinessType: int8(2),
|
||
// Asset: "BTC",
|
||
// Amount: decimal.RequireFromString("42.5"),
|
||
// BeforeBalance: decimal.RequireFromString("10.0"),
|
||
// PostTransferBalance: decimal.RequireFromString("52.5"),
|
||
// Status: wallet.Enable(1),
|
||
// CreateAt: now,
|
||
// DueTime: now + 3600,
|
||
// },
|
||
// errIsNF: false,
|
||
// },
|
||
// },
|
||
// {
|
||
// name: "not found returns ErrRecordNotFound",
|
||
// orderID: "order2",
|
||
// want: want{errIsNF: true},
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tt := range tests {
|
||
// t.Run(tt.name, func(t *testing.T) {
|
||
// got, err := svc.GetOrderBalance(context.Background(), tt.orderID)
|
||
// if tt.want.errIsNF {
|
||
// assert.Error(t, err)
|
||
// assert.True(t, errors.Is(err, repository.ErrRecordNotFound))
|
||
// } else {
|
||
// assert.NoError(t, err)
|
||
// // compare all fields except those auto‐set by GORM (e.g. pointers)
|
||
// assert.Equal(t, tt.want.tx.ID, got.ID)
|
||
// assert.Equal(t, tt.want.tx.OrderID, got.OrderID)
|
||
// assert.Equal(t, tt.want.tx.TransactionID, got.TransactionID)
|
||
// assert.Equal(t, tt.want.tx.Brand, got.Brand)
|
||
// assert.Equal(t, tt.want.tx.UID, got.UID)
|
||
// assert.Equal(t, tt.want.tx.ToUID, got.ToUID)
|
||
// assert.Equal(t, tt.want.tx.TxType, got.TxType)
|
||
// assert.Equal(t, tt.want.tx.BusinessType, got.BusinessType)
|
||
// assert.Equal(t, tt.want.tx.Asset, got.Asset)
|
||
// assert.True(t, tt.want.tx.Amount.Equal(got.Amount))
|
||
// assert.True(t, tt.want.tx.BeforeBalance.Equal(got.BeforeBalance))
|
||
// assert.True(t, tt.want.tx.PostTransferBalance.Equal(got.PostTransferBalance))
|
||
// assert.Equal(t, tt.want.tx.Status, got.Status)
|
||
// assert.Equal(t, tt.want.tx.CreateAt, got.CreateAt)
|
||
// assert.Equal(t, tt.want.tx.DueTime, got.DueTime)
|
||
// }
|
||
// })
|
||
// }
|
||
//}
|
||
//
|
||
//func TestWalletService_GetOrderBalanceForUpdate(t *testing.T) {
|
||
// // setup container + DB + repo
|
||
// _, db, tearDown, err := SetupTestWalletRepository()
|
||
// assert.NoError(t, err)
|
||
// defer tearDown()
|
||
//
|
||
// // create transaction table
|
||
// create := `
|
||
// CREATE TABLE IF NOT EXISTS transaction (
|
||
// id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
// order_id VARCHAR(64),
|
||
// transaction_id VARCHAR(64),
|
||
// brand VARCHAR(32),
|
||
// uid VARCHAR(64),
|
||
// to_uid VARCHAR(64),
|
||
// type TINYINT,
|
||
// business_type TINYINT,
|
||
// asset VARCHAR(32),
|
||
// amount DECIMAL(30,18),
|
||
// before_balance DECIMAL(30,18),
|
||
// post_transfer_balance DECIMAL(30,18),
|
||
// status TINYINT,
|
||
// create_at BIGINT,
|
||
// due_time BIGINT
|
||
// ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`
|
||
// assert.NoError(t, db.Exec(create).Error)
|
||
//
|
||
// // seed one row
|
||
// now := time.Now().Unix()
|
||
// _, err = db.Exec(
|
||
// `INSERT INTO transaction
|
||
// (order_id, transaction_id, brand, uid, to_uid, type, business_type, asset,
|
||
// amount, before_balance, post_transfer_balance, status, create_at, due_time)
|
||
// VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||
// "ordX", "tx-XYZ", "brandZ", "u1", "u2",
|
||
// 1, 2, "ETH",
|
||
// "100.0", "50.0", "150.0",
|
||
// 1, now, now+600,
|
||
// ).RowsAffected, db.Error
|
||
// assert.NoError(t, err)
|
||
//
|
||
// // service under test
|
||
// svc := NewWalletService(db, "u1", "ETH")
|
||
//
|
||
// type want struct {
|
||
// tx entity.Transaction
|
||
// errIsNF bool
|
||
// }
|
||
//
|
||
// cases := []struct {
|
||
// name string
|
||
// orderID string
|
||
// want want
|
||
// }{
|
||
// {
|
||
// name: "found and locks",
|
||
// orderID: "ordX",
|
||
// want: want{
|
||
// tx: entity.Transaction{
|
||
// ID: 1,
|
||
// OrderID: "ordX",
|
||
// TransactionID: "tx-XYZ",
|
||
// Brand: "brandZ",
|
||
// UID: "u1",
|
||
// ToUID: "u2",
|
||
// TxType: wallet.TxType(1),
|
||
// BusinessType: int8(2),
|
||
// Asset: "ETH",
|
||
// Amount: decimal.RequireFromString("100.0"),
|
||
// BeforeBalance: decimal.RequireFromString("50.0"),
|
||
// PostTransferBalance: decimal.RequireFromString("150.0"),
|
||
// Status: wallet.Enable(1),
|
||
// CreateAt: now,
|
||
// DueTime: now + 600,
|
||
// },
|
||
// errIsNF: false,
|
||
// },
|
||
// },
|
||
// {
|
||
// name: "missing row returns ErrRecordNotFound",
|
||
// orderID: "ordY",
|
||
// want: want{errIsNF: true},
|
||
// },
|
||
// }
|
||
//
|
||
// for _, tt := range cases {
|
||
// t.Run(tt.name, func(t *testing.T) {
|
||
// got, err := svc.GetOrderBalanceForUpdate(context.Background(), tt.orderID)
|
||
// if tt.want.errIsNF {
|
||
// assert.Error(t, err)
|
||
// return
|
||
// }
|
||
// assert.NoError(t, err)
|
||
//
|
||
// // verify all fields
|
||
// expected := tt.want.tx
|
||
// assert.Equal(t, expected.ID, got.ID)
|
||
// assert.Equal(t, expected.OrderID, got.OrderID)
|
||
// assert.Equal(t, expected.TransactionID, got.TransactionID)
|
||
// assert.Equal(t, expected.Brand, got.Brand)
|
||
// assert.Equal(t, expected.UID, got.UID)
|
||
// assert.Equal(t, expected.ToUID, got.ToUID)
|
||
// assert.Equal(t, expected.TxType, got.TxType)
|
||
// assert.Equal(t, expected.BusinessType, got.BusinessType)
|
||
// assert.Equal(t, expected.Asset, got.Asset)
|
||
// assert.True(t, expected.Amount.Equal(got.Amount))
|
||
// assert.True(t, expected.BeforeBalance.Equal(got.BeforeBalance))
|
||
// assert.True(t, expected.PostTransferBalance.Equal(got.PostTransferBalance))
|
||
// assert.Equal(t, expected.Status, got.Status)
|
||
// assert.Equal(t, expected.CreateAt, got.CreateAt)
|
||
// assert.Equal(t, expected.DueTime, got.DueTime)
|
||
//
|
||
// // verify local cache was set
|
||
// cached, ok := svc.(*WalletService).localOrderBalances[got.ID]
|
||
// assert.True(t, ok, "expected localOrderBalances to contain key")
|
||
// assert.True(t, expected.PostTransferBalance.Equal(cached))
|
||
// })
|
||
// }
|
||
//}
|