Files
dex/pkg/api/websocket_server_test.go
zeekayandHanzo Dev 10a084c0b2 refactor(dex): fold pkg/lx into pkg/dex — one matcher, one package
The matching engine (pkg/lx, 45.5K LOC incl. tests) and the deterministic
core layered on it (pkg/dex) are one matcher spread across two packages
(dex imported lx in 12 files; lx imported neither). Fold lx into dex and
drop the "lx" name entirely — pkg/dex is now the single matcher package.

Pure rename/fold, ZERO logic change. Behavior proven identical: the merged
pkg/dex runs 457 top-level tests (431 former-lx + 26 former-dex) and 489
test funcs (468 + 21), all green — matching, batch (BatchMatchEqualsSequential,
run-to-run determinism, tile boundary), GPU/CPU parity, truncation, and CL
swap math all pass unchanged.

What moved:
- git mv all 101 pkg/lx/*.go + 1 .c into pkg/dex/ (package lx -> package dex).
- lx's swap_test.go -> swap_math_test.go (only filename collision with dex's
  existing swap_test.go).
- Rewrote imports repo-wide: 54 pkg/lx import lines removed; 42 external
  importers (api, client, consensus, dchain, dpdk, lxgpu, marketdata, test/,
  tests/) repointed to pkg/dex with lx.X -> dex.X; the 12 in-package dex files
  had lx.X -> bare X. 717 selector lines rewritten. Stale pkg/lx comment refs
  updated (pkg/lxgpu left intact).

Collisions resolved (2, both package-level vars):
- ErrNoLiquidity: lx's pool-level error (dexError, used only in swap.go, never
  referenced via qualifier) renamed ErrPoolNoLiquidity to match the existing
  ErrPool* convention; dex's router-level ErrNoLiquidity (public, has errors.Is
  tests) kept.
- priceMultiplierBig: identical after fold (both big.NewInt(PriceMultiplier));
  deduped by deleting dex's copy.

Not folded — pkg/orderbook: it is NOT the GPU seam (that is orderbook_gpu.go,
which lives in the engine and folded in here). pkg/orderbook is a zero-importer,
placeholder-cgo island whose 13 core domain identifiers (Order, OrderBook, Trade,
OrderType, OrderStatus, PriceLevel, Buy, Sell, Market, Limit, Filled,
PartiallyFilled, NewOrderBook) collide incompatibly with the engine's. Folding
it verbatim breaks the build; folding via rename reintroduces the exact parallel
duplication this fold removes. Left for owner decision (recommend deletion as
dead code) — see refactor notes.

Follow-on (separate repos, not touched): 19 files under luxfi/dex import
pkg/lx externally — lux/precompile/dex, lux/precompile-dex/dex,
lux/consensus/examples/01-simple-bridge, lux/_consensus-v12537/examples,
lux/cex/pkg/engine — repoint to pkg/dex after this lands.

Verified: CGO_ENABLED=0 GOWORK=off go build ./... clean; go vet ./... clean;
go test ./... -count=1 all green (22 ok / 0 fail).

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-02 00:57:08 -07:00

3322 lines
81 KiB
Go

package api
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gorilla/websocket"
"github.com/luxfi/dex/pkg/dex"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// Mock AuthService for testing
type mockAuthService struct {
users map[string]string // apiKey -> userID
}
func newMockAuthService() *mockAuthService {
return &mockAuthService{
users: map[string]string{
"valid_key": "user123",
},
}
}
func (m *mockAuthService) Authenticate(apiKey, apiSecret string) (string, error) {
if userID, ok := m.users[apiKey]; ok && apiSecret == "valid_secret" {
return userID, nil
}
return "", assert.AnError
}
func (m *mockAuthService) ValidateSession(sessionID string) (string, error) {
return "user123", nil
}
func (m *mockAuthService) GetUserID(sessionID string) (string, error) {
return "user123", nil
}
func TestNewWebSocketServer(t *testing.T) {
config := ServerConfig{
AuthService: newMockAuthService(),
}
server := NewWebSocketServer(config)
assert.NotNil(t, server)
assert.NotNil(t, server.clients)
assert.NotNil(t, server.broadcast)
assert.NotNil(t, server.subscriptions)
assert.NotNil(t, server.metrics)
assert.NotNil(t, server.ctx)
assert.NotNil(t, server.cancel)
}
func TestServerMetrics(t *testing.T) {
metrics := NewServerMetrics()
assert.NotNil(t, metrics)
assert.Equal(t, uint64(0), metrics.ConnectionsTotal)
assert.Equal(t, uint64(0), metrics.ConnectionsActive)
assert.Equal(t, uint64(0), metrics.MessagesReceived)
assert.Equal(t, uint64(0), metrics.MessagesSent)
}
func TestServerMetricsIncrement(t *testing.T) {
metrics := NewServerMetrics()
metrics.ConnectionsTotal++
metrics.ConnectionsActive++
metrics.MessagesReceived += 10
metrics.MessagesSent += 5
assert.Equal(t, uint64(1), metrics.ConnectionsTotal)
assert.Equal(t, uint64(1), metrics.ConnectionsActive)
assert.Equal(t, uint64(10), metrics.MessagesReceived)
assert.Equal(t, uint64(5), metrics.MessagesSent)
}
func TestRateLimiter(t *testing.T) {
limiter := NewRateLimiter(5, time.Second)
assert.NotNil(t, limiter)
// First 5 requests should pass
for i := 0; i < 5; i++ {
assert.True(t, limiter.Allow(), "Request %d should be allowed", i+1)
}
// 6th request should be rate limited
assert.False(t, limiter.Allow(), "6th request should be rate limited")
}
func TestRateLimiterReset(t *testing.T) {
limiter := NewRateLimiter(2, 50*time.Millisecond)
// Use up the quota
assert.True(t, limiter.Allow())
assert.True(t, limiter.Allow())
assert.False(t, limiter.Allow())
// Wait for window to reset
time.Sleep(60 * time.Millisecond)
// Should be allowed again
assert.True(t, limiter.Allow())
}
func TestMessageStruct(t *testing.T) {
msg := Message{
Type: "test",
Data: map[string]interface{}{"key": "value"},
Error: "",
RequestID: "req123",
Timestamp: time.Now().Unix(),
}
assert.Equal(t, "test", msg.Type)
assert.Equal(t, "value", msg.Data["key"])
assert.Empty(t, msg.Error)
assert.Equal(t, "req123", msg.RequestID)
assert.Greater(t, msg.Timestamp, int64(0))
}
func TestMessageJSON(t *testing.T) {
msg := Message{
Type: "order_update",
Data: map[string]interface{}{"order_id": "12345"},
Timestamp: 1234567890,
}
data, err := json.Marshal(msg)
require.NoError(t, err)
var decoded Message
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)
assert.Equal(t, msg.Type, decoded.Type)
assert.Equal(t, msg.Timestamp, decoded.Timestamp)
}
func TestClientStruct(t *testing.T) {
client := &Client{
ID: "client123",
UserID: "user456",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
lastActivity: time.Now(),
rateLimiter: NewRateLimiter(100, time.Minute),
}
assert.Equal(t, "client123", client.ID)
assert.Equal(t, "user456", client.UserID)
assert.False(t, client.authenticated)
assert.NotNil(t, client.subscriptions)
assert.NotNil(t, client.rateLimiter)
}
func TestClientSubscriptions(t *testing.T) {
client := &Client{
subscriptions: make(map[string]bool),
}
client.subscriptions["BTC/USDC"] = true
client.subscriptions["ETH/USDC"] = true
assert.True(t, client.subscriptions["BTC/USDC"])
assert.True(t, client.subscriptions["ETH/USDC"])
assert.False(t, client.subscriptions["SOL/USDC"])
}
func TestIsOriginAllowed(t *testing.T) {
tests := []struct {
origin string
expected bool
}{
{"https://lux.exchange", true},
{"https://dex.lux.network", true},
{"https://amm.lux.network", true},
{"http://localhost:3000", true},
{"http://localhost:8080", true},
{"https://malicious.site", false},
{"http://evil.com", false},
}
for _, tt := range tests {
t.Run(tt.origin, func(t *testing.T) {
result := isOriginAllowed(tt.origin)
assert.Equal(t, tt.expected, result)
})
}
}
func TestIsOriginAllowedEmptyList(t *testing.T) {
// Save original and restore after test
original := AllowedOrigins
defer func() { AllowedOrigins = original }()
AllowedOrigins = []string{}
assert.True(t, isOriginAllowed("any-origin"))
}
func TestIsOriginAllowedWildcard(t *testing.T) {
original := AllowedOrigins
defer func() { AllowedOrigins = original }()
AllowedOrigins = []string{"*"}
assert.True(t, isOriginAllowed("any-origin"))
assert.True(t, isOriginAllowed("https://example.com"))
}
func TestServerConfigStruct(t *testing.T) {
config := ServerConfig{
AuthService: newMockAuthService(),
}
assert.NotNil(t, config.AuthService)
}
func TestWebSocketUpgrade(t *testing.T) {
config := ServerConfig{
AuthService: newMockAuthService(),
}
server := NewWebSocketServer(config)
// Create test HTTP server
httpServer := httptest.NewServer(http.HandlerFunc(server.HandleConnection))
defer httpServer.Close()
// Convert HTTP URL to WebSocket URL
wsURL := "ws" + strings.TrimPrefix(httpServer.URL, "http")
// Try to connect
dialer := websocket.Dialer{}
conn, resp, err := dialer.Dial(wsURL, nil)
if err != nil {
// Connection may fail in test environment, that's OK
// We're testing the server behavior
t.Logf("WebSocket dial error (expected in unit test): %v", err)
return
}
defer conn.Close()
assert.Equal(t, http.StatusSwitchingProtocols, resp.StatusCode)
// Read welcome message
_, message, err := conn.ReadMessage()
if err == nil {
var msg Message
json.Unmarshal(message, &msg)
assert.Equal(t, "connected", msg.Type)
}
}
func TestGenerateClientID(t *testing.T) {
id1 := generateClientID()
time.Sleep(time.Nanosecond) // Ensure unique timestamps
id2 := generateClientID()
assert.NotEmpty(t, id1)
assert.NotEmpty(t, id2)
assert.NotEqual(t, id1, id2) // Should be unique
}
func TestNewServerMetrics(t *testing.T) {
metrics := NewServerMetrics()
assert.Equal(t, uint64(0), metrics.ConnectionsTotal)
assert.Equal(t, uint64(0), metrics.ConnectionsActive)
assert.Equal(t, uint64(0), metrics.MessagesReceived)
assert.Equal(t, uint64(0), metrics.MessagesSent)
assert.Equal(t, uint64(0), metrics.SubscriptionsActive)
assert.Equal(t, uint64(0), metrics.AuthFailures)
assert.Equal(t, uint64(0), metrics.OrdersProcessed)
assert.Equal(t, uint64(0), metrics.PositionsOpened)
assert.Equal(t, uint64(0), metrics.LiquidationsExecuted)
assert.Equal(t, uint64(0), metrics.ErrorCount)
}
func TestClientSendMessage(t *testing.T) {
client := &Client{
send: make(chan []byte, 256),
}
msg := Message{
Type: "test",
Timestamp: time.Now().Unix(),
}
// This would normally send via WebSocket
// Testing the channel communication
go func() {
data, _ := json.Marshal(msg)
client.send <- data
}()
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "test", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout waiting for message")
}
}
func TestServerBroadcastChannel(t *testing.T) {
config := ServerConfig{
AuthService: newMockAuthService(),
}
server := NewWebSocketServer(config)
// Test broadcast channel
go func() {
server.broadcast <- []byte(`{"type":"broadcast_test"}`)
}()
select {
case msg := <-server.broadcast:
assert.Contains(t, string(msg), "broadcast_test")
case <-time.After(time.Second):
t.Fatal("Timeout waiting for broadcast")
}
}
func TestAllowedOriginsDefault(t *testing.T) {
// Check default origins list is populated
assert.NotEmpty(t, AllowedOrigins)
assert.Contains(t, AllowedOrigins, "https://lux.exchange")
assert.Contains(t, AllowedOrigins, "https://dex.lux.network")
}
func BenchmarkRateLimiterAllow(b *testing.B) {
limiter := NewRateLimiter(b.N, time.Minute)
b.ResetTimer()
for i := 0; i < b.N; i++ {
limiter.Allow()
}
}
func BenchmarkGenerateClientID(b *testing.B) {
for i := 0; i < b.N; i++ {
generateClientID()
}
}
func BenchmarkMessageMarshal(b *testing.B) {
msg := Message{
Type: "order_update",
Data: map[string]interface{}{
"order_id": "12345",
"status": "filled",
"price": 50000.0,
},
Timestamp: time.Now().Unix(),
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
json.Marshal(msg)
}
}
// Tests for helper functions
func TestParseSide(t *testing.T) {
tests := []struct {
input string
expected dex.Side
}{
{"buy", dex.Buy},
{"BUY", dex.Buy},
{"sell", dex.Sell},
{"SELL", dex.Sell},
{"unknown", dex.Buy}, // default
{"", dex.Buy}, // default
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := parseSide(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestParseOrderType(t *testing.T) {
tests := []struct {
input string
expected dex.OrderType
}{
{"market", dex.Market},
{"MARKET", dex.Market},
{"limit", dex.Limit},
{"LIMIT", dex.Limit},
{"stop", dex.Stop},
{"STOP", dex.Stop},
{"stop_limit", dex.StopLimit},
{"STOP_LIMIT", dex.StopLimit},
{"unknown", dex.Limit}, // default
{"", dex.Limit}, // default
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
result := parseOrderType(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestOppositeSide(t *testing.T) {
assert.Equal(t, dex.Sell, oppositeSide(dex.Buy))
assert.Equal(t, dex.Buy, oppositeSide(dex.Sell))
}
// Test server metrics snapshot
func TestServerMetricsGetSnapshot(t *testing.T) {
metrics := NewServerMetrics()
metrics.ConnectionsTotal = 100
metrics.ConnectionsActive = 50
metrics.MessagesReceived = 1000
metrics.MessagesSent = 500
metrics.SubscriptionsActive = 25
metrics.AuthFailures = 5
metrics.OrdersProcessed = 200
metrics.PositionsOpened = 10
metrics.LiquidationsExecuted = 2
metrics.ErrorCount = 15
snapshot := metrics.GetSnapshot()
assert.Equal(t, uint64(100), snapshot["connections_total"])
assert.Equal(t, uint64(50), snapshot["connections_active"])
assert.Equal(t, uint64(1000), snapshot["messages_received"])
assert.Equal(t, uint64(500), snapshot["messages_sent"])
assert.Equal(t, uint64(25), snapshot["subscriptions_active"])
assert.Equal(t, uint64(5), snapshot["auth_failures"])
assert.Equal(t, uint64(200), snapshot["orders_processed"])
assert.Equal(t, uint64(10), snapshot["positions_opened"])
assert.Equal(t, uint64(2), snapshot["liquidations_executed"])
assert.Equal(t, uint64(15), snapshot["error_count"])
}
// Test client send message
func TestClientSendMessageMethod(t *testing.T) {
client := &Client{
send: make(chan []byte, 256),
}
msg := Message{
Type: "test_message",
Data: map[string]interface{}{"foo": "bar"},
RequestID: "req123",
Timestamp: time.Now().Unix(),
}
client.sendMessage(msg)
select {
case data := <-client.send:
var received Message
err := json.Unmarshal(data, &received)
require.NoError(t, err)
assert.Equal(t, "test_message", received.Type)
assert.Equal(t, "req123", received.RequestID)
case <-time.After(time.Second):
t.Fatal("Timeout waiting for message")
}
}
func TestClientSendMessageFullChannel(t *testing.T) {
// Create client with small buffer
client := &Client{
send: make(chan []byte, 1),
}
// Fill the channel
client.send <- []byte("existing")
// This should not block (uses select with default)
msg := Message{Type: "test"}
client.sendMessage(msg)
// Drain channel
<-client.send
}
// Test client send error
func TestClientSendError(t *testing.T) {
client := &Client{
send: make(chan []byte, 256),
}
client.sendError("Test error message", "req456")
select {
case data := <-client.send:
var received Message
err := json.Unmarshal(data, &received)
require.NoError(t, err)
assert.Equal(t, "error", received.Type)
assert.Equal(t, "Test error message", received.Error)
assert.Equal(t, "req456", received.RequestID)
case <-time.After(time.Second):
t.Fatal("Timeout waiting for error message")
}
}
// Create helper to make test server with all components
func newTestWebSocketServer() *WebSocketServer {
oracle := dex.NewPriceOracle()
lendingPool := dex.NewLendingPool()
riskEngine := dex.NewRiskEngine()
marginEngine := dex.NewMarginEngine(oracle, riskEngine)
liquidationEngine := dex.NewLiquidationEngine()
tradingEngine := dex.NewTradingEngine(dex.EngineConfig{
EnablePerps: true,
EnableVaults: true,
EnableLending: true,
})
vaultManager := dex.NewVaultManager(tradingEngine)
config := ServerConfig{
Engine: tradingEngine,
MarginEngine: marginEngine,
LendingPool: lendingPool,
Oracle: oracle,
VaultManager: vaultManager,
LiquidationEngine: liquidationEngine,
AuthService: newMockAuthService(),
}
return NewWebSocketServer(config)
}
// Test process message with various types
func TestProcessMessageInvalidType(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
// Message without type
msg := map[string]interface{}{
"data": "some data",
}
server.processMessage(client, msg)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid message type")
case <-time.After(time.Second):
t.Fatal("Timeout waiting for error")
}
}
func TestProcessMessageUnknownType(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "unknown_message_type",
}
server.processMessage(client, msg)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Unknown message type")
case <-time.After(time.Second):
t.Fatal("Timeout waiting for error")
}
}
func TestProcessMessagePing(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "ping",
"request_id": "ping123",
}
server.processMessage(client, msg)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "pong", received.Type)
assert.Equal(t, "ping123", received.RequestID)
case <-time.After(time.Second):
t.Fatal("Timeout waiting for pong")
}
}
// Test authentication
func TestHandleAuthMissingCredentials(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
// Missing apiKey and apiSecret
msg := map[string]interface{}{
"type": "auth",
}
server.handleAuth(client, msg, "auth_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing credentials")
case <-time.After(time.Second):
t.Fatal("Timeout waiting for error")
}
assert.Equal(t, uint64(1), server.metrics.AuthFailures)
}
func TestHandleAuthInvalidCredentials(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "auth",
"apiKey": "invalid_key",
"apiSecret": "invalid_secret",
}
server.handleAuth(client, msg, "auth_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Authentication failed")
case <-time.After(time.Second):
t.Fatal("Timeout waiting for error")
}
assert.Equal(t, uint64(1), server.metrics.AuthFailures)
}
// Test subscriptions
func TestHandleSubscribeMissingChannel(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "subscribe",
"symbols": []interface{}{"BTC-USDT"},
}
server.handleSubscribe(client, msg, "sub_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid channel")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleSubscribeMissingSymbols(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "subscribe",
"channel": "orderbook",
}
server.handleSubscribe(client, msg, "sub_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing symbols")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleSubscribeSuccess(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "subscribe",
"channel": "orderbook",
"symbols": []interface{}{"BTC-USDT", "ETH-USDT"},
}
server.handleSubscribe(client, msg, "sub_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "subscribed", received.Type)
assert.Equal(t, "orderbook", received.Data["channel"])
case <-time.After(time.Second):
t.Fatal("Timeout")
}
// Check client subscriptions
assert.True(t, client.subscriptions["orderbook:BTC-USDT"])
assert.True(t, client.subscriptions["orderbook:ETH-USDT"])
}
func TestHandleUnsubscribeSuccess(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
// First subscribe
client.subscriptions["orderbook:BTC-USDT"] = true
server.mu.Lock()
server.subscriptions[client.ID] = map[string]bool{"BTC-USDT": true}
server.metrics.SubscriptionsActive = 1
server.mu.Unlock()
// Then unsubscribe
msg := map[string]interface{}{
"type": "unsubscribe",
"channel": "orderbook",
"symbols": []interface{}{"BTC-USDT"},
}
server.handleUnsubscribe(client, msg, "unsub_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "unsubscribed", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
assert.False(t, client.subscriptions["orderbook:BTC-USDT"])
}
// Test place order (unauthenticated)
func TestHandlePlaceOrderUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
"order": map[string]interface{}{
"symbol": "BTC-USDT",
"side": "buy",
"type": "limit",
"price": 50000.0,
"size": 0.1,
},
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandlePlaceOrderMissingOrderData(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing order data")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandlePlaceOrderInvalidSymbol(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
"order": map[string]interface{}{
"side": "buy",
"type": "limit",
"price": 50000.0,
"size": 0.1,
},
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid symbol")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test cancel order
func TestHandleCancelOrderUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "cancel_order",
"orderID": float64(123),
}
server.handleCancelOrder(client, msg, "cancel_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleCancelOrderMissingOrderID(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "cancel_order",
}
server.handleCancelOrder(client, msg, "cancel_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing orderID")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test open position
func TestHandleOpenPositionUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "open_position",
"symbol": "BTC-USDT",
"side": "buy",
"size": 1.0,
"leverage": 10.0,
}
server.handleOpenPosition(client, msg, "pos_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleOpenPositionMissingSymbol(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "open_position",
"side": "buy",
"size": 1.0,
"leverage": 10.0,
}
server.handleOpenPosition(client, msg, "pos_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid symbol")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test close position
func TestHandleClosePositionUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "close_position",
"positionID": "pos123",
"size": 1.0,
}
server.handleClosePosition(client, msg, "close_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test vault deposit
func TestHandleVaultDepositUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_deposit",
"vaultID": "vault123",
"amount": "1000000000000000000",
}
server.handleVaultDeposit(client, msg, "vault_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test lending supply
func TestHandleLendingSupplyUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_supply",
"asset": "USDC",
"amount": "1000000000",
}
server.handleLendingSupply(client, msg, "supply_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test get balances
func TestHandleGetBalancesUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetBalances(client, "balance_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test get positions
func TestHandleGetPositionsUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetPositions(client, "pos_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test get orders
func TestHandleGetOrdersUnauthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetOrders(client, "orders_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test modify order
func TestHandleModifyOrder(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "modify_order",
"orderID": float64(123),
"newPrice": 51000.0,
"newSize": 0.2,
}
server.handleModifyOrder(client, msg, "modify_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "order_modified", received.Type)
assert.Equal(t, float64(123), received.Data["order_id"])
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test modify leverage
func TestHandleModifyLeverageMissingPositionID(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "modify_leverage",
"leverage": 20.0,
}
server.handleModifyLeverage(client, msg, "lev_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid position_id")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test vault withdraw
func TestHandleVaultWithdrawMissingVaultID(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_withdraw",
"amount": "1000",
}
server.handleVaultWithdraw(client, msg, "withdraw_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid vault_id")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test lending borrow
func TestHandleLendingBorrowMissingAsset(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_borrow",
"amount": "1000",
}
server.handleLendingBorrow(client, msg, "borrow_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid asset")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test lending repay
func TestHandleLendingRepayMissingAsset(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_repay",
"amount": "1000",
"interest": "50",
}
server.handleLendingRepay(client, msg, "repay_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid asset")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test broadcast methods
func TestBroadcastOrderBook(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: map[string]bool{"orderbook:BTC-USDT": true},
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.mu.Lock()
server.clients[client.ID] = client
server.mu.Unlock()
snapshot := &dex.OrderBookSnapshot{
Symbol: "BTC-USDT",
Bids: []dex.OrderLevel{{Price: 50000, Size: 1.0}},
Asks: []dex.OrderLevel{{Price: 50100, Size: 2.0}},
}
server.BroadcastOrderBook("BTC-USDT", snapshot)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "orderbook_update", received.Type)
assert.Equal(t, "BTC-USDT", received.Data["symbol"])
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestBroadcastTrade(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: map[string]bool{"trades": true},
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.mu.Lock()
server.clients[client.ID] = client
server.mu.Unlock()
trade := &dex.Trade{
Price: 50000.0,
Size: 1.5,
Timestamp: time.Now(),
}
server.BroadcastTrade(trade)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "trade_update", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestBroadcastPrice(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: map[string]bool{"prices:BTC-USDT": true},
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.mu.Lock()
server.clients[client.ID] = client
server.mu.Unlock()
server.BroadcastPrice("BTC-USDT", 51234.56)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "price_update", received.Type)
assert.Equal(t, "BTC-USDT", received.Data["symbol"])
assert.Equal(t, 51234.56, received.Data["price"])
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Test remove client
func TestRemoveClient(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.mu.Lock()
server.clients[client.ID] = client
server.subscriptions[client.ID] = map[string]bool{"BTC-USDT": true}
server.metrics.ConnectionsActive = 1
server.mu.Unlock()
server.removeClient(client)
server.mu.RLock()
_, exists := server.clients[client.ID]
_, subExists := server.subscriptions[client.ID]
activeConns := server.metrics.ConnectionsActive
server.mu.RUnlock()
assert.False(t, exists)
assert.False(t, subExists)
assert.Equal(t, uint64(0), activeConns)
}
// Test shutdown
func TestServerShutdown(t *testing.T) {
server := newTestWebSocketServer()
// The Shutdown function should not panic
assert.NotPanics(t, func() {
server.Shutdown()
})
// Context should be cancelled
select {
case <-server.ctx.Done():
// Context cancelled as expected
default:
t.Fatal("Context should be cancelled after shutdown")
}
}
// Benchmarks for message handlers
func BenchmarkProcessMessagePing(b *testing.B) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(b.N*2, time.Minute),
}
msg := map[string]interface{}{
"type": "ping",
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
server.processMessage(client, msg)
<-client.send // drain
}
}
func BenchmarkBroadcastOrderBook(b *testing.B) {
server := newTestWebSocketServer()
// Add 100 clients
for i := 0; i < 100; i++ {
client := &Client{
ID: fmt.Sprintf("client%d", i),
send: make(chan []byte, 256),
subscriptions: map[string]bool{"orderbook:BTC-USDT": true},
rateLimiter: NewRateLimiter(b.N*2, time.Minute),
}
server.clients[client.ID] = client
}
snapshot := &dex.OrderBookSnapshot{
Symbol: "BTC-USDT",
Bids: []dex.OrderLevel{{Price: 50000, Size: 1.0}},
Asks: []dex.OrderLevel{{Price: 50100, Size: 2.0}},
}
b.ResetTimer()
for i := 0; i < b.N; i++ {
server.BroadcastOrderBook("BTC-USDT", snapshot)
}
}
// Additional tests for better coverage
func TestHandlePlaceOrderSuccess(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
"order": map[string]interface{}{
"symbol": "BTC-USDT",
"side": "buy",
"type": "limit",
"price": 50000.0,
"size": 0.1,
},
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
// Either order_update or error is acceptable
assert.Contains(t, []string{"order_update", "error"}, received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandlePlaceOrderMissingSide(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
"order": map[string]interface{}{
"symbol": "BTC-USDT",
"type": "limit",
"price": 50000.0,
"size": 0.1,
},
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid side")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandlePlaceOrderMissingType(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
"order": map[string]interface{}{
"symbol": "BTC-USDT",
"side": "buy",
"price": 50000.0,
"size": 0.1,
},
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid order type")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandlePlaceOrderMissingPrice(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
"order": map[string]interface{}{
"symbol": "BTC-USDT",
"side": "buy",
"type": "limit",
"size": 0.1,
},
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid price")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandlePlaceOrderMissingSize(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
"order": map[string]interface{}{
"symbol": "BTC-USDT",
"side": "buy",
"type": "limit",
"price": 50000.0,
},
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid size")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandlePlaceOrderInvalidOrderDataFormat(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "place_order",
"order": "not a map",
}
server.handlePlaceOrder(client, msg, "order_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid order data format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleCancelOrderInvalidFormat(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "cancel_order",
"orderID": "not a number",
}
server.handleCancelOrder(client, msg, "cancel_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid orderID format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleCancelOrderNotFound(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "cancel_order",
"orderID": float64(99999),
}
server.handleCancelOrder(client, msg, "cancel_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "order not found")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleOpenPositionMissingSide(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "open_position",
"symbol": "BTC-USDT",
"size": 1.0,
"leverage": 10.0,
}
server.handleOpenPosition(client, msg, "pos_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid side")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleOpenPositionMissingSize(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "open_position",
"symbol": "BTC-USDT",
"side": "buy",
"leverage": 10.0,
}
server.handleOpenPosition(client, msg, "pos_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid size")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleOpenPositionMissingLeverage(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "open_position",
"symbol": "BTC-USDT",
"side": "buy",
"size": 1.0,
}
server.handleOpenPosition(client, msg, "pos_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid leverage")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleClosePositionMissingPositionID(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "close_position",
"size": 1.0,
}
server.handleClosePosition(client, msg, "close_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid positionID")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleClosePositionMissingSize(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "close_position",
"positionID": "pos123",
}
server.handleClosePosition(client, msg, "close_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid size")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleVaultDepositMissingVaultID(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_deposit",
"amount": "1000",
}
server.handleVaultDeposit(client, msg, "vault_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid vaultID")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleVaultDepositMissingAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_deposit",
"vaultID": "vault123",
}
server.handleVaultDeposit(client, msg, "vault_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid amount")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleVaultDepositInvalidAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_deposit",
"vaultID": "vault123",
"amount": "invalid_number",
}
server.handleVaultDeposit(client, msg, "vault_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid amount format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingSupplyMissingAsset(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_supply",
"amount": "1000",
}
server.handleLendingSupply(client, msg, "supply_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid asset")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingSupplyMissingAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_supply",
"asset": "USDC",
}
server.handleLendingSupply(client, msg, "supply_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid amount")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingSupplyInvalidAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_supply",
"asset": "USDC",
"amount": "not_a_number",
}
server.handleLendingSupply(client, msg, "supply_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid amount format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleUnsubscribeMissingChannel(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "unsubscribe",
"symbols": []interface{}{"BTC-USDT"},
}
server.handleUnsubscribe(client, msg, "unsub_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid channel")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleUnsubscribeMissingSymbols(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "unsubscribe",
"channel": "orderbook",
}
server.handleUnsubscribe(client, msg, "unsub_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing symbols")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleUnsubscribeInvalidSymbolsFormat(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "unsubscribe",
"channel": "orderbook",
"symbols": "not an array",
}
server.handleUnsubscribe(client, msg, "unsub_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid symbols format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleSubscribeInvalidSymbolsFormat(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "subscribe",
"channel": "orderbook",
"symbols": "not an array",
}
server.handleSubscribe(client, msg, "sub_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid symbols format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleModifyLeverageMissingLeverage(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "modify_leverage",
"position_id": "pos123",
}
server.handleModifyLeverage(client, msg, "lev_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid leverage")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleVaultWithdrawMissingAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_withdraw",
"vault_id": "vault123",
}
server.handleVaultWithdraw(client, msg, "withdraw_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid amount")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleVaultWithdrawInvalidAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_withdraw",
"vault_id": "vault123",
"amount": "not_a_number",
}
server.handleVaultWithdraw(client, msg, "withdraw_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid amount format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingBorrowMissingAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_borrow",
"asset": "USDC",
}
server.handleLendingBorrow(client, msg, "borrow_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid amount")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingBorrowInvalidAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_borrow",
"asset": "USDC",
"amount": "invalid",
}
server.handleLendingBorrow(client, msg, "borrow_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid amount format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingRepayMissingAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_repay",
"asset": "USDC",
"interest": "50",
}
server.handleLendingRepay(client, msg, "repay_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid amount")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingRepayMissingInterest(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_repay",
"asset": "USDC",
"amount": "1000",
}
server.handleLendingRepay(client, msg, "repay_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing or invalid interest")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingRepayInvalidAmount(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_repay",
"asset": "USDC",
"amount": "invalid",
"interest": "50",
}
server.handleLendingRepay(client, msg, "repay_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid amount format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingRepayInvalidInterest(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_repay",
"asset": "USDC",
"amount": "1000",
"interest": "invalid",
}
server.handleLendingRepay(client, msg, "repay_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid interest format")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleModifyOrderAlternateFieldNames(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
// Test with alternate field names (order_id, price, size)
msg := map[string]interface{}{
"type": "modify_order",
"order_id": float64(456),
"price": 52000.0,
"size": 0.3,
}
server.handleModifyOrder(client, msg, "modify_req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "order_modified", received.Type)
assert.Equal(t, float64(456), received.Data["order_id"])
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestNotifyLiquidationNoClient(t *testing.T) {
server := newTestWebSocketServer()
// Create a mock position
position := &dex.MarginPosition{
ID: "pos123",
Symbol: "BTC-USDT",
Side: dex.Buy,
Size: 1.0,
EntryPrice: 50000.0,
MarkPrice: 48000.0,
}
// Should not panic even with no matching client
assert.NotPanics(t, func() {
server.notifyLiquidation("nonexistent_user", position)
})
}
func TestNotifyLiquidationWithClient(t *testing.T) {
server := newTestWebSocketServer()
// Create client
client := &Client{
ID: "client1",
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.mu.Lock()
server.clients[client.ID] = client
server.mu.Unlock()
// Create a mock position
position := &dex.MarginPosition{
ID: "pos123",
Symbol: "BTC-USDT",
Side: dex.Buy,
Size: 1.0,
EntryPrice: 50000.0,
MarkPrice: 48000.0,
}
server.notifyLiquidation("user123", position)
// Check client received message
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "position_update", received.Type)
assert.Equal(t, "liquidated", received.Data["action"])
case <-time.After(time.Second):
t.Fatal("Timeout")
}
// Also check broadcast channel
select {
case data := <-server.broadcast:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "public_liquidation", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout waiting for broadcast")
}
}
func TestNotifyLiquidationSellSide(t *testing.T) {
server := newTestWebSocketServer()
// Create client
client := &Client{
ID: "client1",
UserID: "user123",
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.mu.Lock()
server.clients[client.ID] = client
server.mu.Unlock()
// Create a SELL position
position := &dex.MarginPosition{
ID: "pos123",
Symbol: "BTC-USDT",
Side: dex.Sell,
Size: 1.0,
EntryPrice: 50000.0,
MarkPrice: 52000.0,
}
server.notifyLiquidation("user123", position)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "position_update", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestCheckLiquidations(t *testing.T) {
server := newTestWebSocketServer()
// Should not panic even without positions
assert.NotPanics(t, func() {
server.checkLiquidations()
})
}
func TestBroadcastToSubscribersNonMatching(t *testing.T) {
server := newTestWebSocketServer()
// Add client subscribed to different channel
client := &Client{
ID: "client1",
send: make(chan []byte, 256),
subscriptions: map[string]bool{"orderbook:ETH-USDT": true}, // Different symbol
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.mu.Lock()
server.clients[client.ID] = client
server.mu.Unlock()
msg := Message{
Type: "test",
}
// Broadcast to non-matching channel
server.broadcastToSubscribers("orderbook:BTC-USDT", msg)
// Client should not receive message
select {
case <-client.send:
t.Fatal("Client should not have received message")
case <-time.After(50 * time.Millisecond):
// Expected - no message received
}
}
func TestBroadcastToSubscribersFullChannel(t *testing.T) {
server := newTestWebSocketServer()
// Create client with small buffer that's already full
client := &Client{
ID: "client1",
send: make(chan []byte, 1),
subscriptions: map[string]bool{"orderbook:BTC-USDT": true},
rateLimiter: NewRateLimiter(100, time.Minute),
}
// Fill the channel
client.send <- []byte("existing")
server.mu.Lock()
server.clients[client.ID] = client
server.mu.Unlock()
msg := Message{
Type: "test",
}
// Should not block even with full channel
server.broadcastToSubscribers("orderbook:BTC-USDT", msg)
// Drain the channel
<-client.send
}
// Tests for handleGetBalances
func TestHandleGetBalancesNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetBalances(client, "req123")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleGetBalancesAccountNotFound(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "unknown_user",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetBalances(client, "req123")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Account not found")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleGetPositions
func TestHandleGetPositionsNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetPositions(client, "req123")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleGetPositionsAccountNotFound(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "unknown_user",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetPositions(client, "req123")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Account not found")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleGetOrders
func TestHandleGetOrdersNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetOrders(client, "req123")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleGetOrdersSuccess(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
server.handleGetOrders(client, "req123")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "orders_update", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for processMessage coverage - additional paths
func TestProcessMessageInvalidType2(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": 12345, // Invalid - not a string
}
server.processMessage(client, msg)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Invalid message type")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestProcessMessageWithRequestID(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "ping",
"request_id": "req-12345",
}
server.processMessage(client, msg)
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "pong", received.Type)
assert.Equal(t, "req-12345", received.RequestID)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleOpenPosition additional coverage
func TestHandleOpenPositionInvalidSideValue(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "open_position",
"position": map[string]interface{}{
"symbol": "BTC-USDT",
"side": "invalid_side",
"size": 1.0,
"leverage": 10.0,
},
}
server.handleOpenPosition(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
// Should handle invalid side or process it
assert.Contains(t, []string{"position_update", "error"}, received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleClosePosition additional coverage
func TestHandleClosePositionInvalidSizeFormat(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "close_position",
"position": map[string]interface{}{
"position_id": "pos123",
"size": "not_a_number",
},
}
server.handleClosePosition(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
// Should return error for invalid size
assert.Equal(t, "error", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleModifyLeverage additional coverage
func TestHandleModifyLeverageNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "modify_leverage",
"position": map[string]interface{}{
"position_id": "pos123",
"leverage": 20.0,
},
}
server.handleModifyLeverage(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
// Error message check - validation happens before auth check
assert.True(t, received.Error != "")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleModifyLeverageInvalidPositionData(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "modify_leverage",
"position": "not_a_map",
}
server.handleModifyLeverage(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleCancelOrder additional coverage
func TestHandleCancelOrderNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "cancel_order",
"order_id": "order123",
}
server.handleCancelOrder(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for BroadcastPrice
func TestBroadcastPriceNoSubscribers(t *testing.T) {
server := newTestWebSocketServer()
// No clients subscribed, should not panic
assert.NotPanics(t, func() {
server.BroadcastPrice("BTC-USDT", 50000.0)
})
}
// Tests for handleVaultDeposit additional coverage
func TestHandleVaultDepositNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_deposit",
"deposit": map[string]interface{}{
"vault_id": "vault123",
"amount": "100.0",
},
}
server.handleVaultDeposit(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleVaultDepositInvalidDepositData(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_deposit",
"deposit": "not_a_map",
}
server.handleVaultDeposit(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleVaultWithdraw additional coverage
func TestHandleVaultWithdrawNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_withdraw",
"withdraw": map[string]interface{}{
"vault_id": "vault123",
"amount": "50.0",
},
}
server.handleVaultWithdraw(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
// Error message check - validation happens before auth check
assert.True(t, received.Error != "")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleVaultWithdrawInvalidWithdrawData(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "vault_withdraw",
"withdraw": "not_a_map",
}
server.handleVaultWithdraw(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleLendingSupply additional coverage
func TestHandleLendingSupplyNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_supply",
"supply": map[string]interface{}{
"asset": "USDC",
"amount": "1000.0",
},
}
server.handleLendingSupply(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Not authenticated")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingSupplyInvalidSupplyData(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_supply",
"supply": "not_a_map",
}
server.handleLendingSupply(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleLendingBorrow additional coverage
func TestHandleLendingBorrowNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_borrow",
"borrow": map[string]interface{}{
"asset": "USDC",
"amount": "500.0",
},
}
server.handleLendingBorrow(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
// Error message check - validation happens before auth check
assert.True(t, received.Error != "")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingBorrowInvalidBorrowData(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_borrow",
"borrow": "not_a_map",
}
server.handleLendingBorrow(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleLendingRepay additional coverage
func TestHandleLendingRepayNotAuthenticated(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
authenticated: false,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_repay",
"repay": map[string]interface{}{
"asset": "USDC",
"amount": "250.0",
"interest": "10.0",
},
}
server.handleLendingRepay(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
// Error message check - validation happens before auth check
assert.True(t, received.Error != "")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleLendingRepayInvalidRepayData(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
UserID: "user123",
send: make(chan []byte, 256),
authenticated: true,
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "lending_repay",
"repay": "not_a_map",
}
server.handleLendingRepay(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
// Tests for handleSubscribe additional coverage - more paths
func TestHandleSubscribeMissingChannel2(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "subscribe",
"symbols": []interface{}{"BTC-USDT"},
}
server.handleSubscribe(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "channel")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}
func TestHandleSubscribeMissingSymbols2(t *testing.T) {
server := newTestWebSocketServer()
client := &Client{
send: make(chan []byte, 256),
subscriptions: make(map[string]bool),
rateLimiter: NewRateLimiter(100, time.Minute),
}
msg := map[string]interface{}{
"type": "subscribe",
"channel": "orderbook",
}
server.handleSubscribe(client, msg, "req")
select {
case data := <-client.send:
var received Message
json.Unmarshal(data, &received)
assert.Equal(t, "error", received.Type)
assert.Contains(t, received.Error, "Missing symbols")
case <-time.After(time.Second):
t.Fatal("Timeout")
}
}