From 2ba3f059a193d4c04ed294b1abbf06f2c77fc5d0 Mon Sep 17 00:00:00 2001 From: Sudo-Ivan Date: Tue, 30 Dec 2025 21:14:57 -0600 Subject: [PATCH] test: add comprehensive WebSocket interface tests for connection, key generation, and message handling --- pkg/interfaces/websocket_native_test.go | 280 ++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 pkg/interfaces/websocket_native_test.go diff --git a/pkg/interfaces/websocket_native_test.go b/pkg/interfaces/websocket_native_test.go new file mode 100644 index 0000000..1bb1015 --- /dev/null +++ b/pkg/interfaces/websocket_native_test.go @@ -0,0 +1,280 @@ +package interfaces + +import ( + "testing" + "time" + + "git.quad4.io/Networks/Reticulum-Go/pkg/common" +) + +func TestWebSocketGUID(t *testing.T) { + if wsGUID != "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" { + t.Errorf("wsGUID mismatch: expected RFC 6455 GUID, got %s", wsGUID) + } +} + +func TestGenerateWebSocketKey(t *testing.T) { + key1, err := generateWebSocketKey() + if err != nil { + t.Fatalf("Failed to generate key: %v", err) + } + + key2, err := generateWebSocketKey() + if err != nil { + t.Fatalf("Failed to generate key: %v", err) + } + + if key1 == key2 { + t.Error("Generated keys should be unique") + } + + if len(key1) != 24 { + t.Errorf("Expected base64-encoded key length 24, got %d", len(key1)) + } +} + +func TestComputeAcceptKey(t *testing.T) { + testKey := "dGhlIHNhbXBsZSBub25jZQ==" + expectedAccept := "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" + + accept := computeAcceptKey(testKey) + if accept != expectedAccept { + t.Errorf("Accept key mismatch: expected %s, got %s", expectedAccept, accept) + } +} + +func TestNewWebSocketInterface(t *testing.T) { + ws, err := NewWebSocketInterface("test", "wss://socket.quad4.io/ws", true) + if err != nil { + t.Fatalf("Failed to create WebSocket interface: %v", err) + } + + if ws.GetName() != "test" { + t.Errorf("Expected name 'test', got %s", ws.GetName()) + } + + if ws.GetType() != common.IF_TYPE_UDP { + t.Errorf("Expected type IF_TYPE_UDP, got %v", ws.GetType()) + } + + if ws.GetMTU() != 1064 { + t.Errorf("Expected MTU 1064, got %d", ws.GetMTU()) + } + + if ws.IsOnline() { + t.Error("Interface should not be online before Start()") + } +} + +func TestWebSocketConnection(t *testing.T) { + if testing.Short() { + t.Skip("Skipping network test in short mode") + } + + ws, err := NewWebSocketInterface("test", "wss://socket.quad4.io/ws", true) + if err != nil { + t.Fatalf("Failed to create WebSocket interface: %v", err) + } + + ws.SetPacketCallback(func(data []byte, ni common.NetworkInterface) { + t.Logf("Received packet: %d bytes", len(data)) + }) + + err = ws.Start() + if err != nil { + t.Fatalf("Failed to start WebSocket: %v", err) + } + + time.Sleep(2 * time.Second) + + if !ws.IsOnline() { + t.Error("WebSocket should be online after Start()") + } + + testData := []byte{0x01, 0x02, 0x03, 0x04} + err = ws.Send(testData, "") + if err != nil { + t.Errorf("Failed to send data: %v", err) + } + + time.Sleep(1 * time.Second) + + if err := ws.Stop(); err != nil { + t.Errorf("Failed to stop WebSocket: %v", err) + } + + time.Sleep(500 * time.Millisecond) + + if ws.IsOnline() { + t.Error("WebSocket should be offline after Stop()") + } +} + +func TestWebSocketReconnection(t *testing.T) { + if testing.Short() { + t.Skip("Skipping network test in short mode") + } + + ws, err := NewWebSocketInterface("test", "wss://socket.quad4.io/ws", true) + if err != nil { + t.Fatalf("Failed to create WebSocket interface: %v", err) + } + + err = ws.Start() + if err != nil { + t.Fatalf("Failed to start WebSocket: %v", err) + } + + time.Sleep(1 * time.Second) + + if !ws.IsOnline() { + t.Error("WebSocket should be online") + } + + conn := ws.GetConn() + if conn == nil { + t.Error("GetConn() should return a connection") + } + + conn.Close() + + time.Sleep(3 * time.Second) + + if ws.IsOnline() { + t.Log("WebSocket reconnected successfully") + } + + if err := ws.Stop(); err != nil { + t.Errorf("Failed to stop WebSocket: %v", err) + } + + time.Sleep(500 * time.Millisecond) +} + +func TestWebSocketMessageQueue(t *testing.T) { + ws, err := NewWebSocketInterface("test", "wss://socket.quad4.io/ws", true) + if err != nil { + t.Fatalf("Failed to create WebSocket interface: %v", err) + } + + ws.Enable() + + testData := []byte{0x01, 0x02, 0x03} + err = ws.Send(testData, "") + if err != nil { + t.Errorf("Send should queue message when offline, got error: %v", err) + } + + if testing.Short() { + return + } + + err = ws.Start() + if err != nil { + t.Fatalf("Failed to start WebSocket: %v", err) + } + + // Wait for interface to be online (up to 10 seconds) + for i := 0; i < 100; i++ { + if ws.IsOnline() { + break + } + time.Sleep(100 * time.Millisecond) + } + + if !ws.IsOnline() { + t.Error("WebSocket should be online") + } + + time.Sleep(2 * time.Second) + + if err := ws.Stop(); err != nil { + t.Errorf("Failed to stop WebSocket: %v", err) + } + + time.Sleep(500 * time.Millisecond) +} + +func TestWebSocketFrameEncoding(t *testing.T) { + if testing.Short() { + t.Skip("Skipping frame encoding test in short mode") + } + + ws, err := NewWebSocketInterface("test", "wss://socket.quad4.io/ws", true) + if err != nil { + t.Fatalf("Failed to create WebSocket interface: %v", err) + } + + err = ws.Start() + if err != nil { + t.Fatalf("Failed to start WebSocket: %v", err) + } + + time.Sleep(1 * time.Second) + + testCases := []struct { + name string + data []byte + }{ + {"small frame", []byte{0x01, 0x02, 0x03}}, + {"medium frame", make([]byte, 200)}, + {"large frame", make([]byte, 1000)}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := ws.Send(tc.data, "") + if err != nil { + t.Errorf("Failed to send %s: %v", tc.name, err) + } + time.Sleep(100 * time.Millisecond) + }) + } + + if err := ws.Stop(); err != nil { + t.Errorf("Failed to stop WebSocket: %v", err) + } + + time.Sleep(500 * time.Millisecond) +} + +func TestWebSocketEnableDisable(t *testing.T) { + ws, err := NewWebSocketInterface("test", "wss://socket.quad4.io/ws", false) + if err != nil { + t.Fatalf("Failed to create WebSocket interface: %v", err) + } + + if ws.IsEnabled() { + t.Error("Interface should not be enabled initially") + } + + ws.Enable() + if !ws.IsEnabled() { + t.Error("Interface should be enabled after Enable()") + } + + ws.Disable() + if ws.IsEnabled() { + t.Error("Interface should not be enabled after Disable()") + } +} + +func TestWebSocketDetach(t *testing.T) { + ws, err := NewWebSocketInterface("test", "wss://socket.quad4.io/ws", true) + if err != nil { + t.Fatalf("Failed to create WebSocket interface: %v", err) + } + + if ws.IsDetached() { + t.Error("Interface should not be detached initially") + } + + ws.Detach() + if !ws.IsDetached() { + t.Error("Interface should be detached after Detach()") + } + + if ws.IsOnline() { + t.Error("Interface should be offline after Detach()") + } +}