package binancerecharge import ( "context" "testing" "chatapp3-golang/internal/config" "chatapp3-golang/internal/integration" "chatapp3-golang/internal/model" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func TestSaveConfigKeepsExistingSecretWhenBlank(t *testing.T) { db := newBinanceRechargeTestDB(t) service := NewService(config.Config{}, db, &fakeBinanceJavaGateway{}) if _, err := service.SaveConfig(context.Background(), SaveConfigRequest{ SysOrigin: "LIKEI", APIKey: "key-1", APISecret: "secret-1", Enabled: true, Rates: []RateConfigRequest{ {MinAmount: "0", MaxAmount: "100", GoldPerUSD: "80000"}, }, }); err != nil { t.Fatalf("initial save config: %v", err) } resp, err := service.SaveConfig(context.Background(), SaveConfigRequest{ SysOrigin: "LIKEI", APIKey: "key-2", Enabled: true, Rates: []RateConfigRequest{ {MinAmount: "0", MaxAmount: "0", GoldPerUSD: "82000"}, }, }) if err != nil { t.Fatalf("update config: %v", err) } if resp.APIKey != "key-2" { t.Fatalf("APIKey = %q, want key-2", resp.APIKey) } if !resp.APISecretConfigured { t.Fatalf("APISecretConfigured = false, want true") } var row model.BinanceRechargeConfig if err := db.Where("sys_origin = ?", "LIKEI").First(&row).Error; err != nil { t.Fatalf("query config: %v", err) } if row.APISecret != "secret-1" { t.Fatalf("APISecret = %q, want secret-1", row.APISecret) } } func TestVerifyAndRechargeIsIdempotent(t *testing.T) { db := newBinanceRechargeTestDB(t) java := &fakeBinanceJavaGateway{ profile: integration.UserProfile{ ID: integration.Int64Value(200), OriginSys: "LIKEI", }, balanceExists: true, } service := NewService(config.Config{}, db, java) service.SetBinanceVerifier(&fakeBinanceVerifier{}) if _, err := service.SaveConfig(context.Background(), SaveConfigRequest{ SysOrigin: "LIKEI", APIKey: "key", APISecret: "secret", Enabled: true, Rates: []RateConfigRequest{ {MinAmount: "0", MaxAmount: "100", GoldPerUSD: "80000"}, {MinAmount: "100", MaxAmount: "0", GoldPerUSD: "82000"}, }, }); err != nil { t.Fatalf("save config: %v", err) } req := VerifyRechargeRequest{ TargetUserID: 200, OrderNo: "order-1", TransferAmount: "12.5", TransferType: TransferTypeBinancePay, BillNo: "bill-1", } resp, err := service.VerifyAndRecharge(context.Background(), AuthUser{ UserID: 100, SysOrigin: "LIKEI", }, req) if err != nil { t.Fatalf("verify recharge: %v", err) } if resp.Idempotent { t.Fatalf("first response idempotent = true, want false") } if resp.GoldAmount != 1_000_000 { t.Fatalf("GoldAmount = %d, want 1000000", resp.GoldAmount) } if java.changeCalls != 1 { t.Fatalf("changeCalls = %d, want 1", java.changeCalls) } if java.lastChange.RechargeType != rechargeTypeUSDT { t.Fatalf("RechargeType = %q, want %q", java.lastChange.RechargeType, rechargeTypeUSDT) } if java.lastChange.AcceptAmount != "1000000" { t.Fatalf("AcceptAmount = %q, want 1000000", java.lastChange.AcceptAmount) } if java.lastChange.OrderAmount != "12.5" { t.Fatalf("OrderAmount = %q, want 12.5", java.lastChange.OrderAmount) } resp, err = service.VerifyAndRecharge(context.Background(), AuthUser{ UserID: 100, SysOrigin: "LIKEI", }, req) if err != nil { t.Fatalf("verify idempotent recharge: %v", err) } if !resp.Idempotent { t.Fatalf("second response idempotent = false, want true") } if java.changeCalls != 1 { t.Fatalf("changeCalls after idempotent call = %d, want 1", java.changeCalls) } var count int64 if err := db.Model(&model.BinanceRechargeVerifyRecord{}).Where("status = ?", verifyStatusSuccess).Count(&count).Error; err != nil { t.Fatalf("count success records: %v", err) } if count != 1 { t.Fatalf("success record count = %d, want 1", count) } } func newBinanceRechargeTestDB(t *testing.T) *gorm.DB { t.Helper() db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) if err != nil { t.Fatalf("open sqlite: %v", err) } if err := db.AutoMigrate( &model.BinanceRechargeConfig{}, &model.BinanceRechargeRate{}, &model.BinanceRechargeVerifyRecord{}, ); err != nil { t.Fatalf("auto migrate: %v", err) } return db } type fakeBinanceVerifier struct{} func (f *fakeBinanceVerifier) Verify(_ context.Context, _ BinanceCredential, query BinanceVerifyQuery) (VerifiedTransaction, error) { return VerifiedTransaction{ TransactionID: query.BillNo, OrderType: query.TransferType, Amount: query.Amount, RawJSON: `{"transactionId":"` + query.BillNo + `"}`, }, nil } type fakeBinanceJavaGateway struct { profile integration.UserProfile balanceExists bool sellerExists bool changeCalls int lastChange integration.FreightBalanceChangeRequest } func (f *fakeBinanceJavaGateway) GetUserProfile(_ context.Context, userID int64) (integration.UserProfile, error) { if int64(f.profile.ID) == 0 { f.profile.ID = integration.Int64Value(userID) } return f.profile, nil } func (f *fakeBinanceJavaGateway) ExistsFreightBalance(context.Context, int64) (bool, error) { return f.balanceExists, nil } func (f *fakeBinanceJavaGateway) ExistsFreightSeller(context.Context, int64) (bool, error) { return f.sellerExists, nil } func (f *fakeBinanceJavaGateway) ChangeFreightBalance(_ context.Context, req integration.FreightBalanceChangeRequest) error { f.changeCalls++ f.lastChange = req return nil } func (f *fakeBinanceJavaGateway) AddFreightAgentReview(context.Context, integration.FreightAgentReviewRequest) error { return nil } func (f *fakeBinanceJavaGateway) IncreaseFreightRechargeRecord(context.Context, int64, string) error { return nil }