feat: 接入 CoinGlass 清算热力图隔离链路
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
Some checks failed
ci / build + vet + guard + race (push) Has been cancelled
This commit is contained in:
163
internal/repo/webapi/coinglass/client_test.go
Normal file
163
internal/repo/webapi/coinglass/client_test.go
Normal file
@@ -0,0 +1,163 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user