mirror of
https://github.com/NoFxAiOS/nofx.git
synced 2026-06-06 05:51:19 +08:00
test: add Lighter API authentication tests and diagnostic tools
This commit is contained in:
233
cmd/lighter_test/main.go
Normal file
233
cmd/lighter_test/main.go
Normal file
@@ -0,0 +1,233 @@
|
||||
// Lighter API Authentication Test Tool
|
||||
// Usage: go run cmd/lighter_test/main.go -wallet=0x... -apikey=... [-testnet]
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
lighterClient "github.com/elliottech/lighter-go/client"
|
||||
lighterHTTP "github.com/elliottech/lighter-go/client/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Parse command line flags
|
||||
walletAddr := flag.String("wallet", "", "Ethereum wallet address")
|
||||
apiKeyPrivateKey := flag.String("apikey", "", "API key private key (40 bytes hex)")
|
||||
apiKeyIndex := flag.Int("apikeyindex", 0, "API key index (0-255)")
|
||||
testnet := flag.Bool("testnet", false, "Use testnet instead of mainnet")
|
||||
flag.Parse()
|
||||
|
||||
if *walletAddr == "" || *apiKeyPrivateKey == "" {
|
||||
fmt.Println("Usage: go run cmd/lighter_test/main.go -wallet=0x... -apikey=...")
|
||||
fmt.Println("Options:")
|
||||
fmt.Println(" -wallet Ethereum wallet address (required)")
|
||||
fmt.Println(" -apikey API key private key, 40 bytes hex (required)")
|
||||
fmt.Println(" -apikeyindex API key index, 0-255 (default: 0)")
|
||||
fmt.Println(" -testnet Use testnet instead of mainnet")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("=== Lighter API Authentication Test ===")
|
||||
fmt.Printf("Wallet: %s\n", *walletAddr)
|
||||
fmt.Printf("API Key Index: %d\n", *apiKeyIndex)
|
||||
fmt.Printf("Testnet: %v\n", *testnet)
|
||||
fmt.Println()
|
||||
|
||||
// Determine base URL
|
||||
baseURL := "https://mainnet.zklighter.elliot.ai"
|
||||
chainID := uint32(304)
|
||||
if *testnet {
|
||||
baseURL = "https://testnet.zklighter.elliot.ai"
|
||||
chainID = uint32(300)
|
||||
}
|
||||
|
||||
// Create HTTP client
|
||||
httpClient := lighterHTTP.NewClient(baseURL)
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// Step 1: Get account info
|
||||
fmt.Println("Step 1: Getting account info...")
|
||||
accountInfo, err := getAccountByL1Address(client, baseURL, *walletAddr)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Failed to get account info: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("SUCCESS: Account index = %d\n\n", accountInfo.AccountIndex)
|
||||
|
||||
// Step 2: Create TxClient
|
||||
fmt.Println("Step 2: Creating TxClient...")
|
||||
txClient, err := lighterClient.NewTxClient(
|
||||
httpClient,
|
||||
*apiKeyPrivateKey,
|
||||
accountInfo.AccountIndex,
|
||||
uint8(*apiKeyIndex),
|
||||
chainID,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Failed to create TxClient: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Println("SUCCESS: TxClient created\n")
|
||||
|
||||
// Step 3: Generate auth token
|
||||
fmt.Println("Step 3: Generating auth token...")
|
||||
deadline := time.Now().Add(1 * time.Hour)
|
||||
authToken, err := txClient.GetAuthToken(deadline)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Failed to generate auth token: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("SUCCESS: Auth token generated\n")
|
||||
fmt.Printf("Token: %s...\n", authToken[:min(50, len(authToken))])
|
||||
fmt.Printf("Valid until: %s\n\n", deadline.Format(time.RFC3339))
|
||||
|
||||
// Step 4: Test GetActiveOrders API with auth query parameter
|
||||
fmt.Println("Step 4: Testing GetActiveOrders API...")
|
||||
encodedAuth := url.QueryEscape(authToken)
|
||||
endpoint := fmt.Sprintf("%s/api/v1/accountActiveOrders?account_index=%d&market_id=0&auth=%s",
|
||||
baseURL, accountInfo.AccountIndex, encodedAuth)
|
||||
|
||||
fmt.Printf("Endpoint: %s...\n", endpoint[:min(120, len(endpoint))])
|
||||
|
||||
req, err := http.NewRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Failed to create request: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Request failed: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
fmt.Printf("Status: %d\n", resp.StatusCode)
|
||||
fmt.Printf("Response: %s\n\n", string(body))
|
||||
|
||||
// Parse response
|
||||
var apiResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Orders []struct {
|
||||
OrderID string `json:"order_id"`
|
||||
Side string `json:"side"`
|
||||
Type string `json:"type"`
|
||||
Price string `json:"price"`
|
||||
} `json:"orders"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
fmt.Printf("ERROR: Failed to parse response: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if apiResp.Code != 200 {
|
||||
fmt.Printf("API ERROR: code=%d, message=%s\n", apiResp.Code, apiResp.Message)
|
||||
fmt.Println("\n=== DIAGNOSTIC INFO ===")
|
||||
fmt.Println("If you see 'invalid signature', possible causes:")
|
||||
fmt.Println("1. API key is not registered on-chain")
|
||||
fmt.Println("2. API key private key is incorrect")
|
||||
fmt.Println("3. API key index is wrong")
|
||||
fmt.Println("4. Account index mismatch")
|
||||
fmt.Println("\nTo fix:")
|
||||
fmt.Println("- Go to app.lighter.xyz and register/verify your API key")
|
||||
fmt.Println("- Make sure you're using the correct API key private key")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Printf("SUCCESS: Retrieved %d orders\n", len(apiResp.Orders))
|
||||
for i, order := range apiResp.Orders {
|
||||
if i >= 5 {
|
||||
fmt.Printf("... and %d more orders\n", len(apiResp.Orders)-5)
|
||||
break
|
||||
}
|
||||
fmt.Printf(" Order %s: %s %s @ %s\n", order.OrderID, order.Side, order.Type, order.Price)
|
||||
}
|
||||
|
||||
// Step 5: Test GetTrades API (also needs auth)
|
||||
fmt.Println("\nStep 5: Testing GetTrades API...")
|
||||
tradesEndpoint := fmt.Sprintf("%s/api/v1/trades?account_index=%d&sort_by=timestamp&sort_dir=desc&limit=5&auth=%s",
|
||||
baseURL, accountInfo.AccountIndex, encodedAuth)
|
||||
|
||||
tradesReq, _ := http.NewRequest("GET", tradesEndpoint, nil)
|
||||
tradesResp, err := client.Do(tradesReq)
|
||||
if err != nil {
|
||||
fmt.Printf("ERROR: Trades request failed: %v\n", err)
|
||||
} else {
|
||||
defer tradesResp.Body.Close()
|
||||
tradesBody, _ := io.ReadAll(tradesResp.Body)
|
||||
fmt.Printf("Status: %d\n", tradesResp.StatusCode)
|
||||
if tradesResp.StatusCode == 200 {
|
||||
fmt.Println("SUCCESS: GetTrades API working")
|
||||
} else {
|
||||
fmt.Printf("Response: %s\n", string(tradesBody))
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n=== ALL TESTS PASSED ===")
|
||||
}
|
||||
|
||||
// AccountInfo represents Lighter account information
|
||||
type AccountInfo struct {
|
||||
AccountIndex int64 `json:"account_index"`
|
||||
L1Address string `json:"l1_address"`
|
||||
}
|
||||
|
||||
// getAccountByL1Address gets account info by L1 wallet address
|
||||
func getAccountByL1Address(client *http.Client, baseURL, walletAddr string) (*AccountInfo, error) {
|
||||
endpoint := fmt.Sprintf("%s/api/v1/account?by=l1_address&value=%s", baseURL, walletAddr)
|
||||
|
||||
req, err := http.NewRequest("GET", endpoint, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
req = req.WithContext(ctx)
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Parse response - can be in "accounts" or "sub_accounts" field
|
||||
var apiResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Accounts []AccountInfo `json:"accounts"`
|
||||
SubAccounts []AccountInfo `json:"sub_accounts"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &apiResp); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse response: %w, body: %s", err, string(body))
|
||||
}
|
||||
|
||||
// Check main accounts first
|
||||
if len(apiResp.Accounts) > 0 {
|
||||
return &apiResp.Accounts[0], nil
|
||||
}
|
||||
|
||||
// Check sub-accounts
|
||||
if len(apiResp.SubAccounts) > 0 {
|
||||
return &apiResp.SubAccounts[0], nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("no account found for address: %s", walletAddr)
|
||||
}
|
||||
168
scripts/test_lighter_orders.go
Normal file
168
scripts/test_lighter_orders.go
Normal file
@@ -0,0 +1,168 @@
|
||||
//go:build ignore
|
||||
|
||||
// Test script to verify Lighter API authentication
|
||||
// Run: go run scripts/test_lighter_orders.go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
lighterClient "github.com/elliottech/lighter-go/client"
|
||||
lighterHTTP "github.com/elliottech/lighter-go/client/http"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Configuration - update these values
|
||||
walletAddr := os.Getenv("LIGHTER_WALLET")
|
||||
apiKeyPrivateKey := os.Getenv("LIGHTER_API_KEY")
|
||||
|
||||
if walletAddr == "" || apiKeyPrivateKey == "" {
|
||||
fmt.Println("Usage: LIGHTER_WALLET=0x... LIGHTER_API_KEY=... go run scripts/test_lighter_orders.go")
|
||||
fmt.Println("Environment variables required:")
|
||||
fmt.Println(" LIGHTER_WALLET - Ethereum wallet address")
|
||||
fmt.Println(" LIGHTER_API_KEY - API key private key (40 bytes hex)")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
fmt.Println("=== Lighter API Test ===")
|
||||
fmt.Printf("Wallet: %s\n\n", walletAddr)
|
||||
|
||||
baseURL := "https://mainnet.zklighter.elliot.ai"
|
||||
chainID := uint32(304)
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
|
||||
// Step 1: Get account info (no auth required)
|
||||
fmt.Println("1. Getting account info...")
|
||||
accountIndex, err := getAccountIndex(client, baseURL, walletAddr)
|
||||
if err != nil {
|
||||
fmt.Printf(" FAILED: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf(" OK: account_index = %d\n\n", accountIndex)
|
||||
|
||||
// Step 2: Create TxClient and generate auth token
|
||||
fmt.Println("2. Creating TxClient and generating auth token...")
|
||||
httpClient := lighterHTTP.NewClient(baseURL)
|
||||
txClient, err := lighterClient.NewTxClient(httpClient, apiKeyPrivateKey, accountIndex, 0, chainID)
|
||||
if err != nil {
|
||||
fmt.Printf(" FAILED: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
authToken, err := txClient.GetAuthToken(time.Now().Add(1 * time.Hour))
|
||||
if err != nil {
|
||||
fmt.Printf(" FAILED: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf(" OK: auth token generated\n\n")
|
||||
|
||||
// Step 3: Test GetActiveOrders with auth query parameter (NEW method)
|
||||
fmt.Println("3. Testing GetActiveOrders with auth query parameter (FIXED)...")
|
||||
encodedAuth := url.QueryEscape(authToken)
|
||||
endpoint := fmt.Sprintf("%s/api/v1/accountActiveOrders?account_index=%d&market_id=0&auth=%s",
|
||||
baseURL, accountIndex, encodedAuth)
|
||||
|
||||
resp, err := client.Get(endpoint)
|
||||
if err != nil {
|
||||
fmt.Printf(" FAILED: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var result map[string]interface{}
|
||||
json.Unmarshal(body, &result)
|
||||
|
||||
if code, ok := result["code"].(float64); ok && code == 200 {
|
||||
orders := result["orders"].([]interface{})
|
||||
fmt.Printf(" OK: Retrieved %d orders\n", len(orders))
|
||||
if len(orders) > 0 {
|
||||
fmt.Println(" Sample orders:")
|
||||
for i, o := range orders {
|
||||
if i >= 3 {
|
||||
fmt.Printf(" ... and %d more\n", len(orders)-3)
|
||||
break
|
||||
}
|
||||
order := o.(map[string]interface{})
|
||||
fmt.Printf(" - ID: %v, Price: %v, Side: %v\n",
|
||||
order["order_id"], order["price"], order["is_ask"])
|
||||
}
|
||||
}
|
||||
} else {
|
||||
fmt.Printf(" FAILED: %s\n", string(body))
|
||||
fmt.Println("\n Possible causes:")
|
||||
fmt.Println(" - API key not registered on-chain")
|
||||
fmt.Println(" - API key private key incorrect")
|
||||
fmt.Println(" - Account index mismatch")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// Step 4: Test GetActiveOrders with Authorization header (OLD method - for comparison)
|
||||
fmt.Println("\n4. Testing GetActiveOrders with Authorization header (OLD method)...")
|
||||
endpoint2 := fmt.Sprintf("%s/api/v1/accountActiveOrders?account_index=%d&market_id=0",
|
||||
baseURL, accountIndex)
|
||||
|
||||
req, _ := http.NewRequest("GET", endpoint2, nil)
|
||||
req.Header.Set("Authorization", authToken)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
fmt.Printf(" FAILED: %v\n", err)
|
||||
} else {
|
||||
defer resp2.Body.Close()
|
||||
body2, _ := io.ReadAll(resp2.Body)
|
||||
var result2 map[string]interface{}
|
||||
json.Unmarshal(body2, &result2)
|
||||
|
||||
if code, ok := result2["code"].(float64); ok && code == 200 {
|
||||
orders := result2["orders"].([]interface{})
|
||||
fmt.Printf(" OK: Retrieved %d orders (both methods work!)\n", len(orders))
|
||||
} else {
|
||||
fmt.Printf(" FAILED: %s\n", string(body2))
|
||||
fmt.Println(" ^ This is expected - Authorization header doesn't work consistently")
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("\n=== TEST COMPLETE ===")
|
||||
fmt.Println("If test 3 passed, the fix is working correctly.")
|
||||
}
|
||||
|
||||
func getAccountIndex(client *http.Client, baseURL, walletAddr string) (int64, error) {
|
||||
endpoint := fmt.Sprintf("%s/api/v1/account?by=l1_address&value=%s", baseURL, walletAddr)
|
||||
resp, err := client.Get(endpoint)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
var result struct {
|
||||
Code int `json:"code"`
|
||||
Accounts []struct {
|
||||
AccountIndex int64 `json:"account_index"`
|
||||
} `json:"accounts"`
|
||||
SubAccounts []struct {
|
||||
AccountIndex int64 `json:"account_index"`
|
||||
} `json:"sub_accounts"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(body, &result); err != nil {
|
||||
return 0, fmt.Errorf("failed to parse: %w", err)
|
||||
}
|
||||
|
||||
if len(result.Accounts) > 0 {
|
||||
return result.Accounts[0].AccountIndex, nil
|
||||
}
|
||||
if len(result.SubAccounts) > 0 {
|
||||
return result.SubAccounts[0].AccountIndex, nil
|
||||
}
|
||||
|
||||
return 0, fmt.Errorf("no account found")
|
||||
}
|
||||
421
trader/lighter_trader_v2_orders_test.go
Normal file
421
trader/lighter_trader_v2_orders_test.go
Normal file
@@ -0,0 +1,421 @@
|
||||
package trader
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TestGetActiveOrders_ParseResponse tests parsing of Lighter API response
|
||||
func TestGetActiveOrders_ParseResponse(t *testing.T) {
|
||||
// Mock response from Lighter API
|
||||
mockResponse := `{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"orders": [
|
||||
{
|
||||
"order_id": "123456",
|
||||
"order_index": 123456,
|
||||
"market_index": 0,
|
||||
"side": "ask",
|
||||
"type": "limit",
|
||||
"is_ask": true,
|
||||
"price": "3150.50",
|
||||
"initial_base_amount": "1.5",
|
||||
"remaining_base_amount": "1.5",
|
||||
"filled_base_amount": "0",
|
||||
"status": "open",
|
||||
"trigger_price": "",
|
||||
"reduce_only": false,
|
||||
"timestamp": 1736745600000,
|
||||
"created_at": 1736745600000
|
||||
},
|
||||
{
|
||||
"order_id": "123457",
|
||||
"order_index": 123457,
|
||||
"market_index": 0,
|
||||
"side": "bid",
|
||||
"type": "limit",
|
||||
"is_ask": false,
|
||||
"price": "3100.00",
|
||||
"initial_base_amount": "2.0",
|
||||
"remaining_base_amount": "2.0",
|
||||
"filled_base_amount": "0",
|
||||
"status": "open",
|
||||
"trigger_price": "",
|
||||
"reduce_only": false,
|
||||
"timestamp": 1736745601000,
|
||||
"created_at": 1736745601000
|
||||
},
|
||||
{
|
||||
"order_id": "123458",
|
||||
"order_index": 123458,
|
||||
"market_index": 0,
|
||||
"side": "ask",
|
||||
"type": "stop_loss",
|
||||
"is_ask": true,
|
||||
"price": "0",
|
||||
"initial_base_amount": "1.0",
|
||||
"remaining_base_amount": "1.0",
|
||||
"filled_base_amount": "0",
|
||||
"status": "open",
|
||||
"trigger_price": "3000.00",
|
||||
"reduce_only": true,
|
||||
"timestamp": 1736745602000,
|
||||
"created_at": 1736745602000
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
// Parse the response
|
||||
var apiResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(mockResponse), &apiResp)
|
||||
require.NoError(t, err, "Should parse response without error")
|
||||
|
||||
// Verify parsed data
|
||||
assert.Equal(t, 200, apiResp.Code)
|
||||
assert.Equal(t, 3, len(apiResp.Orders))
|
||||
|
||||
// Test first order (sell limit)
|
||||
order1 := apiResp.Orders[0]
|
||||
assert.Equal(t, "123456", order1.OrderID)
|
||||
assert.True(t, order1.IsAsk, "First order should be ask (sell)")
|
||||
assert.Equal(t, "3150.50", order1.Price)
|
||||
assert.Equal(t, "1.5", order1.RemainingBaseAmount)
|
||||
assert.False(t, order1.ReduceOnly)
|
||||
|
||||
// Test second order (buy limit)
|
||||
order2 := apiResp.Orders[1]
|
||||
assert.Equal(t, "123457", order2.OrderID)
|
||||
assert.False(t, order2.IsAsk, "Second order should be bid (buy)")
|
||||
assert.Equal(t, "3100.00", order2.Price)
|
||||
|
||||
// Test third order (stop-loss)
|
||||
order3 := apiResp.Orders[2]
|
||||
assert.Equal(t, "123458", order3.OrderID)
|
||||
assert.Equal(t, "stop_loss", order3.Type)
|
||||
assert.Equal(t, "3000.00", order3.TriggerPrice)
|
||||
assert.True(t, order3.ReduceOnly)
|
||||
}
|
||||
|
||||
// TestGetActiveOrders_EmptyResponse tests handling of empty orders
|
||||
func TestGetActiveOrders_EmptyResponse(t *testing.T) {
|
||||
mockResponse := `{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"orders": []
|
||||
}`
|
||||
|
||||
var apiResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(mockResponse), &apiResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 200, apiResp.Code)
|
||||
assert.Equal(t, 0, len(apiResp.Orders))
|
||||
}
|
||||
|
||||
// TestGetActiveOrders_ErrorResponse tests handling of API error
|
||||
func TestGetActiveOrders_ErrorResponse(t *testing.T) {
|
||||
mockResponse := `{
|
||||
"code": 29500,
|
||||
"message": "internal server error: invalid signature"
|
||||
}`
|
||||
|
||||
var apiResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
}
|
||||
|
||||
err := json.Unmarshal([]byte(mockResponse), &apiResp)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 29500, apiResp.Code)
|
||||
assert.Contains(t, apiResp.Message, "invalid signature")
|
||||
}
|
||||
|
||||
// TestConvertOrderResponseToOpenOrder tests conversion logic
|
||||
func TestConvertOrderResponseToOpenOrder(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
order OrderResponse
|
||||
expectedSide string
|
||||
expectedType string
|
||||
expectedPosSide string
|
||||
}{
|
||||
{
|
||||
name: "Sell limit order (opening short)",
|
||||
order: OrderResponse{
|
||||
OrderID: "1",
|
||||
IsAsk: true,
|
||||
Type: "limit",
|
||||
Price: "3150.00",
|
||||
RemainingBaseAmount: "1.0",
|
||||
ReduceOnly: false,
|
||||
},
|
||||
expectedSide: "SELL",
|
||||
expectedType: "LIMIT",
|
||||
expectedPosSide: "SHORT",
|
||||
},
|
||||
{
|
||||
name: "Buy limit order (opening long)",
|
||||
order: OrderResponse{
|
||||
OrderID: "2",
|
||||
IsAsk: false,
|
||||
Type: "limit",
|
||||
Price: "3100.00",
|
||||
RemainingBaseAmount: "1.0",
|
||||
ReduceOnly: false,
|
||||
},
|
||||
expectedSide: "BUY",
|
||||
expectedType: "LIMIT",
|
||||
expectedPosSide: "LONG",
|
||||
},
|
||||
{
|
||||
name: "Sell stop-loss (closing long)",
|
||||
order: OrderResponse{
|
||||
OrderID: "3",
|
||||
IsAsk: true,
|
||||
Type: "stop_loss",
|
||||
TriggerPrice: "3000.00",
|
||||
RemainingBaseAmount: "1.0",
|
||||
ReduceOnly: true,
|
||||
},
|
||||
expectedSide: "SELL",
|
||||
expectedType: "STOP_MARKET",
|
||||
expectedPosSide: "LONG",
|
||||
},
|
||||
{
|
||||
name: "Buy stop-loss (closing short)",
|
||||
order: OrderResponse{
|
||||
OrderID: "4",
|
||||
IsAsk: false,
|
||||
Type: "stop_loss",
|
||||
TriggerPrice: "3200.00",
|
||||
RemainingBaseAmount: "1.0",
|
||||
ReduceOnly: true,
|
||||
},
|
||||
expectedSide: "BUY",
|
||||
expectedType: "STOP_MARKET",
|
||||
expectedPosSide: "SHORT",
|
||||
},
|
||||
{
|
||||
name: "Take profit (closing long)",
|
||||
order: OrderResponse{
|
||||
OrderID: "5",
|
||||
IsAsk: true,
|
||||
Type: "take_profit",
|
||||
TriggerPrice: "3500.00",
|
||||
RemainingBaseAmount: "1.0",
|
||||
ReduceOnly: true,
|
||||
},
|
||||
expectedSide: "SELL",
|
||||
expectedType: "TAKE_PROFIT_MARKET",
|
||||
expectedPosSide: "LONG",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// Convert side
|
||||
side := "BUY"
|
||||
if tc.order.IsAsk {
|
||||
side = "SELL"
|
||||
}
|
||||
assert.Equal(t, tc.expectedSide, side)
|
||||
|
||||
// Convert order type
|
||||
orderType := "LIMIT"
|
||||
if tc.order.Type == "market" {
|
||||
orderType = "MARKET"
|
||||
} else if tc.order.Type == "stop_loss" || tc.order.Type == "stop" {
|
||||
orderType = "STOP_MARKET"
|
||||
} else if tc.order.Type == "take_profit" {
|
||||
orderType = "TAKE_PROFIT_MARKET"
|
||||
}
|
||||
assert.Equal(t, tc.expectedType, orderType)
|
||||
|
||||
// Convert position side
|
||||
positionSide := "LONG"
|
||||
if tc.order.ReduceOnly {
|
||||
if side == "BUY" {
|
||||
positionSide = "SHORT"
|
||||
} else {
|
||||
positionSide = "LONG"
|
||||
}
|
||||
} else {
|
||||
if side == "SELL" {
|
||||
positionSide = "SHORT"
|
||||
}
|
||||
}
|
||||
assert.Equal(t, tc.expectedPosSide, positionSide)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetActiveOrders_MockServer tests the full HTTP flow with a mock server
|
||||
func TestGetActiveOrders_MockServer(t *testing.T) {
|
||||
// Create mock server
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
// Verify request path and auth parameter
|
||||
assert.Contains(t, r.URL.Path, "/api/v1/accountActiveOrders")
|
||||
|
||||
// Check that auth query parameter is present
|
||||
authParam := r.URL.Query().Get("auth")
|
||||
if authParam == "" {
|
||||
// Return error if no auth parameter
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"code": 29500,
|
||||
"message": "internal server error: invalid signature",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Return success response
|
||||
response := map[string]interface{}{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"orders": []map[string]interface{}{
|
||||
{
|
||||
"order_id": "123456",
|
||||
"order_index": 123456,
|
||||
"market_index": 0,
|
||||
"side": "ask",
|
||||
"type": "limit",
|
||||
"is_ask": true,
|
||||
"price": "3150.50",
|
||||
"initial_base_amount": "1.5",
|
||||
"remaining_base_amount": "1.5",
|
||||
"filled_base_amount": "0",
|
||||
"status": "open",
|
||||
"trigger_price": "",
|
||||
"reduce_only": false,
|
||||
},
|
||||
},
|
||||
}
|
||||
json.NewEncoder(w).Encode(response)
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
// Test request without auth - should fail
|
||||
resp, err := http.Get(server.URL + "/api/v1/accountActiveOrders?account_index=123&market_id=0")
|
||||
require.NoError(t, err)
|
||||
defer resp.Body.Close()
|
||||
|
||||
var errorResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
json.NewDecoder(resp.Body).Decode(&errorResp)
|
||||
assert.Equal(t, 29500, errorResp.Code)
|
||||
|
||||
// Test request with auth - should succeed
|
||||
resp2, err := http.Get(server.URL + "/api/v1/accountActiveOrders?account_index=123&market_id=0&auth=test_token")
|
||||
require.NoError(t, err)
|
||||
defer resp2.Body.Close()
|
||||
|
||||
var successResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
}
|
||||
json.NewDecoder(resp2.Body).Decode(&successResp)
|
||||
assert.Equal(t, 200, successResp.Code)
|
||||
assert.Equal(t, 1, len(successResp.Orders))
|
||||
}
|
||||
|
||||
// TestAuthTokenFormat tests the auth token format
|
||||
func TestAuthTokenFormat(t *testing.T) {
|
||||
// Auth token format: timestamp:account_index:api_key_index:signature
|
||||
// Example: 1768308847:687247:0:742e02...
|
||||
|
||||
sampleToken := "1768308847:687247:0:742e02abc123"
|
||||
|
||||
// The token should be URL encoded when used as query parameter
|
||||
// Colons become %3A
|
||||
expectedEncoded := "1768308847%3A687247%3A0%3A742e02abc123"
|
||||
|
||||
// URL encode the token
|
||||
encoded := url.QueryEscape(sampleToken)
|
||||
|
||||
assert.Equal(t, expectedEncoded, encoded)
|
||||
}
|
||||
|
||||
// TestOrderResponseStruct tests that OrderResponse struct matches API response
|
||||
func TestOrderResponseStruct(t *testing.T) {
|
||||
// Real API response sample (from logs)
|
||||
realResponse := `{
|
||||
"order_id": "4609885",
|
||||
"order_index": 4609885,
|
||||
"market_index": 0,
|
||||
"side": "ask",
|
||||
"type": "limit",
|
||||
"is_ask": true,
|
||||
"price": "3150.00",
|
||||
"initial_base_amount": "0.0300",
|
||||
"remaining_base_amount": "0.0300",
|
||||
"filled_base_amount": "0",
|
||||
"status": "open",
|
||||
"trigger_price": "",
|
||||
"reduce_only": false,
|
||||
"timestamp": 1736745600000,
|
||||
"created_at": 1736745600000
|
||||
}`
|
||||
|
||||
var order OrderResponse
|
||||
err := json.Unmarshal([]byte(realResponse), &order)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "4609885", order.OrderID)
|
||||
assert.Equal(t, int64(4609885), order.OrderIndex)
|
||||
assert.Equal(t, 0, order.MarketIndex)
|
||||
assert.Equal(t, "ask", order.Side)
|
||||
assert.Equal(t, "limit", order.Type)
|
||||
assert.True(t, order.IsAsk)
|
||||
assert.Equal(t, "3150.00", order.Price)
|
||||
assert.Equal(t, "0.0300", order.InitialBaseAmount)
|
||||
assert.Equal(t, "0.0300", order.RemainingBaseAmount)
|
||||
assert.Equal(t, "0", order.FilledBaseAmount)
|
||||
assert.Equal(t, "open", order.Status)
|
||||
assert.Equal(t, "", order.TriggerPrice)
|
||||
assert.False(t, order.ReduceOnly)
|
||||
assert.Equal(t, int64(1736745600000), order.Timestamp)
|
||||
assert.Equal(t, int64(1736745600000), order.CreatedAt)
|
||||
}
|
||||
|
||||
// BenchmarkParseOrderResponse benchmarks response parsing
|
||||
func BenchmarkParseOrderResponse(b *testing.B) {
|
||||
mockResponse := `{
|
||||
"code": 200,
|
||||
"message": "success",
|
||||
"orders": [
|
||||
{"order_id": "1", "is_ask": true, "price": "3150.50", "remaining_base_amount": "1.5"},
|
||||
{"order_id": "2", "is_ask": false, "price": "3100.00", "remaining_base_amount": "2.0"},
|
||||
{"order_id": "3", "is_ask": true, "price": "3200.00", "remaining_base_amount": "0.5"}
|
||||
]
|
||||
}`
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
var apiResp struct {
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Orders []OrderResponse `json:"orders"`
|
||||
}
|
||||
json.Unmarshal([]byte(mockResponse), &apiResp)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user