2026-07-07 18:05:02 +08:00

172 lines
5.5 KiB
Go

package binance
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
"hyapp/pkg/xerr"
"hyapp/services/wallet-service/internal/config"
"hyapp/services/wallet-service/internal/service/wallet/ports"
)
func TestFindUSDTDepositMatchesOffchainTransferAndSignedRequest(t *testing.T) {
var sawDepositRequest bool
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v3/time":
_, _ = w.Write([]byte(`{"serverTime":1783345000000}`))
case "/sapi/v1/capital/deposit/hisrec":
sawDepositRequest = true
if r.Header.Get("X-MBX-APIKEY") != "api-lalu" {
t.Fatalf("X-MBX-APIKEY mismatch")
}
values := r.URL.Query()
signature := values.Get("signature")
values.Del("signature")
if signature != signQueryString(values.Encode(), "secret-lalu") {
t.Fatalf("signature mismatch: query=%s signature=%s", values.Encode(), signature)
}
if values.Get("timestamp") != "1783345000000" || values.Get("recvWindow") != "10000" || values.Get("coin") != "USDT" || values.Get("limit") != "1000" {
t.Fatalf("signed deposit query mismatch: %s", values.Encode())
}
if values.Get("endTime") != "1783345000000" || values.Get("startTime") != "1775569000000" {
t.Fatalf("deposit bounded window mismatch: %s", values.Encode())
}
_, _ = w.Write([]byte(`[{
"id":"deposit-1",
"amount":"500.00000000",
"coin":"USDT",
"network":"TRX",
"status":1,
"address":"TNwqCkF8cnM4XiwjnbbKTfdZvFDqVoa1TN",
"txId":"Off-chain transfer 388611987194",
"transferType":1,
"insertTime":1783344000000,
"completeTime":1783344025000,
"confirmTimes":"1/1",
"unlockConfirm":0,
"walletType":0
}]`))
default:
http.NotFound(w, r)
}
}))
defer server.Close()
client := New(config.BinanceConfig{
Enabled: true,
APIBaseURL: server.URL,
RecvWindow: 10000,
HTTPTimeout: time.Second,
Lalu: config.BinanceAccountConfig{
APIKey: "api-lalu",
APISecretKey: "secret-lalu",
},
})
record, err := client.FindUSDTDeposit(context.Background(), ports.BinanceDepositLookupRequest{
AppCode: "lalu",
ExternalID: "388611987194",
Coin: "USDT",
Network: "TRX",
ToAddress: "TNwqCkF8cnM4XiwjnbbKTfdZvFDqVoa1TN",
AmountMinor: 50000,
})
if err != nil {
t.Fatalf("FindUSDTDeposit failed: %v", err)
}
if !sawDepositRequest {
t.Fatalf("deposit history endpoint was not called")
}
if record.TxID != "Off-chain transfer 388611987194" || record.AmountMinor != 50000 || record.Network != "TRX" || !strings.Contains(record.RawJSON, `"txId"`) {
t.Fatalf("deposit record mismatch: %+v", record)
}
}
func TestFindUSDTDepositRejectsMismatches(t *testing.T) {
baseReq := ports.BinanceDepositLookupRequest{
AppCode: "lalu",
ExternalID: "388611987194",
Coin: "USDT",
Network: "TRX",
ToAddress: "TNwqCkF8cnM4XiwjnbbKTfdZvFDqVoa1TN",
AmountMinor: 50000,
}
baseRecord := depositRecord{
Amount: "500",
Coin: "USDT",
Network: "TRX",
Status: 1,
Address: "TNwqCkF8cnM4XiwjnbbKTfdZvFDqVoa1TN",
TxID: "Off-chain transfer 388611987194",
}
cases := []struct {
name string
mutate func(*depositRecord)
message string
}{
{name: "status", mutate: func(r *depositRecord) { r.Status = 0 }, message: "status is not successful"},
{name: "network", mutate: func(r *depositRecord) { r.Network = "BSC" }, message: "network mismatch"},
{name: "address", mutate: func(r *depositRecord) { r.Address = "TDifferentAddress" }, message: "address mismatch"},
{name: "amount", mutate: func(r *depositRecord) { r.Amount = "499.99" }, message: "amount mismatch"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
record := baseRecord
tc.mutate(&record)
_, err := matchDepositRecord(baseReq, []depositRecord{record})
if !xerr.IsCode(err, xerr.Conflict) || !strings.Contains(err.Error(), tc.message) {
t.Fatalf("expected conflict %q, got %v", tc.message, err)
}
})
}
}
func TestFindUSDTDepositMissingAccountReturnsConfiguredError(t *testing.T) {
client := New(config.BinanceConfig{
Enabled: true,
APIBaseURL: "https://binance.invalid",
Lalu: config.BinanceAccountConfig{
APIKey: "api-lalu",
APISecretKey: "secret-lalu",
},
})
_, err := client.FindUSDTDeposit(context.Background(), ports.BinanceDepositLookupRequest{
AppCode: "aslan",
ExternalID: "388611987194",
Coin: "USDT",
Network: "TRX",
ToAddress: "TNwqCkF8cnM4XiwjnbbKTfdZvFDqVoa1TN",
AmountMinor: 50000,
})
if !xerr.IsCode(err, xerr.Unavailable) || !strings.Contains(err.Error(), "binance account is not configured") {
t.Fatalf("missing account should be explicit, got %v", err)
}
}
func TestDepositTxIDMatchesOffchainVariants(t *testing.T) {
if !depositTxIDMatches("Off-chain transfer 388611987194", "388611987194") {
t.Fatalf("plain off-chain id should match Binance txId")
}
if !depositTxIDMatches("Off-chain transfer 388611987194", "Off-chain transfer 388611987194") {
t.Fatalf("full off-chain txId should match itself")
}
if depositTxIDMatches("Off-chain transfer 388611987194", "388611987195") {
t.Fatalf("different off-chain id must not match")
}
}
func TestSignQueryStringStable(t *testing.T) {
values := url.Values{}
values.Set("coin", "USDT")
values.Set("timestamp", "1783345000000")
signature := signQueryString(values.Encode(), "secret-lalu")
if signature != signQueryString("coin=USDT&timestamp=1783345000000", "secret-lalu") {
t.Fatalf("signature must be based on encoded query string")
}
}