Files
dela 1b61541ff5
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
feat: 接入 CoinGlass 清算热力图隔离链路
2026-05-25 16:57:22 +08:00

164 lines
4.4 KiB
Go

package coinglass
import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"sync/atomic"
"testing"
"time"
"github.com/stretchr/testify/require"
)
type netHTTPDoer struct {
client *http.Client
}
func (d netHTTPDoer) Do(ctx context.Context, req httpRequest) (*httpResponse, error) {
httpReq, err := http.NewRequestWithContext(ctx, http.MethodGet, req.URL, nil)
if err != nil {
return nil, err
}
httpReq.Header = req.Header.Clone()
resp, err := d.client.Do(httpReq)
if err != nil {
return nil, err
}
defer resp.Body.Close()
body, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
return &httpResponse{
StatusCode: resp.StatusCode,
Header: resp.Header.Clone(),
Body: body,
}, nil
}
func TestClientGetPlaintext_HappyPath(t *testing.T) {
const (
path = "/api/index/v2/liqHeatMap"
token = "dfa89d5d00cf4000"
)
plaintext := `{"instrument":{"exName":"Binance","instrumentId":"BTCUSDT"},"liq":[[1,2,3.4]]}`
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
require.Equal(t, path, r.URL.Path)
require.Equal(t, "true", r.URL.Query().Get("merge"))
require.Equal(t, "Binance_BTCUSDT", r.URL.Query().Get("symbol"))
require.NotEmpty(t, r.URL.Query().Get("data"))
require.Equal(t, "true", r.Header.Get("Encryption"))
require.Equal(t, defaultOrigin, r.Header.Get("Origin"))
require.NotEmpty(t, r.Header.Get("Cache-Ts-V2"))
cacheTs := r.Header.Get("Cache-Ts-V2")
_, err := strconv.ParseInt(cacheTs, 10, 64)
require.NoError(t, err)
w.Header().Set("v", "0")
w.Header().Set("user", encryptForTest(t, userKeyFromSeed(cacheTs), token))
_ = json.NewEncoder(w).Encode(map[string]any{
"code": "0",
"msg": "success",
"data": encryptForTest(t, bodyKeyFromToken(token), plaintext),
})
}))
defer ts.Close()
client := newTestClient(t, ts.URL)
got, err := client.GetPlaintext(context.Background(), path, url.Values{
"merge": {"true"},
"symbol": {"Binance_BTCUSDT"},
"interval": {"5"},
"limit": {"288"},
})
require.NoError(t, err)
require.JSONEq(t, plaintext, string(got))
}
func TestClientGetPlaintext_RetriesFakeSuccessOnce(t *testing.T) {
var attempts int32
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&attempts, 1)
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"code": "0",
"msg": "success",
"data": nil,
})
}))
defer ts.Close()
client := newTestClient(t, ts.URL)
_, err := client.GetPlaintext(context.Background(), "/api/index/v2/liqHeatMap", nil)
require.ErrorIs(t, err, ErrFakeSuccess)
require.Equal(t, int32(2), atomic.LoadInt32(&attempts))
}
func TestClientGetPlaintext_BusinessErrorDoesNotRetry(t *testing.T) {
var attempts int32
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&attempts, 1)
_ = json.NewEncoder(w).Encode(map[string]any{
"success": false,
"code": "40000",
"msg": "40000",
})
}))
defer ts.Close()
client := newTestClient(t, ts.URL)
_, err := client.GetPlaintext(context.Background(), "/api/index/v2/liqHeatMap", nil)
require.Error(t, err)
require.True(t, errors.Is(err, ErrAPIBusiness))
require.Equal(t, int32(1), atomic.LoadInt32(&attempts))
}
func TestClientGetPlaintext_MissingDecryptHeadersIsFakeSuccess(t *testing.T) {
const token = "dfa89d5d00cf4000"
var attempts int32
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
atomic.AddInt32(&attempts, 1)
_ = json.NewEncoder(w).Encode(map[string]any{
"code": "0",
"msg": "success",
"data": encryptForTest(t, bodyKeyFromToken(token), `{"ok":true}`),
})
}))
defer ts.Close()
client := newTestClient(t, ts.URL)
_, err := client.GetPlaintext(context.Background(), "/api/index/v2/liqHeatMap", nil)
require.ErrorIs(t, err, ErrFakeSuccess)
require.Equal(t, int32(2), atomic.LoadInt32(&attempts))
}
func newTestClient(t *testing.T, baseURL string) *Client {
t.Helper()
client, err := newClientWithDoer(ClientOptions{
BaseURL: baseURL,
TOTPSecret: "JBSWY3DPEHPK3PXP",
FakeSuccessRetryWait: time.Nanosecond,
}, netHTTPDoer{client: http.DefaultClient})
require.NoError(t, err)
client.now = func() time.Time {
return time.Unix(1735689600, 123*int64(time.Millisecond))
}
return client
}