package renewchange import ( "context" "path/filepath" "strings" "testing" "time" _ "github.com/gogf/gf/contrib/drivers/sqlite/v2" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" rc "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/renewchange" ) func TestDeductRuntimeRenewBalanceRejectsInsufficientBalanceAtomically(t *testing.T) { ctx := context.Background() if err := gdb.SetConfig(gdb.Config{ "default": gdb.ConfigGroup{{Type: "sqlite", Name: filepath.Join(t.TempDir(), "renew-balance.db")}}, }); err != nil { t.Fatalf("configure sqlite: %v", err) } defer g.DB().Close(ctx) createRenewBalanceTables(t, ctx) if _, err := g.DB().Model(rc.TableAgentBalance).Ctx(ctx).Data(g.Map{ "dept_id": 117, "tenant_id": "000000", "balance": 100, "status": renewFinanceAccountStatusNormal, }).Insert(); err != nil { t.Fatalf("seed balance: %v", err) } main := &rc.BatchMainRow{ BatchNo: "REN20260806110000TEST", TenantId: "000000", CreatedBy: 60, } err := g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error { _, _, _, err := deductRuntimeRenewBalance(ctx, tx, main, 117, 576, time.Date(2026, 8, 6, 11, 0, 0, 0, time.Local)) return err }) if err == nil || !strings.Contains(err.Error(), "账户余额不足") { t.Fatalf("deduct error = %v, want insufficient balance", err) } balance, err := g.DB().Model(rc.TableAgentBalance).Ctx(ctx). Where("dept_id", 117). Where("tenant_id", "000000"). Value("balance") if err != nil { t.Fatalf("query balance: %v", err) } if got := balance.Float64(); got != 100 { t.Fatalf("balance = %.2f, want unchanged 100.00", got) } count, err := g.DB().Model(rc.TableBalanceDetail).Ctx(ctx). Where("serial_no", "RENPAY"+main.BatchNo). Count() if err != nil { t.Fatalf("count balance detail: %v", err) } if count != 0 { t.Fatalf("balance detail count = %d, want 0", count) } } func createRenewBalanceTables(t *testing.T, ctx context.Context) { t.Helper() statements := []string{ `CREATE TABLE t_agent_balance ( id INTEGER PRIMARY KEY AUTOINCREMENT, dept_id INTEGER NOT NULL, tenant_id TEXT NOT NULL, balance REAL NOT NULL, status INTEGER NOT NULL DEFAULT 0, updated_by INTEGER, updated_at DATETIME )`, `CREATE TABLE t_balance_detail ( id INTEGER PRIMARY KEY AUTOINCREMENT, dept_id INTEGER NOT NULL, serial_no TEXT NOT NULL, transaction_time DATETIME, income_expense_type INTEGER, transaction_type INTEGER, transaction_channel INTEGER, transaction_status INTEGER, amount REAL, before_balance REAL, balance_after REAL, order_sn TEXT, remark TEXT, tenant_id TEXT NOT NULL, created_dept INTEGER, created_by INTEGER, created_at DATETIME )`, } for _, statement := range statements { if _, err := g.DB().Exec(ctx, statement); err != nil { t.Fatalf("create renew balance table: %v", err) } } }