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") } }