Files
olons_server/internal/store/finance_time_integration_test.go
T
richjohnrambo f56b84bb10
Deploy olons-server production / deploy (push) Successful in 34s
fix: display finance records and ledgers in Beijing time
2026-10-23 00:13:07 +08:00

175 lines
8.3 KiB
Go

package store
import (
"context"
"os"
"testing"
"github.com/jmoiron/sqlx"
)
func financeTimeTestDB(t *testing.T) *sqlx.DB {
t.Helper()
dsn := os.Getenv("OLONS_TEST_MYSQL_DSN")
if dsn == "" {
t.Skip("set OLONS_TEST_MYSQL_DSN to run the finance time integration tests")
}
db, err := sqlx.Connect("mysql", dsn)
if err != nil {
t.Fatal(err)
}
db.SetMaxOpenConns(1)
db.SetMaxIdleConns(1)
t.Cleanup(func() { _ = db.Close() })
return db
}
func TestFinanceRecordTimesUseCorrectSourceZone(t *testing.T) {
db := financeTimeTestDB(t)
// Temporary tables shadow only this connection's tables; no business rows are changed.
for _, query := range []string{
`CREATE TEMPORARY TABLE hg_payment_channel (id BIGINT,tenant_id BIGINT,name VARCHAR(64))`,
`CREATE TEMPORARY TABLE hg_recharge_order (
id BIGINT,tenant_id BIGINT,member_id BIGINT,recharge_no VARCHAR(64),channel_id BIGINT,
request_amount BIGINT DEFAULT 50000,paid_amount BIGINT DEFAULT 50000,currency VARCHAR(8) DEFAULT 'CNY',
voucher_attachment_ids JSON,recharge_status INT DEFAULT 3,remark VARCHAR(255) DEFAULT '',
review_reason_code VARCHAR(64) DEFAULT '',review_reason VARCHAR(255) DEFAULT '',external_no VARCHAR(64) DEFAULT '',
paid_at DATETIME,reviewed_at DATETIME,credited_at DATETIME,created_at DATETIME,updated_at DATETIME)`,
`CREATE TEMPORARY TABLE hg_withdrawal_order (
id BIGINT,tenant_id BIGINT,member_id BIGINT,withdrawal_no VARCHAR(64),payment_account_id BIGINT DEFAULT 1,
channel_id BIGINT,request_amount BIGINT DEFAULT 50000,fee_amount BIGINT DEFAULT 0,actual_amount BIGINT DEFAULT 50000,
currency VARCHAR(8) DEFAULT 'CNY',account_mask VARCHAR(64) DEFAULT '',bank_name VARCHAR(64) DEFAULT '',
holder_mask VARCHAR(64) DEFAULT '',risk_result JSON,withdrawal_status INT DEFAULT 5,
review_reason_code VARCHAR(64) DEFAULT '',review_reason VARCHAR(255) DEFAULT '',external_no VARCHAR(64) DEFAULT '',
reviewed_at DATETIME,processed_at DATETIME,completed_at DATETIME,created_at DATETIME,updated_at DATETIME)`,
} {
if _, err := db.Exec(query); err != nil {
t.Fatal(err)
}
}
s := &Store{db: db}
ctx := context.Background()
for _, test := range []struct{ zone, createdAt string }{
{"+00:00", "2026-10-22 15:59:00"},
{"+08:00", "2026-10-22 23:59:00"},
} {
t.Run(test.zone, func(t *testing.T) {
for _, query := range []string{`DELETE FROM hg_recharge_order`, `DELETE FROM hg_withdrawal_order`} {
if _, err := db.Exec(query); err != nil {
t.Fatal(err)
}
}
if _, err := db.Exec(`SET SESSION time_zone=?`, test.zone); err != nil {
t.Fatal(err)
}
if _, err := db.Exec(`INSERT INTO hg_recharge_order
(id,tenant_id,member_id,recharge_no,channel_id,created_at,updated_at,paid_at,reviewed_at,credited_at)
VALUES(1,0,9,'RT1',1,?,'2026-10-23 00:01:00','2026-10-23 00:01:00','2026-10-23 00:01:00','2026-10-23 00:01:00'),
(2,0,9,'RT2',1,?,?,NULL,NULL,NULL)`, test.createdAt, test.createdAt, test.createdAt); err != nil {
t.Fatal(err)
}
if _, err := db.Exec(`INSERT INTO hg_withdrawal_order
(id,tenant_id,member_id,withdrawal_no,channel_id,created_at,updated_at,reviewed_at,processed_at,completed_at)
VALUES(1,0,9,'WT1',1,?,'2026-10-23 00:01:00','2026-10-23 00:01:00','2026-10-23 00:01:00','2026-10-23 00:01:00'),
(2,0,9,'WT2',1,?,?,NULL,NULL,NULL)`, test.createdAt, test.createdAt, test.createdAt); err != nil {
t.Fatal(err)
}
recharges, err := s.RechargeOrders(ctx, 0, 9, 0, 0, 50)
if err != nil || recharges == nil || len(recharges.Items) != 2 {
t.Fatalf("recharges = %+v, err = %v", recharges, err)
}
withdrawals, err := s.WithdrawalOrders(ctx, 0, 9, 0, 0, 50)
if err != nil || withdrawals == nil || len(withdrawals.Items) != 2 {
t.Fatalf("withdrawals = %+v, err = %v", withdrawals, err)
}
for _, item := range recharges.Items {
if item.CreatedAt != "2026-10-22T15:59:00Z" || (item.ID == 1 && item.UpdatedAt != "2026-10-22T16:01:00Z") || (item.ID == 2 && item.UpdatedAt != item.CreatedAt) {
t.Errorf("recharge list time = %+v", item)
}
}
for _, item := range withdrawals.Items {
if item.CreatedAt != "2026-10-22T15:59:00Z" || (item.ID == 1 && item.UpdatedAt != "2026-10-22T16:01:00Z") || (item.ID == 2 && item.UpdatedAt != item.CreatedAt) {
t.Errorf("withdrawal list time = %+v", item)
}
}
recharge, err := s.RechargeOrder(ctx, 0, 9, 1)
if err != nil {
t.Fatal(err)
}
if recharge.PaidAt != "2026-10-22T16:01:00Z" || recharge.ReviewedAt != recharge.PaidAt || recharge.CreditedAt != recharge.PaidAt {
t.Errorf("recharge review timestamps = %+v", recharge)
}
withdrawal, err := s.WithdrawalOrder(ctx, 0, 9, 1)
if err != nil {
t.Fatal(err)
}
if withdrawal.ReviewedAt != "2026-10-22T16:01:00Z" || withdrawal.ProcessedAt != withdrawal.ReviewedAt || withdrawal.CompletedAt != withdrawal.ReviewedAt {
t.Errorf("withdrawal review timestamps = %+v", withdrawal)
}
recharge, err = s.RechargeOrder(ctx, 0, 9, 2)
if err != nil || recharge == nil || recharge.PaidAt != "" || recharge.ReviewedAt != "" || recharge.CreditedAt != "" {
t.Errorf("empty recharge review times = %+v, err = %v", recharge, err)
}
withdrawal, err = s.WithdrawalOrder(ctx, 0, 9, 2)
if err != nil || withdrawal == nil || withdrawal.ReviewedAt != "" || withdrawal.ProcessedAt != "" || withdrawal.CompletedAt != "" {
t.Errorf("empty withdrawal review times = %+v, err = %v", withdrawal, err)
}
})
}
}
func TestWalletAndOrderLedgerTimesKeepUTCAndBeijingWriters(t *testing.T) {
db := financeTimeTestDB(t)
if _, err := db.Exec(`CREATE TEMPORARY TABLE hg_wallet_ledger (
id BIGINT,tenant_id BIGINT DEFAULT 0,member_id BIGINT DEFAULT 9,ledger_no VARCHAR(64),
biz_type VARCHAR(64) DEFAULT 'commission',biz_no VARCHAR(64) DEFAULT 'ORDER1',entry_type VARCHAR(64) DEFAULT 'available',
direction INT DEFAULT 1,amount BIGINT DEFAULT 100,before_amount BIGINT DEFAULT 1000,after_amount BIGINT DEFAULT 1100,
currency VARCHAR(8) DEFAULT 'CNY',remark VARCHAR(255) DEFAULT '',idempotency_key VARCHAR(128),occurred_at DATETIME)`); err != nil {
t.Fatal(err)
}
fixtures := []struct{ key, remark, occurredAt, want string }{
{"task:1:settle:commission", "任务完成自动返佣", "2026-10-22 16:00:07", "2026-10-22T16:00:07Z"},
{"recharge:2", "储入审核到账", "2026-10-22 23:59:07", "2026-10-22T15:59:07Z"},
{"withdrawal:W1:available", "申请取出冻结", "2026-10-22 15:58:07", "2026-10-22T15:58:07Z"},
{"withdraw:1:consume", "取出审核", "2026-10-22 23:57:07", "2026-10-22T15:57:07Z"},
{"task:2:admin-match:commission", "后台匹配订单完成返佣", "2026-10-22 23:56:07", "2026-10-22T15:56:07Z"},
{"task:3:match:available", "自动派单扣除可用余额", "2026-10-22 15:55:07", "2026-10-22T15:55:07Z"},
{"task:4:match:available", "手动订单强制付款扣除可用余额", "2026-10-22 23:54:07", "2026-10-22T15:54:07Z"},
{"task:5:match:frozen", "会员确认手动订单冻结本金", "2026-10-22 15:53:07", "2026-10-22T15:53:07Z"},
{"task:6:expire:available", "订单超时退回订单本金", "2026-10-22 15:52:07", "2026-10-22T15:52:07Z"},
}
for i, fixture := range fixtures {
if _, err := db.Exec(`INSERT INTO hg_wallet_ledger (id,ledger_no,idempotency_key,remark,occurred_at) VALUES(?,?,?,?,?)`,
i+1, fixture.key, fixture.key, fixture.remark, fixture.occurredAt); err != nil {
t.Fatal(err)
}
}
for _, zone := range []string{"+00:00", "+08:00"} {
t.Run(zone, func(t *testing.T) {
if _, err := db.Exec(`SET SESSION time_zone=?`, zone); err != nil {
t.Fatal(err)
}
s := &Store{db: db}
page, err := s.WalletLedgers(context.Background(), 0, 9, "", 0, 0, 50)
if err != nil || page == nil || len(page.Items) != len(fixtures) {
t.Fatalf("ledger page = %+v, err = %v", page, err)
}
for i, fixture := range fixtures {
if page.Items[i].LedgerNo != fixture.key || page.Items[i].OccurredAt != fixture.want {
t.Errorf("ledger %d = %+v, want %s at %s", i, page.Items[i], fixture.key, fixture.want)
}
}
rows, err := s.OrderLedgers(context.Background(), 0, 9, []string{"ORDER1"})
if err != nil || len(rows) != len(fixtures) {
t.Fatalf("order ledgers = %+v, err = %v", rows, err)
}
for i, row := range rows {
fixture := fixtures[len(fixtures)-i-1]
if row["ledger_no"] != fixture.key || row["occurred_at"] != fixture.want {
t.Errorf("order ledger = %+v, want %s at %s", row, fixture.key, fixture.want)
}
}
})
}
}