11"""Unit tests for AsyncRemoteWorkspace class."""
22
33import asyncio
4- import json
54from pathlib import Path
65from unittest .mock import AsyncMock , Mock , patch
76
@@ -104,59 +103,32 @@ def test_generator():
104103
105104
106105@pytest .mark .asyncio
107- async def test_async_execute_command ():
108- """Test execute_command method with websocket mocking."""
106+ @patch (
107+ "openhands.sdk.workspace.remote.async_remote_workspace.AsyncRemoteWorkspace._execute"
108+ )
109+ async def test_async_execute_command (mock_execute ):
110+ """Test execute_command method calls _execute with correct generator."""
109111 workspace = AsyncRemoteWorkspace (
110- host = "http://localhost:8000" , api_key = "test-key" , working_dir = "workspace"
112+ host = "http://localhost:8000" , working_dir = "workspace"
111113 )
112114
113- # Create mock websocket
114- mock_websocket = AsyncMock ()
115- mock_websocket .send = AsyncMock ()
116- # Simulate server responses: BashCommand event followed by BashOutput event
117- mock_websocket .recv = AsyncMock (
118- side_effect = [
119- json .dumps ({"kind" : "BashCommand" , "command" : "echo hello" , "id" : "cmd-1" }),
120- json .dumps (
121- {
122- "kind" : "BashOutput" ,
123- "command_id" : "cmd-1" ,
124- "stdout" : "hello\n " ,
125- "stderr" : "" ,
126- "exit_code" : 0 ,
127- }
128- ),
129- ]
115+ expected_result = CommandResult (
116+ command = "echo hello" ,
117+ exit_code = 0 ,
118+ stdout = "hello\n " ,
119+ stderr = "" ,
120+ timeout_occurred = False ,
130121 )
122+ mock_execute .return_value = expected_result
131123
132- mock_connect = AsyncMock ()
133- mock_connect .__aenter__ .return_value = mock_websocket
134- mock_connect .__aexit__ .return_value = None
135-
136- with patch (
137- "openhands.sdk.workspace.remote.async_remote_workspace.connect" ,
138- return_value = mock_connect ,
139- ) as patched_connect :
140- result = await workspace .execute_command ("echo hello" , cwd = "/tmp" , timeout = 30.0 )
141-
142- assert result .command == "echo hello"
143- assert result .exit_code == 0
144- assert result .stdout == "hello\n "
145- assert result .stderr == ""
146- assert result .timeout_occurred is False
124+ result = await workspace .execute_command ("echo hello" , cwd = "/tmp" , timeout = 30.0 )
147125
148- # Verify connect was called with ws:// scheme (converted from http://)
149- patched_connect .assert_called_once ()
150- ws_url = patched_connect .call_args [0 ][0 ]
151- assert ws_url .startswith ("ws://" )
152- assert "localhost:8000/sockets/bash-events" in ws_url
126+ assert result == expected_result
127+ mock_execute .assert_called_once ()
153128
154- # Verify websocket was called with correct payload
155- mock_websocket .send .assert_called_once ()
156- sent_payload = json .loads (mock_websocket .send .call_args [0 ][0 ])
157- assert sent_payload ["command" ] == "echo hello"
158- assert sent_payload ["timeout" ] == 30
159- assert sent_payload ["cwd" ] == "/tmp"
129+ # Verify the generator was created correctly
130+ generator_arg = mock_execute .call_args [0 ][0 ]
131+ assert hasattr (generator_arg , "__next__" )
160132
161133
162134@pytest .mark .asyncio
@@ -217,56 +189,25 @@ async def test_async_file_download(mock_execute):
217189
218190@pytest .mark .asyncio
219191async def test_async_execute_command_with_path_objects ():
220- """Test execute_command works with Path objects for cwd and https:// to wss:// ."""
192+ """Test execute_command works with Path objects for cwd."""
221193 workspace = AsyncRemoteWorkspace (
222- host = "https://example.com" , api_key = "test-key" , working_dir = "workspace"
223- )
224-
225- # Create mock websocket
226- mock_websocket = AsyncMock ()
227- mock_websocket .send = AsyncMock ()
228- mock_websocket .recv = AsyncMock (
229- side_effect = [
230- json .dumps ({"kind" : "BashCommand" , "command" : "ls" , "id" : "cmd-2" }),
231- json .dumps (
232- {
233- "kind" : "BashOutput" ,
234- "command_id" : "cmd-2" ,
235- "stdout" : "file1.txt\n " ,
236- "stderr" : "" ,
237- "exit_code" : 0 ,
238- }
239- ),
240- ]
194+ host = "http://localhost:8000" , working_dir = "workspace"
241195 )
242196
243- mock_connect = AsyncMock ()
244- mock_connect .__aenter__ .return_value = mock_websocket
245- mock_connect .__aexit__ .return_value = None
197+ with patch .object (workspace , "_execute" ) as mock_execute :
198+ expected_result = CommandResult (
199+ command = "ls" ,
200+ exit_code = 0 ,
201+ stdout = "file1.txt\n " ,
202+ stderr = "" ,
203+ timeout_occurred = False ,
204+ )
205+ mock_execute .return_value = expected_result
246206
247- with patch (
248- "openhands.sdk.workspace.remote.async_remote_workspace.connect" ,
249- return_value = mock_connect ,
250- ) as patched_connect :
251207 result = await workspace .execute_command ("ls" , cwd = Path ("/tmp/test" ))
252208
253- assert result .command == "ls"
254- assert result .exit_code == 0
255- assert result .stdout == "file1.txt\n "
256- assert result .stderr == ""
257- assert result .timeout_occurred is False
258-
259- # Verify connect was called with wss:// scheme (converted from https://)
260- patched_connect .assert_called_once ()
261- ws_url = patched_connect .call_args [0 ][0 ]
262- assert ws_url .startswith ("wss://" )
263- assert "example.com/sockets/bash-events" in ws_url
264-
265- # Verify Path object was converted to string in the payload
266- mock_websocket .send .assert_called_once ()
267- sent_payload = json .loads (mock_websocket .send .call_args [0 ][0 ])
268- assert sent_payload ["command" ] == "ls"
269- assert sent_payload ["cwd" ] == "/tmp/test" # Path is converted to string
209+ assert result == expected_result
210+ mock_execute .assert_called_once ()
270211
271212
272213@pytest .mark .asyncio
@@ -399,33 +340,29 @@ async def test_async_concurrent_operations():
399340 host = "http://localhost:8000" , working_dir = "workspace"
400341 )
401342
402- # Mock different results for different operations
403- command_result = CommandResult (
404- command = "echo test" ,
405- exit_code = 0 ,
406- stdout = "test\n " ,
407- stderr = "" ,
408- timeout_occurred = False ,
409- )
410- upload_result = FileOperationResult (
411- success = True ,
412- source_path = "/local/file1.txt" ,
413- destination_path = "/remote/file1.txt" ,
414- file_size = 50 ,
415- )
416- download_result = FileOperationResult (
417- success = True ,
418- source_path = "/remote/file2.txt" ,
419- destination_path = "/local/file2.txt" ,
420- file_size = 75 ,
421- )
343+ with patch .object (workspace , "_execute" ) as mock_execute :
344+ # Mock different results for different operations
345+ command_result = CommandResult (
346+ command = "echo test" ,
347+ exit_code = 0 ,
348+ stdout = "test\n " ,
349+ stderr = "" ,
350+ timeout_occurred = False ,
351+ )
352+ upload_result = FileOperationResult (
353+ success = True ,
354+ source_path = "/local/file1.txt" ,
355+ destination_path = "/remote/file1.txt" ,
356+ file_size = 50 ,
357+ )
358+ download_result = FileOperationResult (
359+ success = True ,
360+ source_path = "/remote/file2.txt" ,
361+ destination_path = "/local/file2.txt" ,
362+ file_size = 75 ,
363+ )
422364
423- with (
424- patch .object (workspace , "_execute_command" ) as mock_execute_command ,
425- patch .object (workspace , "_execute" ) as mock_execute ,
426- ):
427- mock_execute_command .return_value = command_result
428- mock_execute .side_effect = [upload_result , download_result ]
365+ mock_execute .side_effect = [command_result , upload_result , download_result ]
429366
430367 # Run operations concurrently
431368 tasks = [
@@ -439,5 +376,4 @@ async def test_async_concurrent_operations():
439376 assert results [0 ] == command_result
440377 assert results [1 ] == upload_result
441378 assert results [2 ] == download_result
442- mock_execute_command .assert_called_once ()
443- assert mock_execute .call_count == 2
379+ assert mock_execute .call_count == 3
0 commit comments