package wallet import ( "errors" "testing" "hfb_sys/backend/internal/model" ) // TestAppendEntriesLogic 测试 AppendEntries 核心逻辑(不依赖数据库) func TestAppendEntriesValidatesEntry(t *testing.T) { entry := Entry{ UserID: 1001, Direction: "in", AmountCent: 10000, BalanceType: "available", BizType: "test", BizNo: "TEST001", } if entry.UserID == 0 { t.Fatal("UserID should not be 0") } if entry.AmountCent <= 0 { t.Fatal("AmountCent should be positive") } if entry.Direction != "in" && entry.Direction != "out" { t.Fatal("Direction should be 'in' or 'out'") } if entry.BalanceType != "available" && entry.BalanceType != "frozen" { t.Fatal("BalanceType should be 'available' or 'frozen'") } } // TestApplyEntryAvailableBalanceIn 测试可用余额入账 func TestApplyEntryAvailableBalanceIn(t *testing.T) { account := &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 5000, FrozenBalanceCent: 2000, } entry := Entry{ UserID: 1001, Direction: "in", AmountCent: 3000, BalanceType: "available", } balanceAfter, err := applyEntry(account, entry) if err != nil { t.Fatalf("applyEntry() error = %v", err) } if account.AvailableBalanceCent != 8000 { t.Fatalf("AvailableBalanceCent = %d, want 8000", account.AvailableBalanceCent) } if balanceAfter != 8000 { t.Fatalf("balanceAfter = %d, want 8000", balanceAfter) } if account.FrozenBalanceCent != 2000 { t.Fatalf("FrozenBalanceCent changed to %d, want unchanged 2000", account.FrozenBalanceCent) } } // TestApplyEntryAvailableBalanceOut 测试可用余额出账 func TestApplyEntryAvailableBalanceOut(t *testing.T) { account := &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 5000, FrozenBalanceCent: 2000, } entry := Entry{ UserID: 1001, Direction: "out", AmountCent: 3000, BalanceType: "available", } balanceAfter, err := applyEntry(account, entry) if err != nil { t.Fatalf("applyEntry() error = %v", err) } if account.AvailableBalanceCent != 2000 { t.Fatalf("AvailableBalanceCent = %d, want 2000", account.AvailableBalanceCent) } if balanceAfter != 2000 { t.Fatalf("balanceAfter = %d, want 2000", balanceAfter) } } // TestApplyEntryFrozenBalanceIn 测试冻结余额入账 func TestApplyEntryFrozenBalanceIn(t *testing.T) { account := &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 5000, FrozenBalanceCent: 2000, } entry := Entry{ UserID: 1001, Direction: "in", AmountCent: 1000, BalanceType: "frozen", } balanceAfter, err := applyEntry(account, entry) if err != nil { t.Fatalf("applyEntry() error = %v", err) } if account.FrozenBalanceCent != 3000 { t.Fatalf("FrozenBalanceCent = %d, want 3000", account.FrozenBalanceCent) } if balanceAfter != 3000 { t.Fatalf("balanceAfter = %d, want 3000", balanceAfter) } if account.AvailableBalanceCent != 5000 { t.Fatalf("AvailableBalanceCent changed to %d, want unchanged 5000", account.AvailableBalanceCent) } } // TestApplyEntryFrozenBalanceOut 测试冻结余额出账 func TestApplyEntryFrozenBalanceOut(t *testing.T) { account := &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 5000, FrozenBalanceCent: 3000, } entry := Entry{ UserID: 1001, Direction: "out", AmountCent: 1500, BalanceType: "frozen", } balanceAfter, err := applyEntry(account, entry) if err != nil { t.Fatalf("applyEntry() error = %v", err) } if account.FrozenBalanceCent != 1500 { t.Fatalf("FrozenBalanceCent = %d, want 1500", account.FrozenBalanceCent) } if balanceAfter != 1500 { t.Fatalf("balanceAfter = %d, want 1500", balanceAfter) } } // TestApplyEntryInsufficientAvailableBalance 测试可用余额不足 func TestApplyEntryInsufficientAvailableBalance(t *testing.T) { account := &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 1000, FrozenBalanceCent: 2000, } entry := Entry{ UserID: 1001, Direction: "out", AmountCent: 2000, BalanceType: "available", } _, err := applyEntry(account, entry) if !errors.Is(err, ErrInsufficientBalance) { t.Fatalf("error = %v, want ErrInsufficientBalance", err) } // 验证余额未变化 if account.AvailableBalanceCent != 1000 { t.Fatalf("AvailableBalanceCent = %d, should remain 1000 on error", account.AvailableBalanceCent) } } // TestApplyEntryInsufficientFrozenBalance 测试冻结余额不足 func TestApplyEntryInsufficientFrozenBalance(t *testing.T) { account := &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 5000, FrozenBalanceCent: 1000, } entry := Entry{ UserID: 1001, Direction: "out", AmountCent: 2000, BalanceType: "frozen", } _, err := applyEntry(account, entry) if !errors.Is(err, ErrInsufficientBalance) { t.Fatalf("error = %v, want ErrInsufficientBalance", err) } // 验证余额未变化 if account.FrozenBalanceCent != 1000 { t.Fatalf("FrozenBalanceCent = %d, should remain 1000 on error", account.FrozenBalanceCent) } } // TestApplyEntryMultipleOperations 测试连续多次操作 func TestApplyEntryMultipleOperations(t *testing.T) { account := &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 0, FrozenBalanceCent: 0, } operations := []struct { entry Entry wantAvl int64 wantFrz int64 }{ { entry: Entry{UserID: 1001, Direction: "in", AmountCent: 10000, BalanceType: "available"}, wantAvl: 10000, wantFrz: 0, }, { entry: Entry{UserID: 1001, Direction: "in", AmountCent: 5000, BalanceType: "frozen"}, wantAvl: 10000, wantFrz: 5000, }, { entry: Entry{UserID: 1001, Direction: "out", AmountCent: 3000, BalanceType: "available"}, wantAvl: 7000, wantFrz: 5000, }, { entry: Entry{UserID: 1001, Direction: "out", AmountCent: 2000, BalanceType: "frozen"}, wantAvl: 7000, wantFrz: 3000, }, } for i, op := range operations { _, err := applyEntry(account, op.entry) if err != nil { t.Fatalf("operation %d: applyEntry() error = %v", i, err) } if account.AvailableBalanceCent != op.wantAvl { t.Fatalf("operation %d: AvailableBalanceCent = %d, want %d", i, account.AvailableBalanceCent, op.wantAvl) } if account.FrozenBalanceCent != op.wantFrz { t.Fatalf("operation %d: FrozenBalanceCent = %d, want %d", i, account.FrozenBalanceCent, op.wantFrz) } } } // TestApplyEntryEdgeCases 测试边界情况 func TestApplyEntryEdgeCases(t *testing.T) { testCases := []struct { name string account *model.WalletAccount entry Entry wantErr error }{ { name: "余额刚好够扣", account: &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 1000, }, entry: Entry{ UserID: 1001, Direction: "out", AmountCent: 1000, BalanceType: "available", }, wantErr: nil, }, { name: "余额差1分不够扣", account: &model.WalletAccount{ UserID: 1001, AvailableBalanceCent: 999, }, entry: Entry{ UserID: 1001, Direction: "out", AmountCent: 1000, BalanceType: "available", }, wantErr: ErrInsufficientBalance, }, } for _, tc := range testCases { t.Run(tc.name, func(t *testing.T) { _, err := applyEntry(tc.account, tc.entry) if !errors.Is(err, tc.wantErr) { t.Fatalf("error = %v, want %v", err, tc.wantErr) } }) } }