diff --git a/backend/app.py b/backend/app.py index 11c5066..13d7c13 100644 --- a/backend/app.py +++ b/backend/app.py @@ -514,13 +514,16 @@ def handle_start_terminal(data): 'container_id': container_id } + # Capture request.sid before starting thread to avoid context issues + sid = request.sid + # Start a thread to read from the container and send to client def read_output(): sock = exec_instance.output try: while True: # Check if socket is still connected - if request.sid not in active_terminals: + if sid not in active_terminals: break try: @@ -536,20 +539,20 @@ def handle_start_terminal(data): decoded_data = data.decode('latin-1', errors='replace') socketio.emit('output', {'data': decoded_data}, - namespace='/terminal', room=request.sid) + namespace='/terminal', room=sid) except Exception as e: logger.error(f"Error reading from container: {e}") break finally: # Clean up - if request.sid in active_terminals: - del active_terminals[request.sid] + if sid in active_terminals: + del active_terminals[sid] try: sock.close() except: pass socketio.emit('exit', {'code': 0}, - namespace='/terminal', room=request.sid) + namespace='/terminal', room=sid) # Start the output reader thread output_thread = threading.Thread(target=read_output, daemon=True) diff --git a/backend/tests/test_websocket_coverage.py b/backend/tests/test_websocket_coverage.py index 73ac693..8ad0eae 100644 --- a/backend/tests/test_websocket_coverage.py +++ b/backend/tests/test_websocket_coverage.py @@ -377,15 +377,24 @@ class TestWebSocketCoverage: def test_input_with_direct_socket_fallback(self, mock_get_client, socketio_client, auth_token): """Test that input works with direct socket (no _sock attribute)""" import app + import threading mock_client = MagicMock() mock_container = MagicMock() mock_exec_instance = MagicMock() + # Create an event to control when the socket returns empty + stop_event = threading.Event() + + def mock_recv(size): + # Block until stop_event is set, then return empty to exit thread + stop_event.wait(timeout=1.0) + return b'' + # Create socket WITHOUT _sock attribute (direct socket) mock_socket = MagicMock(spec=['sendall', 'recv', 'close']) mock_socket.sendall = MagicMock() - mock_socket.recv = MagicMock(return_value=b'') + mock_socket.recv = MagicMock(side_effect=mock_recv) mock_socket.close = MagicMock() # Ensure it has NO _sock attribute @@ -415,3 +424,7 @@ class TestWebSocketCoverage: # Verify sendall was called on the socket itself (not _sock) mock_socket.sendall.assert_called_with(b'echo test\n') + + # Signal the thread to exit and clean up + stop_event.set() + time.sleep(0.1) diff --git a/backend/tests/test_websocket_integration.py b/backend/tests/test_websocket_integration.py index 8e5a204..21e9af3 100644 --- a/backend/tests/test_websocket_integration.py +++ b/backend/tests/test_websocket_integration.py @@ -57,7 +57,7 @@ class TestContainerSocketBehavior: if not is_simulated: # Only test actual output with real Docker time.sleep(0.2) - output = sock.recv(4096) + output = sock._sock.recv(4096) # Verify we got output without errors assert output is not None