From cfbeb50d43b0282e3e8e21ffa0b28ed9f0594fea Mon Sep 17 00:00:00 2001 From: ZopDev Date: Mon, 22 Sep 2025 18:27:10 +0530 Subject: [PATCH 01/12] ADD : Test for pkg/gofr/websocket module --- pkg/gofr/websocket/websocket_test.go | 968 ++++++++++++++++++++++++--- 1 file changed, 861 insertions(+), 107 deletions(-) diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index 8e5a87c3c..a4538a378 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -1,6 +1,10 @@ package websocket import ( + "context" + "encoding/json" + "errors" + "fmt" "net/http" "net/http/httptest" "os" @@ -14,51 +18,78 @@ import ( gomock "go.uber.org/mock/gomock" ) +// Define static errors for better error handling. +var ( + ErrUpgradeFailed = errors.New("upgrade failed") +) + func TestMain(m *testing.M) { os.Setenv("GOFR_TELEMETRY", "false") m.Run() } -func TestConnection_Bind_Success(t *testing.T) { - upgrader := websocket.Upgrader{} - +// CORE FUNCTIONALITY TESTS +// TestConnection_Bind tests the Bind method with various data types. +func TestConnection_Bind(t *testing.T) { tests := []struct { name string inputMessage []byte - expectedData any + targetType any + expected any + expectError bool }{ { - name: "Bind to string", + name: "Bind to string - success", inputMessage: []byte("Hello, WebSocket"), - expectedData: "Hello, WebSocket", + targetType: new(string), + expected: "Hello, WebSocket", + expectError: false, + }, + { + name: "Bind to JSON struct - success", + inputMessage: []byte(`{"name":"test","value":123}`), + targetType: &map[string]any{}, + expected: map[string]any{"name": "test", "value": float64(123)}, + expectError: false, }, { - name: "Bind to JSON struct", - inputMessage: []byte(`{"key":"value"}`), - expectedData: map[string]any{"key": "value"}, + name: "Bind to invalid JSON - error", + inputMessage: []byte(`{"name":"test",invalid}`), + targetType: &map[string]any{}, + expected: nil, + expectError: true, + }, + { + name: "Bind to custom struct - success", + inputMessage: []byte(`{"id":1,"name":"test"}`), + targetType: &testStruct{}, + expected: testStruct{ID: 1, Name: "test"}, + expectError: false, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + upgrader := websocket.Upgrader{} conn, err := upgrader.Upgrade(w, r, nil) - assert.NoError(t, err) + + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() wsConn := &Connection{Conn: conn} + err = wsConn.Bind(tt.targetType) - var data any - switch tt.expectedData.(type) { - case string: - data = new(string) - default: - data = &map[string]any{} + if tt.expectError { + assert.Error(t, err) + } else { + assert.NoError(t, err) + assert.Equal(t, tt.expected, dereferenceValue(tt.targetType)) } - - err = wsConn.Bind(data) - assert.NoError(t, err) - assert.Equal(t, tt.expectedData, dereference(data)) })) defer server.Close() @@ -72,67 +103,509 @@ func TestConnection_Bind_Success(t *testing.T) { err = conn.WriteMessage(websocket.TextMessage, tt.inputMessage) require.NoError(t, err) - }) - // waiting for previous connection to close and test for new testcase. - time.Sleep(500 * time.Millisecond) + time.Sleep(100 * time.Millisecond) + }) } } -func TestNewWSUpgrader_WithOptions(t *testing.T) { - errorHandler := func(_ http.ResponseWriter, _ *http.Request, _ int, _ error) {} +// TestConnection_WriteMessage tests thread-safe writing. +func TestConnection_WriteMessage(t *testing.T) { + upgrader := websocket.Upgrader{} + message := "test message" - checkOrigin := func(_ *http.Request) bool { - return true - } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() - options := []Options{ - WithReadBufferSize(1024), - WithWriteBufferSize(1024), - WithHandshakeTimeout(500 * time.Millisecond), - WithSubprotocols("protocol1", "protocol2"), - WithCompression(), - WithError(errorHandler), - WithCheckOrigin(checkOrigin), - } + wsConn := &Connection{Conn: conn} + + // Test concurrent writes + var wg sync.WaitGroup + for i := 0; i < 10; i++ { + wg.Add(1) + + go func() { + defer wg.Done() + + err := wsConn.WriteMessage(websocket.TextMessage, []byte(message)) + assert.NoError(t, err) + }() + } + + wg.Wait() + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + defer resp.Body.Close() + + // Read messages to prevent connection from closing + go func() { + for { + _, _, err := conn.ReadMessage() + if err != nil { + return + } + } + }() - upgrader := NewWSUpgrader(options...) - actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + time.Sleep(200 * time.Millisecond) +} + +// TestConnection_ReadMessage tests reading messages. +func TestConnection_ReadMessage(t *testing.T) { + upgrader := websocket.Upgrader{} + testMessage := []byte("test read message") + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() + + wsConn := &Connection{Conn: conn} + + // Write a test message + err = wsConn.WriteMessage(websocket.TextMessage, testMessage) + if err != nil { + t.Errorf("Failed to write message: %v", err) + return + } + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + defer resp.Body.Close() + + wsConn := &Connection{Conn: conn} + messageType, message, err := wsConn.ReadMessage() + require.NoError(t, err) + assert.Equal(t, websocket.TextMessage, messageType) + assert.Equal(t, testMessage, message) +} + +// TestConnection_Deadlines tests deadline functionality. +func TestConnection_Deadlines(t *testing.T) { + upgrader := websocket.Upgrader{} + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() + + wsConn := &Connection{Conn: conn} + + // Test read deadline + err = wsConn.SetReadDeadline(time.Now().Add(100 * time.Millisecond)) + assert.NoError(t, err) + + // Test write deadline + err = wsConn.SetWriteDeadline(time.Now().Add(100 * time.Millisecond)) + assert.NoError(t, err) + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + defer resp.Body.Close() + + time.Sleep(200 * time.Millisecond) +} + +// WS UPGRADER TESTS +// TestWSUpgrader_NewWSUpgrader tests upgrader creation with options. +func TestWSUpgrader_NewWSUpgrader(t *testing.T) { + tests := []struct { + name string + options []Options + validate func(t *testing.T, upgrader *WSUpgrader) + }{ + { + name: "Default upgrader", + options: []Options{}, + validate: func(t *testing.T, upgrader *WSUpgrader) { + t.Helper() + assert.NotNil(t, upgrader.Upgrader) + }, + }, + { + name: "With all options", + options: []Options{ + WithReadBufferSize(2048), + WithWriteBufferSize(2048), + WithHandshakeTimeout(2 * time.Second), + WithSubprotocols("test-protocol"), + WithCompression(), + WithError(func(_ http.ResponseWriter, _ *http.Request, _ int, _ error) {}), + WithCheckOrigin(func(_ *http.Request) bool { return true }), + }, + validate: func(t *testing.T, upgrader *WSUpgrader) { + t.Helper() + actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + assert.Equal(t, 2048, actualUpgrader.ReadBufferSize) + assert.Equal(t, 2048, actualUpgrader.WriteBufferSize) + assert.Equal(t, 2*time.Second, actualUpgrader.HandshakeTimeout) + assert.Equal(t, []string{"test-protocol"}, actualUpgrader.Subprotocols) + assert.True(t, actualUpgrader.EnableCompression) + assert.NotNil(t, actualUpgrader.Error) + assert.NotNil(t, actualUpgrader.CheckOrigin) + }, + }, + } - assert.Equal(t, 1024, actualUpgrader.ReadBufferSize) - assert.Equal(t, 1024, actualUpgrader.WriteBufferSize) - assert.Equal(t, 500*time.Millisecond, actualUpgrader.HandshakeTimeout) - assert.Equal(t, []string{"protocol1", "protocol2"}, actualUpgrader.Subprotocols) - assert.True(t, actualUpgrader.EnableCompression) - assert.NotNil(t, actualUpgrader.Error) - assert.NotNil(t, actualUpgrader.CheckOrigin) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + upgrader := NewWSUpgrader(tt.options...) + tt.validate(t, upgrader) + }) + } } -func Test_Upgrade(t *testing.T) { +// TestWSUpgrader_Upgrade tests the upgrade functionality. +func TestWSUpgrader_Upgrade(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() mockUpgrader := NewMockUpgrader(ctrl) - expectedConn := &websocket.Conn{} - mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(expectedConn, nil) - wsUpgrader := WSUpgrader{Upgrader: mockUpgrader} + tests := []struct { + name string + setupMock func() + expectError bool + expectedResult *websocket.Conn + }{ + { + name: "Successful upgrade", + setupMock: func() { + mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(expectedConn, nil) + }, + expectError: false, + expectedResult: expectedConn, + }, + { + name: "Upgrade error", + setupMock: func() { + mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, ErrUpgradeFailed) + }, + expectError: true, + expectedResult: nil, + }, + } - req, err := http.NewRequestWithContext(t.Context(), http.MethodGet, "/", http.NoBody) - require.NoError(t, err) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.setupMock() - w := httptest.NewRecorder() + wsUpgrader := &WSUpgrader{Upgrader: mockUpgrader} + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", http.NoBody) + require.NoError(t, err) - conn, err := wsUpgrader.Upgrade(w, req, nil) - require.NoError(t, err) + w := httptest.NewRecorder() + conn, err := wsUpgrader.Upgrade(w, req, nil) + + if tt.expectError { + require.Error(t, err) + require.Nil(t, conn) + } else { + require.NoError(t, err) + assert.Equal(t, tt.expectedResult, conn) + } + }) + } +} + +// MANAGER TESTS +// TestManager_New tests manager creation. +func TestManager_New(t *testing.T) { + manager := New() + + assert.NotNil(t, manager) + assert.NotNil(t, manager.WebSocketUpgrader) + assert.NotNil(t, manager.WebSocketConnections) + assert.Empty(t, manager.WebSocketConnections) +} + +// TestManager_ConnectionManagement tests connection management. +func TestManager_ConnectionManagement(t *testing.T) { + manager := New() + + // Test adding connections with nil websocket (to avoid close issues) + conn1 := &Connection{Conn: nil} + conn2 := &Connection{Conn: nil} + + manager.AddWebsocketConnection("conn1", conn1) + manager.AddWebsocketConnection("conn2", conn2) + + // Test getting connections + retrievedConn1 := manager.GetWebsocketConnection("conn1") + assert.Equal(t, conn1, retrievedConn1) + + retrievedConn2 := manager.GetWebsocketConnection("conn2") + assert.Equal(t, conn2, retrievedConn2) + + // Test getting non-existent connection + nonExistent := manager.GetWebsocketConnection("non-existent") + assert.Nil(t, nonExistent) + + // Test listing connections + connections := manager.ListConnections() + assert.ElementsMatch(t, []string{"conn1", "conn2"}, connections) + + // Test getting connection by service name + serviceConn := manager.GetConnectionByServiceName("conn1") + assert.Equal(t, conn1, serviceConn) + + // Test closing connection + manager.CloseConnection("conn1") + assert.Nil(t, manager.GetWebsocketConnection("conn1")) + assert.ElementsMatch(t, []string{"conn2"}, manager.ListConnections()) +} + +// TestManager_ConcurrentOperations tests concurrent operations. +func TestManager_ConcurrentOperations(t *testing.T) { + manager := New() + + var wg sync.WaitGroup + + numGoroutines := 100 + + // Concurrent add operations + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + + go func(i int) { + defer wg.Done() + + conn := &Connection{Conn: nil} // Use nil to avoid close issues + manager.AddWebsocketConnection("conn"+string(rune(i)), conn) + }(i) + } + + wg.Wait() + + // Verify all connections were added + connections := manager.ListConnections() + assert.Len(t, connections, numGoroutines) + + // Concurrent read operations + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + + go func(i int) { + defer wg.Done() + + conn := manager.GetWebsocketConnection("conn" + string(rune(i))) + assert.NotNil(t, conn) + }(i) + } + + wg.Wait() + + // Concurrent close operations + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + + go func(i int) { + defer wg.Done() + manager.CloseConnection("conn" + string(rune(i))) + }(i) + } + + wg.Wait() + + // Verify all connections were closed + connections = manager.ListConnections() + assert.Empty(t, connections) +} + +// EDGE CASE TESTS +// TestConnection_Bind_EdgeCases tests edge cases for the Bind method. +func TestConnection_Bind_EdgeCases(t *testing.T) { + tests := []struct { + name string + inputMessage []byte + targetType any + expectError bool + description string + }{ + { + name: "Bind to nil pointer", + inputMessage: []byte("test"), + targetType: (*string)(nil), + expectError: true, + description: "Should handle nil pointer gracefully", + }, + { + name: "Bind to non-pointer", + inputMessage: []byte("test"), + targetType: "not a pointer", + expectError: true, + description: "Should handle non-pointer types", + }, + { + name: "Bind to empty string", + inputMessage: []byte(""), + targetType: new(string), + expectError: false, + description: "Should handle empty string", + }, + { + name: "Bind to large JSON", + inputMessage: createLargeJSON(), + targetType: &map[string]any{}, + expectError: false, + description: "Should handle large JSON payloads", + }, + { + name: "Bind to invalid UTF-8", + inputMessage: []byte{0xff, 0xfe, 0xfd}, + targetType: new(string), + expectError: false, + description: "Should handle invalid UTF-8 sequences", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + upgrader := websocket.Upgrader{} + conn, err := upgrader.Upgrade(w, r, nil) + + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + + defer conn.Close() + + wsConn := &Connection{Conn: conn} + err = wsConn.Bind(tt.targetType) + + if tt.expectError { + assert.Error(t, err, tt.description) + } else { + assert.NoError(t, err, tt.description) + } + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + defer resp.Body.Close() + + err = conn.WriteMessage(websocket.TextMessage, tt.inputMessage) + require.NoError(t, err) + + time.Sleep(100 * time.Millisecond) + }) + } +} + +// TestManager_EdgeCases tests edge cases for Manager. +func TestManager_EdgeCases(t *testing.T) { + manager := New() + + t.Run("Add nil connection", func(t *testing.T) { + manager.AddWebsocketConnection("nil-conn", nil) + conn := manager.GetWebsocketConnection("nil-conn") + assert.Nil(t, conn) + }) + + t.Run("Add connection with nil websocket", func(t *testing.T) { + conn := &Connection{Conn: nil} + manager.AddWebsocketConnection("nil-ws-conn", conn) + retrieved := manager.GetWebsocketConnection("nil-ws-conn") + assert.Equal(t, conn, retrieved) + }) + + t.Run("Close non-existent connection", func(_ *testing.T) { + // Should not panic + manager.CloseConnection("non-existent") + }) + + t.Run("Get connection after close", func(t *testing.T) { + conn := &Connection{Conn: nil} // Use nil to avoid close issues + manager.AddWebsocketConnection("temp-conn", conn) + manager.CloseConnection("temp-conn") + retrieved := manager.GetWebsocketConnection("temp-conn") + assert.Nil(t, retrieved) + }) + + t.Run("List connections when empty", func(t *testing.T) { + emptyManager := New() + connections := emptyManager.ListConnections() + assert.Empty(t, connections) + }) + + t.Run("Add duplicate connection ID", func(t *testing.T) { + conn1 := &Connection{Conn: nil} // Use nil to avoid close issues + conn2 := &Connection{Conn: nil} + + manager.AddWebsocketConnection("duplicate", conn1) + manager.AddWebsocketConnection("duplicate", conn2) + + retrieved := manager.GetWebsocketConnection("duplicate") + assert.Equal(t, conn2, retrieved) // Should be the last one added + }) +} + +// TestConnection_UnimplementedMethods tests unimplemented methods. +func TestConnection_UnimplementedMethods(t *testing.T) { + conn := &Connection{} + + // Test Param method + assert.Empty(t, conn.Param("test")) + + // Test PathParam method + assert.Empty(t, conn.PathParam("test")) + + // Test HostName method + assert.Empty(t, conn.HostName()) + + // Test Context method + ctx := conn.Context() + assert.NotNil(t, ctx) - assert.Equal(t, expectedConn, conn) + // Test Params method + params := conn.Params("test") + assert.Nil(t, params) } -func Test_UnimplementedMethods(t *testing.T) { +// TestConnection_ErrorHandling tests error handling. +func TestConnection_ErrorHandling(t *testing.T) { + // Test with nil connection conn := &Connection{} + // These should not panic assert.Empty(t, conn.Param("test")) assert.Empty(t, conn.PathParam("test")) assert.Empty(t, conn.HostName()) @@ -140,92 +613,373 @@ func Test_UnimplementedMethods(t *testing.T) { assert.Nil(t, conn.Params("test")) } -func dereference(v any) any { - switch v := v.(type) { - case *string: - return *v - case *map[string]any: - return *v - default: - return v - } +// OPTIONS TESTS +// TestOptions tests all option functions. +func TestOptions(t *testing.T) { + upgrader := &websocket.Upgrader{} + + // Test WithReadBufferSize + WithReadBufferSize(1024)(upgrader) + assert.Equal(t, 1024, upgrader.ReadBufferSize) + + // Test WithWriteBufferSize + WithWriteBufferSize(2048)(upgrader) + assert.Equal(t, 2048, upgrader.WriteBufferSize) + + // Test WithHandshakeTimeout + timeout := 5 * time.Second + WithHandshakeTimeout(timeout)(upgrader) + assert.Equal(t, timeout, upgrader.HandshakeTimeout) + + // Test WithSubprotocols + protocols := []string{"protocol1", "protocol2"} + WithSubprotocols(protocols...)(upgrader) + assert.Equal(t, protocols, upgrader.Subprotocols) + + // Test WithCompression + WithCompression()(upgrader) + assert.True(t, upgrader.EnableCompression) + + // Test WithError + errorHandler := func(_ http.ResponseWriter, _ *http.Request, _ int, _ error) {} + WithError(errorHandler)(upgrader) + assert.NotNil(t, upgrader.Error) + + // Test WithCheckOrigin + checkOrigin := func(_ *http.Request) bool { return true } + WithCheckOrigin(checkOrigin)(upgrader) + assert.NotNil(t, upgrader.CheckOrigin) } -func TestConcurrentWriteMessageCalls(t *testing.T) { +// CONSTANTS TESTS +// TestConstants tests package constants. +func TestConstants(t *testing.T) { + assert.Equal(t, WSConnectionKey, WSKey("ws-connection-key")) + assert.Equal(t, 1, TextMessage) + assert.Equal(t, "couldn't establish connection to web socket", ErrorConnection.Error()) +} + +// TestConnection_Bind_ErrorPaths tests error paths in Bind method for 100% coverage. +func TestConnection_Bind_ErrorPaths(t *testing.T) { upgrader := websocket.Upgrader{} - const message = "this is a test message" + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() + + wsConn := &Connection{Conn: conn} + + // Test error path in Bind when ReadMessage fails + // We'll close the connection to force a read error + conn.Close() - loop := 10 - workers := 10 + var data string + err = wsConn.Bind(&data) + + if err == nil { + t.Error("Expected error for closed connection") + } + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + defer resp.Body.Close() + + // Write a message to trigger the server + err = conn.WriteMessage(websocket.TextMessage, []byte("test")) + require.NoError(t, err) + + time.Sleep(100 * time.Millisecond) +} + +// TestConnection_Bind_JSONError tests JSON unmarshaling error path. +func TestConnection_Bind_JSONError(t *testing.T) { + upgrader := websocket.Upgrader{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) - assert.NoError(t, err) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() + + wsConn := &Connection{Conn: conn} + + // Test with invalid JSON that will cause unmarshaling error + var data map[string]any + err = wsConn.Bind(&data) + // This should fail with invalid JSON + if err == nil { + t.Error("Expected error for invalid JSON") + } + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + defer resp.Body.Close() + + // Send invalid JSON to trigger error path + err = conn.WriteMessage(websocket.TextMessage, []byte(`{"invalid": json}`)) + require.NoError(t, err) + + time.Sleep(100 * time.Millisecond) +} + +// TestManager_CloseConnection_WithNilConn tests CloseConnection with nil connection. +func TestManager_CloseConnection_WithNilConn(t *testing.T) { + manager := New() + + // Add connection with nil websocket + conn := &Connection{Conn: nil} + manager.AddWebsocketConnection("test-conn", conn) + + // Close connection - should not panic + manager.CloseConnection("test-conn") + + // Verify connection is removed + retrieved := manager.GetWebsocketConnection("test-conn") + assert.Nil(t, retrieved) +} + +// TestManager_CloseConnection_NonExistent tests closing non-existent connection. +func TestManager_CloseConnection_NonExistent(t *testing.T) { + manager := New() + + // Close non-existent connection - should not panic + manager.CloseConnection("non-existent") + + // Verify no connections exist + connections := manager.ListConnections() + assert.Empty(t, connections) +} + +// TestManager_CloseConnection_WithRealConn tests CloseConnection with a real websocket connection. +func TestManager_CloseConnection_WithRealConn(t *testing.T) { + manager := New() + + // Create a real websocket connection using httptest + upgrader := websocket.Upgrader{} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } defer conn.Close() - wc := &Connection{Conn: conn} + wsConn := &Connection{Conn: conn} + manager.AddWebsocketConnection("test-conn", wsConn) + + // Close connection - should call Close() on the websocket + manager.CloseConnection("test-conn") + + // Verify connection is removed + retrieved := manager.GetWebsocketConnection("test-conn") + assert.Nil(t, retrieved) + })) + + defer server.Close() + + // Connect to the server to trigger the handler + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + defer resp.Body.Close() + + // Send a message to trigger the server handler + err = conn.WriteMessage(websocket.TextMessage, []byte("test")) + require.NoError(t, err) + + time.Sleep(100 * time.Millisecond) +} + +// TestWSUpgrader_Upgrade_Error tests upgrade error path. +func TestWSUpgrader_Upgrade_Error(t *testing.T) { + // Create a mock upgrader that returns an error + mockUpgrader := &mockUpgraderWithError{} + wsUpgrader := &WSUpgrader{Upgrader: mockUpgrader} - wg := sync.WaitGroup{} + req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", http.NoBody) + require.NoError(t, err) - for range loop { - for range workers { - wg.Add(1) + w := httptest.NewRecorder() - go func() { - defer wg.Done() + conn, err := wsUpgrader.Upgrade(w, req, nil) + require.Error(t, err) + require.Nil(t, conn) +} - if err := wc.WriteMessage(websocket.TextMessage, []byte(message)); err != nil { - t.Errorf("concurrently wc.WriteMessage() returned %v", err) - } - }() +// PERFORMANCE TESTS +// TestConnection_Performance tests performance characteristics. +func TestConnection_Performance(t *testing.T) { + if testing.Short() { + t.Skip("Skipping performance test in short mode") + } + + upgrader := websocket.Upgrader{} + messageCount := 10000 + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() + + wsConn := &Connection{Conn: conn} + + start := time.Now() + + // Write many messages + for i := 0; i < messageCount; i++ { + message := []byte("performance test message") + err := wsConn.WriteMessage(websocket.TextMessage, message) + + if err != nil { + t.Errorf("Failed to write message: %v", err) + return } } - wg.Wait() + duration := time.Since(start) + t.Logf("Wrote %d messages in %v (%.2f msg/sec)", + messageCount, duration, float64(messageCount)/duration.Seconds()) })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) - server.Close() + defer conn.Close() + defer resp.Body.Close() + + // Read messages + start := time.Now() + + for i := 0; i < messageCount; i++ { + _, _, err := conn.ReadMessage() + require.NoError(t, err) + } + + duration := time.Since(start) + t.Logf("Read %d messages in %v (%.2f msg/sec)", + messageCount, duration, float64(messageCount)/duration.Seconds()) } -func TestManager_ListConnections(t *testing.T) { +// TestManager_Performance tests manager performance. +func TestManager_Performance(t *testing.T) { + if testing.Short() { + t.Skip("Skipping performance test in short mode") + } + manager := New() + connectionCount := 10000 + + // Test adding many connections + start := time.Now() - // Add mock connections - manager.AddWebsocketConnection("conn1", &Connection{Conn: &websocket.Conn{}}) - manager.AddWebsocketConnection("conn2", &Connection{Conn: &websocket.Conn{}}) - manager.AddWebsocketConnection("conn3", &Connection{Conn: &websocket.Conn{}}) + for i := 0; i < connectionCount; i++ { + conn := &Connection{Conn: nil} // Use nil to avoid close issues + manager.AddWebsocketConnection("conn"+string(rune(i)), conn) + } - // Get the list of connections + addDuration := time.Since(start) + t.Logf("Added %d connections in %v (%.2f conn/sec)", + connectionCount, addDuration, float64(connectionCount)/addDuration.Seconds()) + + // Test listing connections + start = time.Now() connections := manager.ListConnections() + listDuration := time.Since(start) - assert.ElementsMatch(t, []string{"conn1", "conn2", "conn3"}, connections) -} + assert.Len(t, connections, connectionCount) + t.Logf("Listed %d connections in %v (%.2f conn/sec)", + len(connections), listDuration, float64(len(connections))/listDuration.Seconds()) -func TestManager_GetConnectionByServiceName(t *testing.T) { - manager := New() + // Test getting connections + start = time.Now() + + for i := 0; i < connectionCount; i++ { + conn := manager.GetWebsocketConnection("conn" + string(rune(i))) + assert.NotNil(t, conn) + } + + getDuration := time.Since(start) + t.Logf("Retrieved %d connections in %v (%.2f conn/sec)", + connectionCount, getDuration, float64(connectionCount)/getDuration.Seconds()) + + // Test closing connections + start = time.Now() - mockConn := &Connection{Conn: &websocket.Conn{}} - manager.AddWebsocketConnection("testService", mockConn) + for i := 0; i < connectionCount; i++ { + manager.CloseConnection("conn" + string(rune(i))) + } - retrievedConn := manager.GetConnectionByServiceName("testService") + closeDuration := time.Since(start) + t.Logf("Closed %d connections in %v (%.2f conn/sec)", + connectionCount, closeDuration, float64(connectionCount)/closeDuration.Seconds()) - assert.Equal(t, mockConn, retrievedConn) + // Verify all connections are closed + connections = manager.ListConnections() + assert.Empty(t, connections) } -func TestManager_CloseConnection(t *testing.T) { - manager := New() +// testStruct is a helper type for testing. +type testStruct struct { + ID int `json:"id"` + Name string `json:"name"` +} + +// dereferenceValue is a helper function to dereference pointers in tests. +func dereferenceValue(v any) any { + switch val := v.(type) { + case *string: + return *val + case *map[string]any: + return *val + case *testStruct: + return *val + default: + return val + } +} - mockConn := &Connection{ - Conn: &websocket.Conn{}, +// createLargeJSON creates a large JSON payload for testing. +func createLargeJSON() []byte { + data := make(map[string]any) + for i := 0; i < 1000; i++ { + data[fmt.Sprintf("key%d", i)] = fmt.Sprintf("value%d", i) } - mockConn.Conn = nil - manager.AddWebsocketConnection("testConn", mockConn) + jsonData, _ := json.Marshal(data) - assert.NotNil(t, manager.GetWebsocketConnection("testConn")) + return jsonData +} - manager.CloseConnection("testConn") +// mockUpgraderWithError is a mock upgrader that always returns an error. +type mockUpgraderWithError struct{} - assert.Nil(t, manager.GetWebsocketConnection("testConn")) +func (*mockUpgraderWithError) Upgrade(_ http.ResponseWriter, _ *http.Request, _ http.Header) (*websocket.Conn, error) { + return nil, ErrUpgradeFailed } From c26cb98bb0013a6bf506776db6f0a259e6e7bca3 Mon Sep 17 00:00:00 2001 From: ZopDev Date: Wed, 24 Sep 2025 12:47:35 +0530 Subject: [PATCH 02/12] Refactor websocket test according to need --- pkg/gofr/websocket/mock_interfaces.go | 102 +++- pkg/gofr/websocket/websocket_test.go | 711 ++++++++++++++++++++------ 2 files changed, 664 insertions(+), 149 deletions(-) diff --git a/pkg/gofr/websocket/mock_interfaces.go b/pkg/gofr/websocket/mock_interfaces.go index 785da344a..7261b6495 100644 --- a/pkg/gofr/websocket/mock_interfaces.go +++ b/pkg/gofr/websocket/mock_interfaces.go @@ -1,9 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: interfaces.go +// Source: pkg/gofr/websocket/interfaces.go // // Generated by this command: // -// mockgen -source=interfaces.go -package=websocket -destination=mock_interfaces.go +// mockgen -source=pkg/gofr/websocket/interfaces.go -package=websocket -destination=pkg/gofr/websocket/mock_interfaces.go // // Package websocket is a generated GoMock package. @@ -12,6 +12,7 @@ package websocket import ( http "net/http" reflect "reflect" + time "time" websocket "github.com/gorilla/websocket" gomock "go.uber.org/mock/gomock" @@ -21,6 +22,7 @@ import ( type MockUpgrader struct { ctrl *gomock.Controller recorder *MockUpgraderMockRecorder + isgomock struct{} } // MockUpgraderMockRecorder is the mock recorder for MockUpgrader. @@ -54,3 +56,99 @@ func (mr *MockUpgraderMockRecorder) Upgrade(w, r, responseHeader any) *gomock.Ca mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upgrade", reflect.TypeOf((*MockUpgrader)(nil).Upgrade), w, r, responseHeader) } + +// MockWebSocketConn is a mock of WebSocketConn interface. +type MockWebSocketConn struct { + ctrl *gomock.Controller + recorder *MockWebSocketConnMockRecorder + isgomock struct{} +} + +// MockWebSocketConnMockRecorder is the mock recorder for MockWebSocketConn. +type MockWebSocketConnMockRecorder struct { + mock *MockWebSocketConn +} + +// NewMockWebSocketConn creates a new mock instance. +func NewMockWebSocketConn(ctrl *gomock.Controller) *MockWebSocketConn { + mock := &MockWebSocketConn{ctrl: ctrl} + mock.recorder = &MockWebSocketConnMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockWebSocketConn) EXPECT() *MockWebSocketConnMockRecorder { + return m.recorder +} + +// Close mocks base method. +func (m *MockWebSocketConn) Close() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Close") + ret0, _ := ret[0].(error) + return ret0 +} + +// Close indicates an expected call of Close. +func (mr *MockWebSocketConnMockRecorder) Close() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockWebSocketConn)(nil).Close)) +} + +// ReadMessage mocks base method. +func (m *MockWebSocketConn) ReadMessage() (int, []byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ReadMessage") + ret0, _ := ret[0].(int) + ret1, _ := ret[1].([]byte) + ret2, _ := ret[2].(error) + return ret0, ret1, ret2 +} + +// ReadMessage indicates an expected call of ReadMessage. +func (mr *MockWebSocketConnMockRecorder) ReadMessage() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadMessage", reflect.TypeOf((*MockWebSocketConn)(nil).ReadMessage)) +} + +// SetReadDeadline mocks base method. +func (m *MockWebSocketConn) SetReadDeadline(t time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetReadDeadline", t) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetReadDeadline indicates an expected call of SetReadDeadline. +func (mr *MockWebSocketConnMockRecorder) SetReadDeadline(t any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockWebSocketConn)(nil).SetReadDeadline), t) +} + +// SetWriteDeadline mocks base method. +func (m *MockWebSocketConn) SetWriteDeadline(t time.Time) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetWriteDeadline", t) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetWriteDeadline indicates an expected call of SetWriteDeadline. +func (mr *MockWebSocketConnMockRecorder) SetWriteDeadline(t any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockWebSocketConn)(nil).SetWriteDeadline), t) +} + +// WriteMessage mocks base method. +func (m *MockWebSocketConn) WriteMessage(messageType int, data []byte) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "WriteMessage", messageType, data) + ret0, _ := ret[0].(error) + return ret0 +} + +// WriteMessage indicates an expected call of WriteMessage. +func (mr *MockWebSocketConnMockRecorder) WriteMessage(messageType, data any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteMessage", reflect.TypeOf((*MockWebSocketConn)(nil).WriteMessage), messageType, data) +} diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index a4538a378..318975aa1 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -15,12 +15,14 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - gomock "go.uber.org/mock/gomock" ) // Define static errors for better error handling. var ( - ErrUpgradeFailed = errors.New("upgrade failed") + ErrUpgradeFailed = errors.New("upgrade failed") + ErrNetworkTimeout = errors.New("network timeout") + ErrUnexpectedResponse = errors.New("unexpected server response") + ErrConnectionClosed = errors.New("connection closed") ) func TestMain(m *testing.M) { @@ -28,6 +30,61 @@ func TestMain(m *testing.M) { m.Run() } +// Helper functions to reduce test duplication + +// setupWebSocketServer creates a test WebSocket server with the given handler. +func setupWebSocketServer(t *testing.T, handler func(*websocket.Conn)) *httptest.Server { + t.Helper() + + upgrader := websocket.Upgrader{} + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() + + handler(conn) + })) + + return server +} + +// connectToWebSocket connects to a WebSocket server and returns the connection. +func connectToWebSocket(t *testing.T, serverURL string) (*websocket.Conn, *http.Response) { + t.Helper() + + url := "ws" + serverURL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + return conn, resp +} + +// createTestConnection creates a WebSocket connection for testing. +func createTestConnection(t *testing.T, conn *websocket.Conn) *Connection { + t.Helper() + + return &Connection{Conn: conn} +} + +// sendMessageToWebSocket sends a message to a WebSocket connection. +func sendMessageToWebSocket(t *testing.T, conn *websocket.Conn, message []byte) { + t.Helper() + + err := conn.WriteMessage(websocket.TextMessage, message) + require.NoError(t, err) +} + +// waitForWebSocketOperation waits for a WebSocket operation to complete. +func waitForWebSocketOperation(t *testing.T, duration time.Duration) { + t.Helper() + + time.Sleep(duration) +} + // CORE FUNCTIONALITY TESTS // TestConnection_Bind tests the Bind method with various data types. func TestConnection_Bind(t *testing.T) { @@ -70,41 +127,25 @@ func TestConnection_Bind(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - upgrader := websocket.Upgrader{} - conn, err := upgrader.Upgrade(w, r, nil) - - if err != nil { - t.Errorf("Failed to upgrade connection: %v", err) - return - } - - defer conn.Close() - - wsConn := &Connection{Conn: conn} - err = wsConn.Bind(tt.targetType) + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + wsConn := createTestConnection(t, conn) + err := wsConn.Bind(tt.targetType) if tt.expectError { - assert.Error(t, err) + require.Error(t, err) } else { - assert.NoError(t, err) + require.NoError(t, err) assert.Equal(t, tt.expected, dereferenceValue(tt.targetType)) } - })) + }) defer server.Close() - url := "ws" + server.URL[len("http"):] + "/ws" - dialer := websocket.DefaultDialer - conn, resp, err := dialer.Dial(url, nil) - require.NoError(t, err) - + conn, resp := connectToWebSocket(t, server.URL) defer conn.Close() defer resp.Body.Close() - err = conn.WriteMessage(websocket.TextMessage, tt.inputMessage) - require.NoError(t, err) - - time.Sleep(100 * time.Millisecond) + sendMessageToWebSocket(t, conn, tt.inputMessage) + waitForWebSocketOperation(t, 100*time.Millisecond) }) } } @@ -164,43 +205,92 @@ func TestConnection_WriteMessage(t *testing.T) { // TestConnection_ReadMessage tests reading messages. func TestConnection_ReadMessage(t *testing.T) { - upgrader := websocket.Upgrader{} testMessage := []byte("test read message") - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - t.Errorf("Failed to upgrade connection: %v", err) - return - } - defer conn.Close() - - wsConn := &Connection{Conn: conn} + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + wsConn := createTestConnection(t, conn) // Write a test message - err = wsConn.WriteMessage(websocket.TextMessage, testMessage) - if err != nil { - t.Errorf("Failed to write message: %v", err) - return - } - })) + err := wsConn.WriteMessage(websocket.TextMessage, testMessage) + require.NoError(t, err) + }) defer server.Close() - url := "ws" + server.URL[len("http"):] + "/ws" - dialer := websocket.DefaultDialer - conn, resp, err := dialer.Dial(url, nil) - require.NoError(t, err) - + conn, resp := connectToWebSocket(t, server.URL) defer conn.Close() defer resp.Body.Close() - wsConn := &Connection{Conn: conn} + wsConn := createTestConnection(t, conn) messageType, message, err := wsConn.ReadMessage() require.NoError(t, err) assert.Equal(t, websocket.TextMessage, messageType) assert.Equal(t, testMessage, message) } +// TestConnection_ReadMessage_ErrorHandling tests reading messages with error scenarios. +func TestConnection_ReadMessage_ErrorHandling(t *testing.T) { + tests := []struct { + name string + setupServer func(*testing.T, *websocket.Conn) + expectError bool + description string + }{ + { + name: "Connection closed before read", + setupServer: func(t *testing.T, conn *websocket.Conn) { + t.Helper() + wsConn := createTestConnection(t, conn) + // Close connection to force read error + conn.Close() + + messageType, message, err := wsConn.ReadMessage() + require.Error(t, err, "Expected error for closed connection") + assert.Equal(t, -1, messageType) // Closed connection returns -1 + assert.Nil(t, message) + }, + expectError: true, + description: "Should handle connection closed before read", + }, + { + name: "Network timeout during read", + setupServer: func(t *testing.T, conn *websocket.Conn) { + t.Helper() + wsConn := createTestConnection(t, conn) + // Set a very short read deadline to simulate timeout + err := wsConn.SetReadDeadline(time.Now().Add(1 * time.Millisecond)) + require.NoError(t, err) + + // Wait for deadline to pass + time.Sleep(10 * time.Millisecond) + + messageType, message, err := wsConn.ReadMessage() + assert.Error(t, err, "Expected timeout error") + assert.Equal(t, -1, messageType) // Timeout returns -1 + assert.Nil(t, message) + }, + expectError: true, + description: "Should handle network timeout during read", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + tt.setupServer(t, conn) + }) + defer server.Close() + + conn, resp := connectToWebSocket(t, server.URL) + defer conn.Close() + defer resp.Body.Close() + + // Send a message to trigger the server handler + sendMessageToWebSocket(t, conn, []byte("test")) + waitForWebSocketOperation(t, 100*time.Millisecond) + }) + } +} + // TestConnection_Deadlines tests deadline functionality. func TestConnection_Deadlines(t *testing.T) { upgrader := websocket.Upgrader{} @@ -237,7 +327,7 @@ func TestConnection_Deadlines(t *testing.T) { } // WS UPGRADER TESTS -// TestWSUpgrader_NewWSUpgrader tests upgrader creation with options. +// TestWSUpgrader_NewWSUpgrader tests upgrader creation with basic options. func TestWSUpgrader_NewWSUpgrader(t *testing.T) { tests := []struct { name string @@ -285,55 +375,347 @@ func TestWSUpgrader_NewWSUpgrader(t *testing.T) { } } -// TestWSUpgrader_Upgrade tests the upgrade functionality. -func TestWSUpgrader_Upgrade(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() +// TestWSUpgrader_BufferOptions tests buffer size options. +func TestWSUpgrader_BufferOptions(t *testing.T) { + tests := []struct { + name string + options []Options + validate func(t *testing.T, upgrader *WSUpgrader) + }{ + { + name: "With multiple subprotocols", + options: []Options{ + WithSubprotocols("protocol1", "protocol2", "protocol3"), + }, + validate: func(t *testing.T, upgrader *WSUpgrader) { + t.Helper() + actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + assert.Equal(t, []string{"protocol1", "protocol2", "protocol3"}, actualUpgrader.Subprotocols) + }, + }, + { + name: "With zero buffer sizes", + options: []Options{ + WithReadBufferSize(0), + WithWriteBufferSize(0), + }, + validate: func(t *testing.T, upgrader *WSUpgrader) { + t.Helper() + actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + assert.Equal(t, 0, actualUpgrader.ReadBufferSize) + assert.Equal(t, 0, actualUpgrader.WriteBufferSize) + }, + }, + { + name: "With negative buffer sizes", + options: []Options{ + WithReadBufferSize(-1), + WithWriteBufferSize(-1), + }, + validate: func(t *testing.T, upgrader *WSUpgrader) { + t.Helper() + actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + assert.Equal(t, -1, actualUpgrader.ReadBufferSize) + assert.Equal(t, -1, actualUpgrader.WriteBufferSize) + }, + }, + { + name: "With very large buffer sizes", + options: []Options{ + WithReadBufferSize(1024 * 1024), // 1MB + WithWriteBufferSize(1024 * 1024), // 1MB + }, + validate: func(t *testing.T, upgrader *WSUpgrader) { + t.Helper() + actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + assert.Equal(t, 1024*1024, actualUpgrader.ReadBufferSize) + assert.Equal(t, 1024*1024, actualUpgrader.WriteBufferSize) + }, + }, + } - mockUpgrader := NewMockUpgrader(ctrl) - expectedConn := &websocket.Conn{} + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + upgrader := NewWSUpgrader(tt.options...) + tt.validate(t, upgrader) + }) + } +} +// TestWSUpgrader_TimeoutOptions tests timeout options. +func TestWSUpgrader_TimeoutOptions(t *testing.T) { tests := []struct { - name string - setupMock func() - expectError bool - expectedResult *websocket.Conn + name string + options []Options + validate func(t *testing.T, upgrader *WSUpgrader) }{ { - name: "Successful upgrade", - setupMock: func() { - mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(expectedConn, nil) + name: "With zero handshake timeout", + options: []Options{ + WithHandshakeTimeout(0), + }, + validate: func(t *testing.T, upgrader *WSUpgrader) { + t.Helper() + actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + assert.Equal(t, time.Duration(0), actualUpgrader.HandshakeTimeout) }, - expectError: false, - expectedResult: expectedConn, }, { - name: "Upgrade error", - setupMock: func() { - mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, ErrUpgradeFailed) + name: "With very long handshake timeout", + options: []Options{ + WithHandshakeTimeout(24 * time.Hour), + }, + validate: func(t *testing.T, upgrader *WSUpgrader) { + t.Helper() + actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + assert.Equal(t, 24*time.Hour, actualUpgrader.HandshakeTimeout) }, - expectError: true, - expectedResult: nil, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tt.setupMock() + upgrader := NewWSUpgrader(tt.options...) + tt.validate(t, upgrader) + }) + } +} - wsUpgrader := &WSUpgrader{Upgrader: mockUpgrader} - req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", http.NoBody) - require.NoError(t, err) +// TestWSUpgrader_ConflictingOptions tests conflicting or invalid option combinations. +func TestWSUpgrader_ConflictingOptions(t *testing.T) { + tests := []struct { + name string + options []Options + expectError bool + description string + }{ + { + name: "Multiple buffer size options - last wins", + options: []Options{ + WithReadBufferSize(1024), + WithReadBufferSize(2048), + WithWriteBufferSize(512), + WithWriteBufferSize(4096), + }, + expectError: false, + description: "Last option should override previous ones", + }, + { + name: "Multiple handshake timeout options - last wins", + options: []Options{ + WithHandshakeTimeout(1 * time.Second), + WithHandshakeTimeout(2 * time.Second), + WithHandshakeTimeout(3 * time.Second), + }, + expectError: false, + description: "Last timeout option should override previous ones", + }, + { + name: "Multiple subprotocol options - last wins", + options: []Options{ + WithSubprotocols("protocol1", "protocol2"), + WithSubprotocols("protocol3", "protocol4", "protocol5"), + }, + expectError: false, + description: "Last subprotocol option should override previous ones", + }, + { + name: "Multiple error handlers - last wins", + options: []Options{ + WithError(func(_ http.ResponseWriter, _ *http.Request, _ int, _ error) {}), + WithError(func(_ http.ResponseWriter, _ *http.Request, _ int, _ error) {}), + }, + expectError: false, + description: "Last error handler should override previous one", + }, + { + name: "Multiple check origin handlers - last wins", + options: []Options{ + WithCheckOrigin(func(_ *http.Request) bool { return false }), + WithCheckOrigin(func(_ *http.Request) bool { return true }), + }, + expectError: false, + description: "Last check origin handler should override previous one", + }, + { + name: "Multiple compression options - last wins", + options: []Options{ + WithCompression(), + WithCompression(), + }, + expectError: false, + description: "Multiple compression options should not conflict", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + upgrader := NewWSUpgrader(tt.options...) + assert.NotNil(t, upgrader, tt.description) + + // Verify the last option takes precedence + actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) + assert.NotNil(t, actualUpgrader, "Upgrader should be created successfully") + }) + } +} - w := httptest.NewRecorder() - conn, err := wsUpgrader.Upgrade(w, req, nil) +// TestWSUpgrader_RealConnectionConflicts tests conflicting options with real WebSocket connections. +func TestWSUpgrader_RealConnectionConflicts(t *testing.T) { + tests := []struct { + name string + options []Options + expectError bool + description string + }{ + { + name: "CheckOrigin rejecting all connections", + options: []Options{ + WithCheckOrigin(func(_ *http.Request) bool { return false }), + }, + expectError: true, + description: "Should reject connection when CheckOrigin returns false", + }, + { + name: "Very short handshake timeout", + options: []Options{ + WithHandshakeTimeout(1 * time.Nanosecond), + }, + expectError: true, + description: "Should timeout with very short handshake timeout", + }, + { + name: "CheckOrigin accepting all connections", + options: []Options{ + WithCheckOrigin(func(_ *http.Request) bool { return true }), + }, + expectError: false, + description: "Should accept connection when CheckOrigin returns true", + }, + { + name: "Normal timeout with compression", + options: []Options{ + WithHandshakeTimeout(5 * time.Second), + WithCompression(), + }, + expectError: false, + description: "Should work with normal timeout and compression", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + upgrader := NewWSUpgrader(tt.options...) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if tt.expectError { + assert.Error(t, err, tt.description) + assert.Nil(t, conn, "Connection should be nil on error") + } else { + require.NoError(t, err, tt.description) + if conn != nil { + conn.Close() + } + } + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + if tt.expectError { - require.Error(t, err) - require.Nil(t, conn) + require.Error(t, err, tt.description) + if resp != nil { + resp.Body.Close() + } } else { - require.NoError(t, err) - assert.Equal(t, tt.expectedResult, conn) + require.NoError(t, err, tt.description) + + if conn != nil { + conn.Close() + } + + if resp != nil { + resp.Body.Close() + } + } + }) + } +} + +// TestWSUpgrader_Upgrade tests the upgrade functionality with real connections. +func TestWSUpgrader_Upgrade(t *testing.T) { + tests := []struct { + name string + options []Options + expectError bool + description string + }{ + { + name: "Successful upgrade with default options", + options: []Options{}, + expectError: false, + description: "Should successfully upgrade HTTP to WebSocket", + }, + { + name: "Successful upgrade with custom options", + options: []Options{ + WithReadBufferSize(1024), + WithWriteBufferSize(1024), + WithHandshakeTimeout(5 * time.Second), + }, + expectError: false, + description: "Should successfully upgrade with custom options", + }, + { + name: "Upgrade with CheckOrigin rejection", + options: []Options{ + WithCheckOrigin(func(_ *http.Request) bool { return false }), + }, + expectError: true, + description: "Should fail when CheckOrigin returns false", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + upgrader := NewWSUpgrader(tt.options...) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if tt.expectError { + assert.Error(t, err, tt.description) + assert.Nil(t, conn, "Connection should be nil on error") + } else { + require.NoError(t, err, tt.description) + if conn != nil { + conn.Close() + } + } + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + + if tt.expectError { + require.Error(t, err, tt.description) + if resp != nil { + resp.Body.Close() + } + } else { + require.NoError(t, err, tt.description) + + if conn != nil { + conn.Close() + } + + if resp != nil { + resp.Body.Close() + } } }) } @@ -509,7 +891,7 @@ func TestConnection_Bind_EdgeCases(t *testing.T) { if tt.expectError { assert.Error(t, err, tt.description) } else { - assert.NoError(t, err, tt.description) + require.NoError(t, err, tt.description) } })) defer server.Close() @@ -579,40 +961,28 @@ func TestManager_EdgeCases(t *testing.T) { } // TestConnection_UnimplementedMethods tests unimplemented methods. +// These methods are intentionally unimplemented for WebSocket connections. func TestConnection_UnimplementedMethods(t *testing.T) { conn := &Connection{} - // Test Param method + // Test Param method - should return empty string assert.Empty(t, conn.Param("test")) - // Test PathParam method + // Test PathParam method - should return empty string assert.Empty(t, conn.PathParam("test")) - // Test HostName method + // Test HostName method - should return empty string assert.Empty(t, conn.HostName()) - // Test Context method + // Test Context method - should return a valid context ctx := conn.Context() assert.NotNil(t, ctx) - // Test Params method + // Test Params method - should return nil params := conn.Params("test") assert.Nil(t, params) } -// TestConnection_ErrorHandling tests error handling. -func TestConnection_ErrorHandling(t *testing.T) { - // Test with nil connection - conn := &Connection{} - - // These should not panic - assert.Empty(t, conn.Param("test")) - assert.Empty(t, conn.PathParam("test")) - assert.Empty(t, conn.HostName()) - assert.NotNil(t, conn.Context()) - assert.Nil(t, conn.Params("test")) -} - // OPTIONS TESTS // TestOptions tests all option functions. func TestOptions(t *testing.T) { @@ -659,46 +1029,100 @@ func TestConstants(t *testing.T) { assert.Equal(t, "couldn't establish connection to web socket", ErrorConnection.Error()) } -// TestConnection_Bind_ErrorPaths tests error paths in Bind method for 100% coverage. +// TestConnection_Bind_ErrorPaths tests comprehensive error paths in Bind method. func TestConnection_Bind_ErrorPaths(t *testing.T) { - upgrader := websocket.Upgrader{} - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - t.Errorf("Failed to upgrade connection: %v", err) - return - } - defer conn.Close() - - wsConn := &Connection{Conn: conn} - - // Test error path in Bind when ReadMessage fails - // We'll close the connection to force a read error - conn.Close() - - var data string - err = wsConn.Bind(&data) - - if err == nil { - t.Error("Expected error for closed connection") - } - })) - defer server.Close() - - url := "ws" + server.URL[len("http"):] + "/ws" - dialer := websocket.DefaultDialer - conn, resp, err := dialer.Dial(url, nil) - require.NoError(t, err) + tests := []struct { + name string + setupServer func(*testing.T, *websocket.Conn) + expectError bool + description string + }{ + { + name: "Connection closed before read", + setupServer: func(t *testing.T, conn *websocket.Conn) { + t.Helper() + wsConn := createTestConnection(t, conn) + // Close connection to force read error + conn.Close() + + var data string + err := wsConn.Bind(&data) + assert.Error(t, err, "Expected error for closed connection") + }, + expectError: true, + description: "Should handle connection closed before read", + }, + { + name: "Network timeout during read", + setupServer: func(t *testing.T, conn *websocket.Conn) { + t.Helper() + wsConn := createTestConnection(t, conn) + // Set a very short read deadline to simulate timeout + err := wsConn.SetReadDeadline(time.Now().Add(1 * time.Millisecond)) + require.NoError(t, err) + + // Wait for deadline to pass + time.Sleep(10 * time.Millisecond) + + var data string + err = wsConn.Bind(&data) + assert.Error(t, err, "Expected timeout error") + }, + expectError: true, + description: "Should handle network timeout during read", + }, + { + name: "Unexpected server response - binary message", + setupServer: func(t *testing.T, conn *websocket.Conn) { + t.Helper() + wsConn := createTestConnection(t, conn) + + // Send binary message instead of text + err := conn.WriteMessage(websocket.BinaryMessage, []byte("binary data")) + require.NoError(t, err) + + var data string + err = wsConn.Bind(&data) + // This should still work as we're reading the message + assert.NoError(t, err, "Should handle binary messages") + }, + expectError: false, + description: "Should handle binary messages gracefully", + }, + { + name: "Connection interrupted during read", + setupServer: func(t *testing.T, conn *websocket.Conn) { + t.Helper() + wsConn := createTestConnection(t, conn) + + // Close connection immediately to simulate interruption + conn.Close() + + var data string + err := wsConn.Bind(&data) + assert.Error(t, err, "Expected error for interrupted connection") + }, + expectError: true, + description: "Should handle connection interruption during read", + }, + } - defer conn.Close() - defer resp.Body.Close() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + tt.setupServer(t, conn) + }) + defer server.Close() - // Write a message to trigger the server - err = conn.WriteMessage(websocket.TextMessage, []byte("test")) - require.NoError(t, err) + conn, resp := connectToWebSocket(t, server.URL) + defer conn.Close() + defer resp.Body.Close() - time.Sleep(100 * time.Millisecond) + // Send a message to trigger the server handler + sendMessageToWebSocket(t, conn, []byte("test")) + waitForWebSocketOperation(t, 100*time.Millisecond) + }) + } } // TestConnection_Bind_JSONError tests JSON unmarshaling error path. @@ -811,18 +1235,17 @@ func TestManager_CloseConnection_WithRealConn(t *testing.T) { time.Sleep(100 * time.Millisecond) } -// TestWSUpgrader_Upgrade_Error tests upgrade error path. +// TestWSUpgrader_Upgrade_Error tests upgrade error path with invalid request. func TestWSUpgrader_Upgrade_Error(t *testing.T) { - // Create a mock upgrader that returns an error - mockUpgrader := &mockUpgraderWithError{} - wsUpgrader := &WSUpgrader{Upgrader: mockUpgrader} - + upgrader := NewWSUpgrader() + + // Test with invalid request (not a WebSocket upgrade request) req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", http.NoBody) require.NoError(t, err) w := httptest.NewRecorder() - conn, err := wsUpgrader.Upgrade(w, req, nil) + conn, err := upgrader.Upgrade(w, req, nil) require.Error(t, err) require.Nil(t, conn) } @@ -977,9 +1400,3 @@ func createLargeJSON() []byte { return jsonData } -// mockUpgraderWithError is a mock upgrader that always returns an error. -type mockUpgraderWithError struct{} - -func (*mockUpgraderWithError) Upgrade(_ http.ResponseWriter, _ *http.Request, _ http.Header) (*websocket.Conn, error) { - return nil, ErrUpgradeFailed -} From 762157504f08e78e75c9316e027f1ad0e6051c06 Mon Sep 17 00:00:00 2001 From: ZopDev Date: Wed, 24 Sep 2025 14:38:04 +0530 Subject: [PATCH 03/12] Refactored test --- pkg/gofr/websocket/mock_interfaces.go | 100 -------- pkg/gofr/websocket/websocket_test.go | 323 ++++++++++++++++++++++---- 2 files changed, 276 insertions(+), 147 deletions(-) diff --git a/pkg/gofr/websocket/mock_interfaces.go b/pkg/gofr/websocket/mock_interfaces.go index 7261b6495..4eb276bd2 100644 --- a/pkg/gofr/websocket/mock_interfaces.go +++ b/pkg/gofr/websocket/mock_interfaces.go @@ -1,9 +1,7 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: pkg/gofr/websocket/interfaces.go // // Generated by this command: // -// mockgen -source=pkg/gofr/websocket/interfaces.go -package=websocket -destination=pkg/gofr/websocket/mock_interfaces.go // // Package websocket is a generated GoMock package. @@ -12,7 +10,6 @@ package websocket import ( http "net/http" reflect "reflect" - time "time" websocket "github.com/gorilla/websocket" gomock "go.uber.org/mock/gomock" @@ -22,7 +19,6 @@ import ( type MockUpgrader struct { ctrl *gomock.Controller recorder *MockUpgraderMockRecorder - isgomock struct{} } // MockUpgraderMockRecorder is the mock recorder for MockUpgrader. @@ -56,99 +52,3 @@ func (mr *MockUpgraderMockRecorder) Upgrade(w, r, responseHeader any) *gomock.Ca mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Upgrade", reflect.TypeOf((*MockUpgrader)(nil).Upgrade), w, r, responseHeader) } - -// MockWebSocketConn is a mock of WebSocketConn interface. -type MockWebSocketConn struct { - ctrl *gomock.Controller - recorder *MockWebSocketConnMockRecorder - isgomock struct{} -} - -// MockWebSocketConnMockRecorder is the mock recorder for MockWebSocketConn. -type MockWebSocketConnMockRecorder struct { - mock *MockWebSocketConn -} - -// NewMockWebSocketConn creates a new mock instance. -func NewMockWebSocketConn(ctrl *gomock.Controller) *MockWebSocketConn { - mock := &MockWebSocketConn{ctrl: ctrl} - mock.recorder = &MockWebSocketConnMockRecorder{mock} - return mock -} - -// EXPECT returns an object that allows the caller to indicate expected use. -func (m *MockWebSocketConn) EXPECT() *MockWebSocketConnMockRecorder { - return m.recorder -} - -// Close mocks base method. -func (m *MockWebSocketConn) Close() error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Close") - ret0, _ := ret[0].(error) - return ret0 -} - -// Close indicates an expected call of Close. -func (mr *MockWebSocketConnMockRecorder) Close() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockWebSocketConn)(nil).Close)) -} - -// ReadMessage mocks base method. -func (m *MockWebSocketConn) ReadMessage() (int, []byte, error) { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "ReadMessage") - ret0, _ := ret[0].(int) - ret1, _ := ret[1].([]byte) - ret2, _ := ret[2].(error) - return ret0, ret1, ret2 -} - -// ReadMessage indicates an expected call of ReadMessage. -func (mr *MockWebSocketConnMockRecorder) ReadMessage() *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadMessage", reflect.TypeOf((*MockWebSocketConn)(nil).ReadMessage)) -} - -// SetReadDeadline mocks base method. -func (m *MockWebSocketConn) SetReadDeadline(t time.Time) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetReadDeadline", t) - ret0, _ := ret[0].(error) - return ret0 -} - -// SetReadDeadline indicates an expected call of SetReadDeadline. -func (mr *MockWebSocketConnMockRecorder) SetReadDeadline(t any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockWebSocketConn)(nil).SetReadDeadline), t) -} - -// SetWriteDeadline mocks base method. -func (m *MockWebSocketConn) SetWriteDeadline(t time.Time) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "SetWriteDeadline", t) - ret0, _ := ret[0].(error) - return ret0 -} - -// SetWriteDeadline indicates an expected call of SetWriteDeadline. -func (mr *MockWebSocketConnMockRecorder) SetWriteDeadline(t any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockWebSocketConn)(nil).SetWriteDeadline), t) -} - -// WriteMessage mocks base method. -func (m *MockWebSocketConn) WriteMessage(messageType int, data []byte) error { - m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "WriteMessage", messageType, data) - ret0, _ := ret[0].(error) - return ret0 -} - -// WriteMessage indicates an expected call of WriteMessage. -func (mr *MockWebSocketConnMockRecorder) WriteMessage(messageType, data any) *gomock.Call { - mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteMessage", reflect.TypeOf((*MockWebSocketConn)(nil).WriteMessage), messageType, data) -} diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index 318975aa1..55272b59a 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -35,7 +35,7 @@ func TestMain(m *testing.M) { // setupWebSocketServer creates a test WebSocket server with the given handler. func setupWebSocketServer(t *testing.T, handler func(*websocket.Conn)) *httptest.Server { t.Helper() - + upgrader := websocket.Upgrader{} server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) @@ -44,36 +44,36 @@ func setupWebSocketServer(t *testing.T, handler func(*websocket.Conn)) *httptest return } defer conn.Close() - + handler(conn) })) - + return server } // connectToWebSocket connects to a WebSocket server and returns the connection. func connectToWebSocket(t *testing.T, serverURL string) (*websocket.Conn, *http.Response) { t.Helper() - + url := "ws" + serverURL[len("http"):] + "/ws" dialer := websocket.DefaultDialer conn, resp, err := dialer.Dial(url, nil) require.NoError(t, err) - + return conn, resp } // createTestConnection creates a WebSocket connection for testing. func createTestConnection(t *testing.T, conn *websocket.Conn) *Connection { t.Helper() - + return &Connection{Conn: conn} } // sendMessageToWebSocket sends a message to a WebSocket connection. func sendMessageToWebSocket(t *testing.T, conn *websocket.Conn, message []byte) { t.Helper() - + err := conn.WriteMessage(websocket.TextMessage, message) require.NoError(t, err) } @@ -81,7 +81,7 @@ func sendMessageToWebSocket(t *testing.T, conn *websocket.Conn, message []byte) // waitForWebSocketOperation waits for a WebSocket operation to complete. func waitForWebSocketOperation(t *testing.T, duration time.Duration) { t.Helper() - + time.Sleep(duration) } @@ -142,7 +142,10 @@ func TestConnection_Bind(t *testing.T) { conn, resp := connectToWebSocket(t, server.URL) defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } sendMessageToWebSocket(t, conn, tt.inputMessage) waitForWebSocketOperation(t, 100*time.Millisecond) @@ -188,7 +191,10 @@ func TestConnection_WriteMessage(t *testing.T) { require.NoError(t, err) defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } // Read messages to prevent connection from closing go func() { @@ -217,8 +223,12 @@ func TestConnection_ReadMessage(t *testing.T) { defer server.Close() conn, resp := connectToWebSocket(t, server.URL) + defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } wsConn := createTestConnection(t, conn) messageType, message, err := wsConn.ReadMessage() @@ -242,7 +252,7 @@ func TestConnection_ReadMessage_ErrorHandling(t *testing.T) { wsConn := createTestConnection(t, conn) // Close connection to force read error conn.Close() - + messageType, message, err := wsConn.ReadMessage() require.Error(t, err, "Expected error for closed connection") assert.Equal(t, -1, messageType) // Closed connection returns -1 @@ -259,12 +269,12 @@ func TestConnection_ReadMessage_ErrorHandling(t *testing.T) { // Set a very short read deadline to simulate timeout err := wsConn.SetReadDeadline(time.Now().Add(1 * time.Millisecond)) require.NoError(t, err) - + // Wait for deadline to pass time.Sleep(10 * time.Millisecond) - + messageType, message, err := wsConn.ReadMessage() - assert.Error(t, err, "Expected timeout error") + require.Error(t, err, "Expected timeout error") assert.Equal(t, -1, messageType) // Timeout returns -1 assert.Nil(t, message) }, @@ -282,7 +292,10 @@ func TestConnection_ReadMessage_ErrorHandling(t *testing.T) { conn, resp := connectToWebSocket(t, server.URL) defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } // Send a message to trigger the server handler sendMessageToWebSocket(t, conn, []byte("test")) @@ -321,7 +334,10 @@ func TestConnection_Deadlines(t *testing.T) { require.NoError(t, err) defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } time.Sleep(200 * time.Millisecond) } @@ -552,7 +568,7 @@ func TestWSUpgrader_ConflictingOptions(t *testing.T) { t.Run(tt.name, func(t *testing.T) { upgrader := NewWSUpgrader(tt.options...) assert.NotNil(t, upgrader, tt.description) - + // Verify the last option takes precedence actualUpgrader := upgrader.Upgrader.(*websocket.Upgrader) assert.NotNil(t, actualUpgrader, "Upgrader should be created successfully") @@ -606,14 +622,15 @@ func TestWSUpgrader_RealConnectionConflicts(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { upgrader := NewWSUpgrader(tt.options...) - + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if tt.expectError { assert.Error(t, err, tt.description) assert.Nil(t, conn, "Connection should be nil on error") } else { - require.NoError(t, err, tt.description) + assert.NoError(t, err, tt.description) + if conn != nil { conn.Close() } @@ -624,9 +641,10 @@ func TestWSUpgrader_RealConnectionConflicts(t *testing.T) { url := "ws" + server.URL[len("http"):] + "/ws" dialer := websocket.DefaultDialer conn, resp, err := dialer.Dial(url, nil) - + if tt.expectError { require.Error(t, err, tt.description) + if resp != nil { resp.Body.Close() } @@ -682,14 +700,15 @@ func TestWSUpgrader_Upgrade(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { upgrader := NewWSUpgrader(tt.options...) - + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) if tt.expectError { assert.Error(t, err, tt.description) assert.Nil(t, conn, "Connection should be nil on error") } else { - require.NoError(t, err, tt.description) + assert.NoError(t, err, tt.description) + if conn != nil { conn.Close() } @@ -700,9 +719,10 @@ func TestWSUpgrader_Upgrade(t *testing.T) { url := "ws" + server.URL[len("http"):] + "/ws" dialer := websocket.DefaultDialer conn, resp, err := dialer.Dial(url, nil) - + if tt.expectError { require.Error(t, err, tt.description) + if resp != nil { resp.Body.Close() } @@ -835,13 +855,6 @@ func TestConnection_Bind_EdgeCases(t *testing.T) { expectError bool description string }{ - { - name: "Bind to nil pointer", - inputMessage: []byte("test"), - targetType: (*string)(nil), - expectError: true, - description: "Should handle nil pointer gracefully", - }, { name: "Bind to non-pointer", inputMessage: []byte("test"), @@ -891,7 +904,7 @@ func TestConnection_Bind_EdgeCases(t *testing.T) { if tt.expectError { assert.Error(t, err, tt.description) } else { - require.NoError(t, err, tt.description) + assert.NoError(t, err, tt.description) } })) defer server.Close() @@ -902,7 +915,10 @@ func TestConnection_Bind_EdgeCases(t *testing.T) { require.NoError(t, err) defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } err = conn.WriteMessage(websocket.TextMessage, tt.inputMessage) require.NoError(t, err) @@ -983,6 +999,207 @@ func TestConnection_UnimplementedMethods(t *testing.T) { assert.Nil(t, params) } +// TestConnection_ConcurrentWriteMessage tests concurrent WriteMessage calls. +func TestConnection_ConcurrentWriteMessage(t *testing.T) { + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + wsConn := createTestConnection(t, conn) + + var wg sync.WaitGroup + + numGoroutines := 10 + + // Send multiple messages concurrently + for i := 0; i < numGoroutines; i++ { + wg.Add(1) + + go func(i int) { + defer wg.Done() + + message := fmt.Sprintf("message %d", i) + err := wsConn.WriteMessage(websocket.TextMessage, []byte(message)) + + if err != nil { + t.Errorf("WriteMessage failed: %v", err) + } + }(i) + } + + wg.Wait() + }) + defer server.Close() + + conn, resp := connectToWebSocket(t, server.URL) + + defer conn.Close() + + if resp != nil { + resp.Body.Close() + } + + // Read all messages + for i := 0; i < 10; i++ { + messageType, message, err := conn.ReadMessage() + require.NoError(t, err) + assert.Equal(t, websocket.TextMessage, messageType) + assert.Contains(t, string(message), "message") + } +} + +// TestConnection_Bind_JSONUnmarshalError tests Bind method with JSON unmarshaling error. +func TestConnection_Bind_JSONUnmarshalError(t *testing.T) { + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + wsConn := createTestConnection(t, conn) + + // Send invalid JSON + err := conn.WriteMessage(websocket.TextMessage, []byte("invalid json")) + if err != nil { + return // Ignore errors in server handler + } + + // Try to bind to a struct - should fail + var data struct { + Field string `json:"field"` + } + + err = wsConn.Bind(&data) + if err != nil { + return // Expected error, ignore + } + }) + defer server.Close() + + conn, resp := connectToWebSocket(t, server.URL) + defer conn.Close() + + if resp != nil { + resp.Body.Close() + } +} + +// TestConnection_Bind_StringCase tests Bind method with string case. +func TestConnection_Bind_StringCase(t *testing.T) { + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + wsConn := createTestConnection(t, conn) + + // Send text message + err := conn.WriteMessage(websocket.TextMessage, []byte("test message")) + + if err != nil { + return // Ignore errors in server handler + } + + // Bind to string + var data string + + err = wsConn.Bind(&data) + + if err != nil { + return // Ignore errors in server handler + } + }) + defer server.Close() + + conn, resp := connectToWebSocket(t, server.URL) + defer conn.Close() + + if resp != nil { + resp.Body.Close() + } +} + +// TestConnection_Bind_JSONCase tests Bind method with JSON case. +func TestConnection_Bind_JSONCase(t *testing.T) { + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + wsConn := createTestConnection(t, conn) + + // Send JSON message + jsonData := `{"name": "test", "value": 123}` + err := conn.WriteMessage(websocket.TextMessage, []byte(jsonData)) + + if err != nil { + return // Ignore errors in server handler + } + + // Bind to struct + var data struct { + Name string `json:"name"` + Value int `json:"value"` + } + + err = wsConn.Bind(&data) + + if err != nil { + return // Ignore errors in server handler + } + }) + defer server.Close() + + conn, resp := connectToWebSocket(t, server.URL) + defer conn.Close() + + if resp != nil { + resp.Body.Close() + } +} + +// TestManager_CloseConnection_WithValidConn tests CloseConnection with valid connection. +func TestManager_CloseConnection_WithValidConn(t *testing.T) { + manager := New() + + // Create a real connection + server := setupWebSocketServer(t, func(_ *websocket.Conn) { + // Just keep the connection open + time.Sleep(100 * time.Millisecond) + }) + defer server.Close() + + conn, resp := connectToWebSocket(t, server.URL) + if resp != nil { + resp.Body.Close() + } + + wsConn := &Connection{Conn: conn} + manager.AddWebsocketConnection("test", wsConn) + + // Close connection - should not panic + manager.CloseConnection("test") + + // Verify connection is removed + assert.Nil(t, manager.GetWebsocketConnection("test")) +} + +// TestWSUpgrader_Upgrade_WithResponseHeader tests Upgrade with response header. +func TestWSUpgrader_Upgrade_WithResponseHeader(t *testing.T) { + upgrader := NewWSUpgrader() + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + responseHeader := http.Header{} + responseHeader.Set("X-Test-Header", "test-value") + + conn, err := upgrader.Upgrade(w, r, responseHeader) + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return + } + defer conn.Close() + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + + if resp != nil { + resp.Body.Close() + } + + // Verify response header was set + assert.Equal(t, "test-value", resp.Header.Get("X-Test-Header")) +} + // OPTIONS TESTS // TestOptions tests all option functions. func TestOptions(t *testing.T) { @@ -1044,7 +1261,7 @@ func TestConnection_Bind_ErrorPaths(t *testing.T) { wsConn := createTestConnection(t, conn) // Close connection to force read error conn.Close() - + var data string err := wsConn.Bind(&data) assert.Error(t, err, "Expected error for closed connection") @@ -1060,13 +1277,13 @@ func TestConnection_Bind_ErrorPaths(t *testing.T) { // Set a very short read deadline to simulate timeout err := wsConn.SetReadDeadline(time.Now().Add(1 * time.Millisecond)) require.NoError(t, err) - + // Wait for deadline to pass time.Sleep(10 * time.Millisecond) - + var data string err = wsConn.Bind(&data) - assert.Error(t, err, "Expected timeout error") + require.Error(t, err, "Expected timeout error") }, expectError: true, description: "Should handle network timeout during read", @@ -1076,11 +1293,11 @@ func TestConnection_Bind_ErrorPaths(t *testing.T) { setupServer: func(t *testing.T, conn *websocket.Conn) { t.Helper() wsConn := createTestConnection(t, conn) - + // Send binary message instead of text err := conn.WriteMessage(websocket.BinaryMessage, []byte("binary data")) require.NoError(t, err) - + var data string err = wsConn.Bind(&data) // This should still work as we're reading the message @@ -1094,10 +1311,10 @@ func TestConnection_Bind_ErrorPaths(t *testing.T) { setupServer: func(t *testing.T, conn *websocket.Conn) { t.Helper() wsConn := createTestConnection(t, conn) - + // Close connection immediately to simulate interruption conn.Close() - + var data string err := wsConn.Bind(&data) assert.Error(t, err, "Expected error for interrupted connection") @@ -1115,8 +1332,12 @@ func TestConnection_Bind_ErrorPaths(t *testing.T) { defer server.Close() conn, resp := connectToWebSocket(t, server.URL) + defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } // Send a message to trigger the server handler sendMessageToWebSocket(t, conn, []byte("test")) @@ -1155,7 +1376,10 @@ func TestConnection_Bind_JSONError(t *testing.T) { require.NoError(t, err) defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } // Send invalid JSON to trigger error path err = conn.WriteMessage(websocket.TextMessage, []byte(`{"invalid": json}`)) @@ -1226,7 +1450,10 @@ func TestManager_CloseConnection_WithRealConn(t *testing.T) { require.NoError(t, err) defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } // Send a message to trigger the server handler err = conn.WriteMessage(websocket.TextMessage, []byte("test")) @@ -1238,7 +1465,7 @@ func TestManager_CloseConnection_WithRealConn(t *testing.T) { // TestWSUpgrader_Upgrade_Error tests upgrade error path with invalid request. func TestWSUpgrader_Upgrade_Error(t *testing.T) { upgrader := NewWSUpgrader() - + // Test with invalid request (not a WebSocket upgrade request) req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "/", http.NoBody) require.NoError(t, err) @@ -1295,7 +1522,10 @@ func TestConnection_Performance(t *testing.T) { require.NoError(t, err) defer conn.Close() - defer resp.Body.Close() + + if resp != nil { + resp.Body.Close() + } // Read messages start := time.Now() @@ -1399,4 +1629,3 @@ func createLargeJSON() []byte { return jsonData } - From 1b1f7f2e01c80617eb97801bce01402d2e8ffba6 Mon Sep 17 00:00:00 2001 From: ZopDev Date: Wed, 24 Sep 2025 16:17:33 +0530 Subject: [PATCH 04/12] Refactor Websocket_test and fixed linters --- pkg/gofr/websocket/websocket_test.go | 112 ++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 12 deletions(-) diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index 55272b59a..8832b1bf7 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -3,7 +3,6 @@ package websocket import ( "context" "encoding/json" - "errors" "fmt" "net/http" "net/http/httptest" @@ -15,14 +14,7 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" -) - -// Define static errors for better error handling. -var ( - ErrUpgradeFailed = errors.New("upgrade failed") - ErrNetworkTimeout = errors.New("network timeout") - ErrUnexpectedResponse = errors.New("unexpected server response") - ErrConnectionClosed = errors.New("connection closed") + "go.uber.org/mock/gomock" ) func TestMain(m *testing.M) { @@ -30,8 +22,6 @@ func TestMain(m *testing.M) { m.Run() } -// Helper functions to reduce test duplication - // setupWebSocketServer creates a test WebSocket server with the given handler. func setupWebSocketServer(t *testing.T, handler func(*websocket.Conn)) *httptest.Server { t.Helper() @@ -1332,7 +1322,6 @@ func TestConnection_Bind_ErrorPaths(t *testing.T) { defer server.Close() conn, resp := connectToWebSocket(t, server.URL) - defer conn.Close() if resp != nil { @@ -1629,3 +1618,102 @@ func createLargeJSON() []byte { return jsonData } + +// MOCK TESTS +// TestMockUpgrader tests the mock upgrader functionality. +func TestMockUpgrader(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUpgrader := NewMockUpgrader(ctrl) + + // Test EXPECT method + recorder := mockUpgrader.EXPECT() + assert.NotNil(t, recorder) + + // Test Upgrade method + req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws", http.NoBody) + responseHeader := http.Header{} + + // Set up mock expectations + mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) + + // Call the method + conn, err := mockUpgrader.Upgrade(nil, req, responseHeader) + assert.Nil(t, conn) + require.NoError(t, err) +} + +// TestMockUpgrader_WithRealConnection tests the mock upgrader with real connection. +func TestMockUpgrader_WithRealConnection(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUpgrader := NewMockUpgrader(ctrl) + + // Create a real WebSocket server + server := setupWebSocketServer(t, func(_ *websocket.Conn) { + // Just keep the connection open + time.Sleep(100 * time.Millisecond) + }) + defer server.Close() + + // Test with real connection + req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws", http.NoBody) + responseHeader := http.Header{} + + // Set up mock expectations + mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) + + // Call the method + conn, err := mockUpgrader.Upgrade(nil, req, responseHeader) + assert.Nil(t, conn) + require.NoError(t, err) +} + +// TestMockUpgrader_Upgrade_Error tests the mock upgrader with error. +func TestMockUpgrader_Upgrade_Error(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUpgrader := NewMockUpgrader(ctrl) + + req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws", http.NoBody) + responseHeader := http.Header{} + + // Set up mock expectations for error case + expectedError := fmt.Errorf("upgrade failed: %w", ErrorConnection) + + mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, expectedError) + + // Call the method + conn, err := mockUpgrader.Upgrade(nil, req, responseHeader) + assert.Nil(t, conn) + require.Error(t, err) + assert.Equal(t, expectedError, err) +} + +// TestMockUpgrader_Integration tests the mock upgrader integration. +func TestMockUpgrader_Integration(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + mockUpgrader := NewMockUpgrader(ctrl) + + // Test multiple calls + req1, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws1", http.NoBody) + req2, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws2", http.NoBody) + responseHeader := http.Header{} + + // Set up mock expectations for multiple calls + mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(2) + + // Call the method multiple times + conn1, err1 := mockUpgrader.Upgrade(nil, req1, responseHeader) + conn2, err2 := mockUpgrader.Upgrade(nil, req2, responseHeader) + + assert.Nil(t, conn1) + require.NoError(t, err1) + assert.Nil(t, conn2) + require.NoError(t, err2) +} From cd7487ad68f624244ca41ec91dffc5e183c195a5 Mon Sep 17 00:00:00 2001 From: ZopDev Date: Tue, 30 Sep 2025 11:48:14 +0530 Subject: [PATCH 05/12] Refactored test file --- pkg/gofr/websocket/websocket_test.go | 542 ++++++++++----------------- 1 file changed, 199 insertions(+), 343 deletions(-) diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index 8832b1bf7..4ce7effdb 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -14,7 +14,6 @@ import ( "github.com/gorilla/websocket" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "go.uber.org/mock/gomock" ) func TestMain(m *testing.M) { @@ -76,42 +75,31 @@ func waitForWebSocketOperation(t *testing.T, duration time.Duration) { } // CORE FUNCTIONALITY TESTS -// TestConnection_Bind tests the Bind method with various data types. -func TestConnection_Bind(t *testing.T) { +// TestConnection_Bind_Success tests the Bind method with successful cases. +func TestConnection_Bind_Success(t *testing.T) { tests := []struct { name string inputMessage []byte targetType any expected any - expectError bool }{ { name: "Bind to string - success", inputMessage: []byte("Hello, WebSocket"), targetType: new(string), expected: "Hello, WebSocket", - expectError: false, }, { name: "Bind to JSON struct - success", inputMessage: []byte(`{"name":"test","value":123}`), targetType: &map[string]any{}, expected: map[string]any{"name": "test", "value": float64(123)}, - expectError: false, - }, - { - name: "Bind to invalid JSON - error", - inputMessage: []byte(`{"name":"test",invalid}`), - targetType: &map[string]any{}, - expected: nil, - expectError: true, }, { name: "Bind to custom struct - success", inputMessage: []byte(`{"id":1,"name":"test"}`), targetType: &testStruct{}, expected: testStruct{ID: 1, Name: "test"}, - expectError: false, }, } @@ -120,13 +108,44 @@ func TestConnection_Bind(t *testing.T) { server := setupWebSocketServer(t, func(conn *websocket.Conn) { wsConn := createTestConnection(t, conn) err := wsConn.Bind(tt.targetType) + require.NoError(t, err) + assert.Equal(t, tt.expected, dereferenceValue(tt.targetType)) + }) + defer server.Close() - if tt.expectError { - require.Error(t, err) - } else { - require.NoError(t, err) - assert.Equal(t, tt.expected, dereferenceValue(tt.targetType)) - } + conn, resp := connectToWebSocket(t, server.URL) + defer conn.Close() + + if resp != nil { + resp.Body.Close() + } + + sendMessageToWebSocket(t, conn, tt.inputMessage) + waitForWebSocketOperation(t, 100*time.Millisecond) + }) + } +} + +// TestConnection_Bind_Failure tests the Bind method with error cases. +func TestConnection_Bind_Failure(t *testing.T) { + tests := []struct { + name string + inputMessage []byte + targetType any + }{ + { + name: "Bind to invalid JSON - error", + inputMessage: []byte(`{"name":"test",invalid}`), + targetType: &map[string]any{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := setupWebSocketServer(t, func(conn *websocket.Conn) { + wsConn := createTestConnection(t, conn) + err := wsConn.Bind(tt.targetType) + require.Error(t, err) }) defer server.Close() @@ -566,36 +585,18 @@ func TestWSUpgrader_ConflictingOptions(t *testing.T) { } } -// TestWSUpgrader_RealConnectionConflicts tests conflicting options with real WebSocket connections. -func TestWSUpgrader_RealConnectionConflicts(t *testing.T) { +// TestWSUpgrader_RealConnectionConflicts_Success tests successful connection scenarios. +func TestWSUpgrader_RealConnectionConflicts_Success(t *testing.T) { tests := []struct { name string options []Options - expectError bool description string }{ - { - name: "CheckOrigin rejecting all connections", - options: []Options{ - WithCheckOrigin(func(_ *http.Request) bool { return false }), - }, - expectError: true, - description: "Should reject connection when CheckOrigin returns false", - }, - { - name: "Very short handshake timeout", - options: []Options{ - WithHandshakeTimeout(1 * time.Nanosecond), - }, - expectError: true, - description: "Should timeout with very short handshake timeout", - }, { name: "CheckOrigin accepting all connections", options: []Options{ WithCheckOrigin(func(_ *http.Request) bool { return true }), }, - expectError: false, description: "Should accept connection when CheckOrigin returns true", }, { @@ -604,7 +605,6 @@ func TestWSUpgrader_RealConnectionConflicts(t *testing.T) { WithHandshakeTimeout(5 * time.Second), WithCompression(), }, - expectError: false, description: "Should work with normal timeout and compression", }, } @@ -615,15 +615,10 @@ func TestWSUpgrader_RealConnectionConflicts(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) - if tt.expectError { - assert.Error(t, err, tt.description) - assert.Nil(t, conn, "Connection should be nil on error") - } else { - assert.NoError(t, err, tt.description) - - if conn != nil { - conn.Close() - } + assert.NoError(t, err, tt.description) + + if conn != nil { + conn.Close() } })) defer server.Close() @@ -631,40 +626,75 @@ func TestWSUpgrader_RealConnectionConflicts(t *testing.T) { url := "ws" + server.URL[len("http"):] + "/ws" dialer := websocket.DefaultDialer conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err, tt.description) - if tt.expectError { - require.Error(t, err, tt.description) + if conn != nil { + conn.Close() + } - if resp != nil { - resp.Body.Close() - } - } else { - require.NoError(t, err, tt.description) + if resp != nil { + resp.Body.Close() + } + }) + } +} - if conn != nil { - conn.Close() - } +// TestWSUpgrader_RealConnectionConflicts_Failure tests error connection scenarios. +func TestWSUpgrader_RealConnectionConflicts_Failure(t *testing.T) { + tests := []struct { + name string + options []Options + description string + }{ + { + name: "CheckOrigin rejecting all connections", + options: []Options{ + WithCheckOrigin(func(_ *http.Request) bool { return false }), + }, + description: "Should reject connection when CheckOrigin returns false", + }, + { + name: "Very short handshake timeout", + options: []Options{ + WithHandshakeTimeout(1 * time.Nanosecond), + }, + description: "Should timeout with very short handshake timeout", + }, + } - if resp != nil { - resp.Body.Close() - } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + upgrader := NewWSUpgrader(tt.options...) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + assert.Error(t, err, tt.description) + assert.Nil(t, conn, "Connection should be nil on error") + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + _, resp, err := dialer.Dial(url, nil) + require.Error(t, err, tt.description) + + if resp != nil { + resp.Body.Close() } }) } } -// TestWSUpgrader_Upgrade tests the upgrade functionality with real connections. -func TestWSUpgrader_Upgrade(t *testing.T) { +// TestWSUpgrader_Upgrade_Success tests successful upgrade scenarios. +func TestWSUpgrader_Upgrade_Success(t *testing.T) { tests := []struct { name string options []Options - expectError bool description string }{ { name: "Successful upgrade with default options", options: []Options{}, - expectError: false, description: "Should successfully upgrade HTTP to WebSocket", }, { @@ -674,15 +704,52 @@ func TestWSUpgrader_Upgrade(t *testing.T) { WithWriteBufferSize(1024), WithHandshakeTimeout(5 * time.Second), }, - expectError: false, description: "Should successfully upgrade with custom options", }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + upgrader := NewWSUpgrader(tt.options...) + + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + assert.NoError(t, err, tt.description) + + if conn != nil { + conn.Close() + } + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err, tt.description) + + if conn != nil { + conn.Close() + } + + if resp != nil { + resp.Body.Close() + } + }) + } +} + +// TestWSUpgrader_Upgrade_Failure tests error upgrade scenarios. +func TestWSUpgrader_Upgrade_Failure(t *testing.T) { + tests := []struct { + name string + options []Options + description string + }{ { name: "Upgrade with CheckOrigin rejection", options: []Options{ WithCheckOrigin(func(_ *http.Request) bool { return false }), }, - expectError: true, description: "Should fail when CheckOrigin returns false", }, } @@ -693,39 +760,18 @@ func TestWSUpgrader_Upgrade(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { conn, err := upgrader.Upgrade(w, r, nil) - if tt.expectError { - assert.Error(t, err, tt.description) - assert.Nil(t, conn, "Connection should be nil on error") - } else { - assert.NoError(t, err, tt.description) - - if conn != nil { - conn.Close() - } - } + assert.Error(t, err, tt.description) + assert.Nil(t, conn, "Connection should be nil on error") })) defer server.Close() url := "ws" + server.URL[len("http"):] + "/ws" dialer := websocket.DefaultDialer - conn, resp, err := dialer.Dial(url, nil) + _, resp, err := dialer.Dial(url, nil) + require.Error(t, err, tt.description) - if tt.expectError { - require.Error(t, err, tt.description) - - if resp != nil { - resp.Body.Close() - } - } else { - require.NoError(t, err, tt.description) - - if conn != nil { - conn.Close() - } - - if resp != nil { - resp.Body.Close() - } + if resp != nil { + resp.Body.Close() } }) } @@ -836,41 +882,30 @@ func TestManager_ConcurrentOperations(t *testing.T) { } // EDGE CASE TESTS -// TestConnection_Bind_EdgeCases tests edge cases for the Bind method. -func TestConnection_Bind_EdgeCases(t *testing.T) { +// TestConnection_Bind_EdgeCases_Success tests successful edge cases for the Bind method. +func TestConnection_Bind_EdgeCases_Success(t *testing.T) { tests := []struct { name string inputMessage []byte targetType any - expectError bool description string }{ - { - name: "Bind to non-pointer", - inputMessage: []byte("test"), - targetType: "not a pointer", - expectError: true, - description: "Should handle non-pointer types", - }, { name: "Bind to empty string", inputMessage: []byte(""), targetType: new(string), - expectError: false, description: "Should handle empty string", }, { name: "Bind to large JSON", inputMessage: createLargeJSON(), targetType: &map[string]any{}, - expectError: false, description: "Should handle large JSON payloads", }, { name: "Bind to invalid UTF-8", inputMessage: []byte{0xff, 0xfe, 0xfd}, targetType: new(string), - expectError: false, description: "Should handle invalid UTF-8 sequences", }, } @@ -890,12 +925,61 @@ func TestConnection_Bind_EdgeCases(t *testing.T) { wsConn := &Connection{Conn: conn} err = wsConn.Bind(tt.targetType) + assert.NoError(t, err, tt.description) + })) + defer server.Close() + + url := "ws" + server.URL[len("http"):] + "/ws" + dialer := websocket.DefaultDialer + conn, resp, err := dialer.Dial(url, nil) + require.NoError(t, err) + + defer conn.Close() + + if resp != nil { + resp.Body.Close() + } + + err = conn.WriteMessage(websocket.TextMessage, tt.inputMessage) + require.NoError(t, err) + + time.Sleep(100 * time.Millisecond) + }) + } +} + +// TestConnection_Bind_EdgeCases_Failure tests error edge cases for the Bind method. +func TestConnection_Bind_EdgeCases_Failure(t *testing.T) { + tests := []struct { + name string + inputMessage []byte + targetType any + description string + }{ + { + name: "Bind to non-pointer", + inputMessage: []byte("test"), + targetType: "not a pointer", + description: "Should handle non-pointer types", + }, + } - if tt.expectError { - assert.Error(t, err, tt.description) - } else { - assert.NoError(t, err, tt.description) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + upgrader := websocket.Upgrader{} + conn, err := upgrader.Upgrade(w, r, nil) + + if err != nil { + t.Errorf("Failed to upgrade connection: %v", err) + return } + + defer conn.Close() + + wsConn := &Connection{Conn: conn} + err = wsConn.Bind(tt.targetType) + assert.Error(t, err, tt.description) })) defer server.Close() @@ -1228,14 +1312,6 @@ func TestOptions(t *testing.T) { assert.NotNil(t, upgrader.CheckOrigin) } -// CONSTANTS TESTS -// TestConstants tests package constants. -func TestConstants(t *testing.T) { - assert.Equal(t, WSConnectionKey, WSKey("ws-connection-key")) - assert.Equal(t, 1, TextMessage) - assert.Equal(t, "couldn't establish connection to web socket", ErrorConnection.Error()) -} - // TestConnection_Bind_ErrorPaths tests comprehensive error paths in Bind method. func TestConnection_Bind_ErrorPaths(t *testing.T) { tests := []struct { @@ -1451,8 +1527,8 @@ func TestManager_CloseConnection_WithRealConn(t *testing.T) { time.Sleep(100 * time.Millisecond) } -// TestWSUpgrader_Upgrade_Error tests upgrade error path with invalid request. -func TestWSUpgrader_Upgrade_Error(t *testing.T) { +// TestWSUpgrader_Upgrade_InvalidRequest tests upgrade error path with invalid request. +func TestWSUpgrader_Upgrade_InvalidRequest(t *testing.T) { upgrader := NewWSUpgrader() // Test with invalid request (not a WebSocket upgrade request) @@ -1466,127 +1542,6 @@ func TestWSUpgrader_Upgrade_Error(t *testing.T) { require.Nil(t, conn) } -// PERFORMANCE TESTS -// TestConnection_Performance tests performance characteristics. -func TestConnection_Performance(t *testing.T) { - if testing.Short() { - t.Skip("Skipping performance test in short mode") - } - - upgrader := websocket.Upgrader{} - messageCount := 10000 - - server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - conn, err := upgrader.Upgrade(w, r, nil) - if err != nil { - t.Errorf("Failed to upgrade connection: %v", err) - return - } - defer conn.Close() - - wsConn := &Connection{Conn: conn} - - start := time.Now() - - // Write many messages - for i := 0; i < messageCount; i++ { - message := []byte("performance test message") - err := wsConn.WriteMessage(websocket.TextMessage, message) - - if err != nil { - t.Errorf("Failed to write message: %v", err) - return - } - } - - duration := time.Since(start) - t.Logf("Wrote %d messages in %v (%.2f msg/sec)", - messageCount, duration, float64(messageCount)/duration.Seconds()) - })) - defer server.Close() - - url := "ws" + server.URL[len("http"):] + "/ws" - dialer := websocket.DefaultDialer - conn, resp, err := dialer.Dial(url, nil) - require.NoError(t, err) - - defer conn.Close() - - if resp != nil { - resp.Body.Close() - } - - // Read messages - start := time.Now() - - for i := 0; i < messageCount; i++ { - _, _, err := conn.ReadMessage() - require.NoError(t, err) - } - - duration := time.Since(start) - t.Logf("Read %d messages in %v (%.2f msg/sec)", - messageCount, duration, float64(messageCount)/duration.Seconds()) -} - -// TestManager_Performance tests manager performance. -func TestManager_Performance(t *testing.T) { - if testing.Short() { - t.Skip("Skipping performance test in short mode") - } - - manager := New() - connectionCount := 10000 - - // Test adding many connections - start := time.Now() - - for i := 0; i < connectionCount; i++ { - conn := &Connection{Conn: nil} // Use nil to avoid close issues - manager.AddWebsocketConnection("conn"+string(rune(i)), conn) - } - - addDuration := time.Since(start) - t.Logf("Added %d connections in %v (%.2f conn/sec)", - connectionCount, addDuration, float64(connectionCount)/addDuration.Seconds()) - - // Test listing connections - start = time.Now() - connections := manager.ListConnections() - listDuration := time.Since(start) - - assert.Len(t, connections, connectionCount) - t.Logf("Listed %d connections in %v (%.2f conn/sec)", - len(connections), listDuration, float64(len(connections))/listDuration.Seconds()) - - // Test getting connections - start = time.Now() - - for i := 0; i < connectionCount; i++ { - conn := manager.GetWebsocketConnection("conn" + string(rune(i))) - assert.NotNil(t, conn) - } - - getDuration := time.Since(start) - t.Logf("Retrieved %d connections in %v (%.2f conn/sec)", - connectionCount, getDuration, float64(connectionCount)/getDuration.Seconds()) - - // Test closing connections - start = time.Now() - - for i := 0; i < connectionCount; i++ { - manager.CloseConnection("conn" + string(rune(i))) - } - - closeDuration := time.Since(start) - t.Logf("Closed %d connections in %v (%.2f conn/sec)", - connectionCount, closeDuration, float64(connectionCount)/closeDuration.Seconds()) - - // Verify all connections are closed - connections = manager.ListConnections() - assert.Empty(t, connections) -} - // testStruct is a helper type for testing. type testStruct struct { ID int `json:"id"` @@ -1618,102 +1573,3 @@ func createLargeJSON() []byte { return jsonData } - -// MOCK TESTS -// TestMockUpgrader tests the mock upgrader functionality. -func TestMockUpgrader(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockUpgrader := NewMockUpgrader(ctrl) - - // Test EXPECT method - recorder := mockUpgrader.EXPECT() - assert.NotNil(t, recorder) - - // Test Upgrade method - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws", http.NoBody) - responseHeader := http.Header{} - - // Set up mock expectations - mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) - - // Call the method - conn, err := mockUpgrader.Upgrade(nil, req, responseHeader) - assert.Nil(t, conn) - require.NoError(t, err) -} - -// TestMockUpgrader_WithRealConnection tests the mock upgrader with real connection. -func TestMockUpgrader_WithRealConnection(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockUpgrader := NewMockUpgrader(ctrl) - - // Create a real WebSocket server - server := setupWebSocketServer(t, func(_ *websocket.Conn) { - // Just keep the connection open - time.Sleep(100 * time.Millisecond) - }) - defer server.Close() - - // Test with real connection - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws", http.NoBody) - responseHeader := http.Header{} - - // Set up mock expectations - mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil) - - // Call the method - conn, err := mockUpgrader.Upgrade(nil, req, responseHeader) - assert.Nil(t, conn) - require.NoError(t, err) -} - -// TestMockUpgrader_Upgrade_Error tests the mock upgrader with error. -func TestMockUpgrader_Upgrade_Error(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockUpgrader := NewMockUpgrader(ctrl) - - req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws", http.NoBody) - responseHeader := http.Header{} - - // Set up mock expectations for error case - expectedError := fmt.Errorf("upgrade failed: %w", ErrorConnection) - - mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, expectedError) - - // Call the method - conn, err := mockUpgrader.Upgrade(nil, req, responseHeader) - assert.Nil(t, conn) - require.Error(t, err) - assert.Equal(t, expectedError, err) -} - -// TestMockUpgrader_Integration tests the mock upgrader integration. -func TestMockUpgrader_Integration(t *testing.T) { - ctrl := gomock.NewController(t) - defer ctrl.Finish() - - mockUpgrader := NewMockUpgrader(ctrl) - - // Test multiple calls - req1, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws1", http.NoBody) - req2, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, "/ws2", http.NoBody) - responseHeader := http.Header{} - - // Set up mock expectations for multiple calls - mockUpgrader.EXPECT().Upgrade(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil, nil).Times(2) - - // Call the method multiple times - conn1, err1 := mockUpgrader.Upgrade(nil, req1, responseHeader) - conn2, err2 := mockUpgrader.Upgrade(nil, req2, responseHeader) - - assert.Nil(t, conn1) - require.NoError(t, err1) - assert.Nil(t, conn2) - require.NoError(t, err2) -} From 8973b77b50ef0640950510e18c6689b047d8e03d Mon Sep 17 00:00:00 2001 From: ZopDev Date: Tue, 30 Sep 2025 12:30:06 +0530 Subject: [PATCH 06/12] Refactored websocket test according to review comments --- pkg/gofr/websocket/websocket_test.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index 4ce7effdb..f11b79cb3 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -68,10 +68,10 @@ func sendMessageToWebSocket(t *testing.T, conn *websocket.Conn, message []byte) } // waitForWebSocketOperation waits for a WebSocket operation to complete. -func waitForWebSocketOperation(t *testing.T, duration time.Duration) { +func waitForWebSocketOperation(t *testing.T) { t.Helper() - time.Sleep(duration) + time.Sleep(100 * time.Millisecond) } // CORE FUNCTIONALITY TESTS @@ -121,7 +121,7 @@ func TestConnection_Bind_Success(t *testing.T) { } sendMessageToWebSocket(t, conn, tt.inputMessage) - waitForWebSocketOperation(t, 100*time.Millisecond) + waitForWebSocketOperation(t) }) } } @@ -157,7 +157,7 @@ func TestConnection_Bind_Failure(t *testing.T) { } sendMessageToWebSocket(t, conn, tt.inputMessage) - waitForWebSocketOperation(t, 100*time.Millisecond) + waitForWebSocketOperation(t) }) } } @@ -308,7 +308,7 @@ func TestConnection_ReadMessage_ErrorHandling(t *testing.T) { // Send a message to trigger the server handler sendMessageToWebSocket(t, conn, []byte("test")) - waitForWebSocketOperation(t, 100*time.Millisecond) + waitForWebSocketOperation(t) }) } } @@ -1406,7 +1406,7 @@ func TestConnection_Bind_ErrorPaths(t *testing.T) { // Send a message to trigger the server handler sendMessageToWebSocket(t, conn, []byte("test")) - waitForWebSocketOperation(t, 100*time.Millisecond) + waitForWebSocketOperation(t) }) } } From 28531ee25039517b95ed701496e15292f9c74462 Mon Sep 17 00:00:00 2001 From: ZopDev Date: Tue, 30 Sep 2025 12:32:02 +0530 Subject: [PATCH 07/12] updated : go.sum and go.work.sum --- go.sum | 11 +++++++---- go.work.sum | 3 --- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/go.sum b/go.sum index 3b094c1fd..349823a6f 100644 --- a/go.sum +++ b/go.sum @@ -39,8 +39,9 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/dgo/v210 v210.0.0-20230328113526-b66f8ae53a2d h1:abDbP7XBVgwda+h0J5Qra5p2OQpidU2FdkXvzCKL+H8= github.com/dgraph-io/dgo/v210 v210.0.0-20230328113526-b66f8ae53a2d/go.mod h1:wKFzULXAPj3U2BDAPWXhSbQQNC6FU1+1/5iika6IY7g= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= @@ -55,8 +56,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -158,8 +159,9 @@ github.com/pierrec/lz4/v4 v4.1.22/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFu github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= @@ -304,6 +306,7 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= diff --git a/go.work.sum b/go.work.sum index cef7e0be8..52db9a3da 100644 --- a/go.work.sum +++ b/go.work.sum @@ -892,7 +892,6 @@ github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= @@ -954,7 +953,6 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -1238,7 +1236,6 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgc github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= From ccb551ab98ae8ce1cbaa7d0ae44e4853d0f20fc6 Mon Sep 17 00:00:00 2001 From: ZopDev Date: Tue, 30 Sep 2025 12:41:34 +0530 Subject: [PATCH 08/12] revert changes for mock_interface.go --- pkg/gofr/websocket/mock_interfaces.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pkg/gofr/websocket/mock_interfaces.go b/pkg/gofr/websocket/mock_interfaces.go index 4eb276bd2..785da344a 100644 --- a/pkg/gofr/websocket/mock_interfaces.go +++ b/pkg/gofr/websocket/mock_interfaces.go @@ -1,7 +1,9 @@ // Code generated by MockGen. DO NOT EDIT. +// Source: interfaces.go // // Generated by this command: // +// mockgen -source=interfaces.go -package=websocket -destination=mock_interfaces.go // // Package websocket is a generated GoMock package. From db9bbf76d740cbfef0521068fcc82bd74b9e2197 Mon Sep 17 00:00:00 2001 From: ZopDev Date: Mon, 6 Oct 2025 12:17:21 +0530 Subject: [PATCH 09/12] Reverted go.work.sum changed --- go.work.sum | 3 +++ 1 file changed, 3 insertions(+) diff --git a/go.work.sum b/go.work.sum index c0f20728e..5d64a2ae5 100644 --- a/go.work.sum +++ b/go.work.sum @@ -892,6 +892,7 @@ github.com/cpuguy83/dockercfg v0.3.1/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHf github.com/cpuguy83/dockercfg v0.3.2 h1:DlJTyZGBDlXqUZ2Dk2Q3xHs/FtnooJJVaad2S9GKorA= github.com/cpuguy83/dockercfg v0.3.2/go.mod h1:sugsbF4//dDlL/i+S+rtpIWp+5h0BHJHfjj5/jFyUJc= github.com/creack/pty v1.1.9 h1:uDmaGzcdjhF4i/plgjmEsriH11Y0o7RKapEf/LDaM3w= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgraph-io/badger/v2 v2.2007.4 h1:TRWBQg8UrlUhaFdco01nO2uXwzKS7zd+HVdwV/GHc4o= github.com/dgraph-io/badger/v2 v2.2007.4/go.mod h1:vSw/ax2qojzbN6eXHIx6KPKtCSHJN/Uz0X0VPruTIhk= github.com/dgraph-io/ristretto v0.0.3-0.20200630154024-f66de99634de h1:t0UHb5vdojIDUqktM6+xJAfScFBsVpXZmqC9dsgJmeA= @@ -953,6 +954,7 @@ github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -1236,6 +1238,7 @@ github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgc github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v1.20.4/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE= From c0818cbaf8597b4a51524a4d48647c7c9b74b844 Mon Sep 17 00:00:00 2001 From: ZopDev Date: Mon, 6 Oct 2025 12:24:03 +0530 Subject: [PATCH 10/12] Resolved merge conflict --- pkg/gofr/websocket/websocket_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index f11b79cb3..8081dea5a 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -914,8 +914,8 @@ func TestConnection_Bind_EdgeCases_Success(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { upgrader := websocket.Upgrader{} - conn, err := upgrader.Upgrade(w, r, nil) + conn, err := upgrader.Upgrade(w, r, nil) if err != nil { t.Errorf("Failed to upgrade connection: %v", err) return @@ -1255,6 +1255,7 @@ func TestWSUpgrader_Upgrade_WithResponseHeader(t *testing.T) { t.Errorf("Failed to upgrade connection: %v", err) return } + defer conn.Close() })) defer server.Close() From cfae3979b12e9fe06cff175bea8c4d61dc3a359b Mon Sep 17 00:00:00 2001 From: ZopDev Date: Mon, 6 Oct 2025 12:46:37 +0530 Subject: [PATCH 11/12] fixed linting errors --- pkg/gofr/websocket/websocket_test.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index 8081dea5a..06c21e6f3 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -870,6 +870,7 @@ func TestManager_ConcurrentOperations(t *testing.T) { go func(i int) { defer wg.Done() + manager.CloseConnection("conn" + string(rune(i))) }(i) } @@ -968,8 +969,8 @@ func TestConnection_Bind_EdgeCases_Failure(t *testing.T) { t.Run(tt.name, func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { upgrader := websocket.Upgrader{} - conn, err := upgrader.Upgrade(w, r, nil) + conn, err := upgrader.Upgrade(w, r, nil) if err != nil { t.Errorf("Failed to upgrade connection: %v", err) return @@ -1188,8 +1189,8 @@ func TestConnection_Bind_JSONCase(t *testing.T) { // Send JSON message jsonData := `{"name": "test", "value": 123}` - err := conn.WriteMessage(websocket.TextMessage, []byte(jsonData)) + err := conn.WriteMessage(websocket.TextMessage, []byte(jsonData)) if err != nil { return // Ignore errors in server handler } @@ -1201,7 +1202,6 @@ func TestConnection_Bind_JSONCase(t *testing.T) { } err = wsConn.Bind(&data) - if err != nil { return // Ignore errors in server handler } @@ -1428,6 +1428,7 @@ func TestConnection_Bind_JSONError(t *testing.T) { // Test with invalid JSON that will cause unmarshaling error var data map[string]any + err = wsConn.Bind(&data) // This should fail with invalid JSON if err == nil { From 9c98190593cb45e812a436cb126754141c70359b Mon Sep 17 00:00:00 2001 From: ZopDev Date: Mon, 6 Oct 2025 13:27:22 +0530 Subject: [PATCH 12/12] Resolved linting error --- pkg/gofr/websocket/websocket_test.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pkg/gofr/websocket/websocket_test.go b/pkg/gofr/websocket/websocket_test.go index 06c21e6f3..7b6c7a58a 100644 --- a/pkg/gofr/websocket/websocket_test.go +++ b/pkg/gofr/websocket/websocket_test.go @@ -1091,8 +1091,8 @@ func TestConnection_ConcurrentWriteMessage(t *testing.T) { defer wg.Done() message := fmt.Sprintf("message %d", i) - err := wsConn.WriteMessage(websocket.TextMessage, []byte(message)) + err := wsConn.WriteMessage(websocket.TextMessage, []byte(message)) if err != nil { t.Errorf("WriteMessage failed: %v", err) } @@ -1158,7 +1158,6 @@ func TestConnection_Bind_StringCase(t *testing.T) { // Send text message err := conn.WriteMessage(websocket.TextMessage, []byte("test message")) - if err != nil { return // Ignore errors in server handler } @@ -1167,7 +1166,6 @@ func TestConnection_Bind_StringCase(t *testing.T) { var data string err = wsConn.Bind(&data) - if err != nil { return // Ignore errors in server handler }