Skip to content

Commit 2e2c45a

Browse files
committed
unit test adjustments
1 parent 1f89f6c commit 2e2c45a

9 files changed

Lines changed: 346 additions & 78 deletions

tests/sdk/async_devbox/test_core.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -137,11 +137,9 @@ async def test_shutdown(self, mock_async_client: AsyncMock, devbox_view: MockDev
137137
async def test_suspend(self, mock_async_client: AsyncMock, devbox_view: MockDevboxView) -> None:
138138
"""Test suspend method."""
139139
mock_async_client.devboxes.suspend = AsyncMock(return_value=devbox_view)
140-
polling_config = PollingConfig(timeout_seconds=60.0)
141140

142141
devbox = AsyncDevbox(mock_async_client, "dev_123")
143142
result = await devbox.suspend(
144-
polling_config=polling_config,
145143
extra_headers={"X-Custom": "value"},
146144
extra_query={"param": "value"},
147145
extra_body={"key": "value"},
@@ -152,7 +150,6 @@ async def test_suspend(self, mock_async_client: AsyncMock, devbox_view: MockDevb
152150
assert result == devbox_view
153151
mock_async_client.devboxes.suspend.assert_called_once_with(
154152
"dev_123",
155-
polling_config=polling_config,
156153
extra_headers={"X-Custom": "value"},
157154
extra_query={"param": "value"},
158155
extra_body={"key": "value"},
@@ -164,11 +161,9 @@ async def test_suspend(self, mock_async_client: AsyncMock, devbox_view: MockDevb
164161
async def test_resume(self, mock_async_client: AsyncMock, devbox_view: MockDevboxView) -> None:
165162
"""Test resume method."""
166163
mock_async_client.devboxes.resume = AsyncMock(return_value=devbox_view)
167-
polling_config = PollingConfig(timeout_seconds=60.0)
168164

169165
devbox = AsyncDevbox(mock_async_client, "dev_123")
170166
result = await devbox.resume(
171-
polling_config=polling_config,
172167
extra_headers={"X-Custom": "value"},
173168
extra_query={"param": "value"},
174169
extra_body={"key": "value"},
@@ -179,7 +174,6 @@ async def test_resume(self, mock_async_client: AsyncMock, devbox_view: MockDevbo
179174
assert result == devbox_view
180175
mock_async_client.devboxes.resume.assert_called_once_with(
181176
"dev_123",
182-
polling_config=polling_config,
183177
extra_headers={"X-Custom": "value"},
184178
extra_query={"param": "value"},
185179
extra_body={"key": "value"},

tests/sdk/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class MockExecutionView:
5555
exit_status: int = 0
5656
stdout: str = "output"
5757
stderr: str = ""
58+
stdout_truncated: bool = False
59+
stderr_truncated: bool = False
5860

5961

6062
@dataclass

tests/sdk/test_async_clients.py

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -215,20 +215,6 @@ async def test_create(self, mock_async_client: AsyncMock, object_view: MockObjec
215215
metadata={"key": "value"},
216216
)
217217

218-
@pytest.mark.asyncio
219-
async def test_create_auto_detect_content_type(
220-
self, mock_async_client: AsyncMock, object_view: MockObjectView
221-
) -> None:
222-
"""Test create auto-detects content type."""
223-
mock_async_client.objects.create = AsyncMock(return_value=object_view)
224-
225-
client = AsyncStorageObjectClient(mock_async_client)
226-
obj = await client.create(name="test.txt")
227-
228-
assert isinstance(obj, AsyncStorageObject)
229-
call_kwargs = mock_async_client.objects.create.call_args[1]
230-
assert "content_type" not in call_kwargs
231-
232218
def test_from_id(self, mock_async_client: AsyncMock) -> None:
233219
"""Test from_id method."""
234220
client = AsyncStorageObjectClient(mock_async_client)

tests/sdk/test_async_execution.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,8 @@ async def test_result_needs_polling(self, mock_async_client: AsyncMock) -> None:
159159
exit_status=0,
160160
stdout="output",
161161
stderr="",
162+
stdout_truncated=False,
163+
stderr_truncated=False,
162164
)
163165

164166
mock_async_client.devboxes.wait_for_command = AsyncMock(return_value=completed_execution)
@@ -263,19 +265,3 @@ async def test_kill(self, mock_async_client: AsyncMock, execution_view: MockExec
263265
"exec_123",
264266
devbox_id="dev_123",
265267
)
266-
267-
@pytest.mark.asyncio
268-
async def test_kill_with_process_group(
269-
self, mock_async_client: AsyncMock, execution_view: MockExecutionView
270-
) -> None:
271-
"""Test kill with kill_process_group."""
272-
mock_async_client.devboxes.executions.kill = AsyncMock(return_value=None)
273-
274-
execution = AsyncExecution(mock_async_client, "dev_123", execution_view) # type: ignore[arg-type]
275-
await execution.kill(kill_process_group=True)
276-
277-
mock_async_client.devboxes.executions.kill.assert_awaited_once_with(
278-
"exec_123",
279-
devbox_id="dev_123",
280-
kill_process_group=True,
281-
)

tests/sdk/test_async_execution_result.py

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from __future__ import annotations
44

55
from types import SimpleNamespace
6-
from unittest.mock import AsyncMock
6+
from unittest.mock import Mock, AsyncMock
77

88
import pytest
99

@@ -45,6 +45,8 @@ def test_exit_code_none(self, mock_async_client: AsyncMock) -> None:
4545
exit_status=None,
4646
stdout="",
4747
stderr="",
48+
stdout_truncated=False,
49+
stderr_truncated=False,
4850
)
4951
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
5052
assert result.exit_code is None
@@ -63,6 +65,8 @@ def test_success_false(self, mock_async_client: AsyncMock) -> None:
6365
exit_status=1,
6466
stdout="",
6567
stderr="error",
68+
stdout_truncated=False,
69+
stderr_truncated=False,
6670
)
6771
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
6872
assert result.success is False
@@ -81,6 +85,8 @@ def test_failed_true(self, mock_async_client: AsyncMock) -> None:
8185
exit_status=1,
8286
stdout="",
8387
stderr="error",
88+
stdout_truncated=False,
89+
stderr_truncated=False,
8490
)
8591
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
8692
assert result.failed is True
@@ -94,6 +100,8 @@ def test_failed_none(self, mock_async_client: AsyncMock) -> None:
94100
exit_status=None,
95101
stdout="",
96102
stderr="",
103+
stdout_truncated=False,
104+
stderr_truncated=False,
97105
)
98106
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
99107
assert result.failed is False
@@ -115,6 +123,8 @@ async def test_stdout_empty(self, mock_async_client: AsyncMock) -> None:
115123
exit_status=0,
116124
stdout=None,
117125
stderr="",
126+
stdout_truncated=False,
127+
stderr_truncated=False,
118128
)
119129
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
120130
assert await result.stdout() == ""
@@ -129,6 +139,8 @@ async def test_stderr(self, mock_async_client: AsyncMock) -> None:
129139
exit_status=1,
130140
stdout="",
131141
stderr="error message",
142+
stdout_truncated=False,
143+
stderr_truncated=False,
132144
)
133145
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
134146
assert await result.stderr() == "error message"
@@ -144,3 +156,171 @@ def test_raw_property(self, mock_async_client: AsyncMock, execution_view: MockEx
144156
"""Test raw property."""
145157
result = AsyncExecutionResult(mock_async_client, "dev_123", execution_view) # type: ignore[arg-type]
146158
assert result.raw == execution_view
159+
160+
@pytest.mark.asyncio
161+
async def test_stdout_with_truncation_and_streaming(
162+
self, mock_async_client: AsyncMock, mock_async_stream: AsyncMock
163+
) -> None:
164+
"""Test stdout streams full output when truncated."""
165+
from types import SimpleNamespace as SN
166+
167+
# Mock chunk data
168+
async def mock_iter():
169+
yield SN(output="line1\n")
170+
yield SN(output="line2\n")
171+
yield SN(output="line3\n")
172+
173+
mock_async_stream.__aiter__ = Mock(return_value=mock_iter())
174+
175+
# Setup client mock to return our stream
176+
mock_async_client.devboxes.executions.stream_stdout_updates = AsyncMock(return_value=mock_async_stream)
177+
178+
execution = SimpleNamespace(
179+
execution_id="exec_123",
180+
devbox_id="dev_123",
181+
status="completed",
182+
exit_status=0,
183+
stdout="partial",
184+
stderr="",
185+
stdout_truncated=True,
186+
stderr_truncated=False,
187+
)
188+
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
189+
190+
# Should stream full output
191+
output = await result.stdout()
192+
assert output == "line1\nline2\nline3\n"
193+
mock_async_client.devboxes.executions.stream_stdout_updates.assert_called_once_with(
194+
"exec_123", devbox_id="dev_123"
195+
)
196+
197+
@pytest.mark.asyncio
198+
async def test_stderr_with_truncation_and_streaming(
199+
self, mock_async_client: AsyncMock, mock_async_stream: AsyncMock
200+
) -> None:
201+
"""Test stderr streams full output when truncated."""
202+
from types import SimpleNamespace as SN
203+
204+
# Mock chunk data
205+
async def mock_iter():
206+
yield SN(output="error1\n")
207+
yield SN(output="error2\n")
208+
209+
mock_async_stream.__aiter__ = Mock(return_value=mock_iter())
210+
211+
# Setup client mock to return our stream
212+
mock_async_client.devboxes.executions.stream_stderr_updates = AsyncMock(return_value=mock_async_stream)
213+
214+
execution = SimpleNamespace(
215+
execution_id="exec_123",
216+
devbox_id="dev_123",
217+
status="completed",
218+
exit_status=0,
219+
stdout="",
220+
stderr="partial error",
221+
stdout_truncated=False,
222+
stderr_truncated=True,
223+
)
224+
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
225+
226+
# Should stream full output
227+
output = await result.stderr()
228+
assert output == "error1\nerror2\n"
229+
mock_async_client.devboxes.executions.stream_stderr_updates.assert_called_once_with(
230+
"exec_123", devbox_id="dev_123"
231+
)
232+
233+
@pytest.mark.asyncio
234+
async def test_stdout_with_num_lines_when_truncated(
235+
self, mock_async_client: AsyncMock, mock_async_stream: AsyncMock
236+
) -> None:
237+
"""Test stdout with num_lines parameter when truncated."""
238+
from types import SimpleNamespace as SN
239+
240+
# Mock chunk data with many lines
241+
async def mock_iter():
242+
yield SN(output="line1\nline2\nline3\n")
243+
yield SN(output="line4\nline5\n")
244+
245+
mock_async_stream.__aiter__ = Mock(return_value=mock_iter())
246+
247+
# Setup client mock to return our stream
248+
mock_async_client.devboxes.executions.stream_stdout_updates = AsyncMock(return_value=mock_async_stream)
249+
250+
execution = SimpleNamespace(
251+
execution_id="exec_123",
252+
devbox_id="dev_123",
253+
status="completed",
254+
exit_status=0,
255+
stdout="line1\n",
256+
stderr="",
257+
stdout_truncated=True,
258+
stderr_truncated=False,
259+
)
260+
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
261+
262+
# Should stream and return last 2 lines
263+
output = await result.stdout(num_lines=2)
264+
assert output == "line4\nline5"
265+
266+
@pytest.mark.asyncio
267+
async def test_stdout_no_streaming_when_not_truncated(self, mock_async_client: AsyncMock) -> None:
268+
"""Test stdout doesn't stream when not truncated."""
269+
execution = SimpleNamespace(
270+
execution_id="exec_123",
271+
devbox_id="dev_123",
272+
status="completed",
273+
exit_status=0,
274+
stdout="complete output",
275+
stderr="",
276+
stdout_truncated=False,
277+
stderr_truncated=False,
278+
)
279+
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
280+
281+
# Should return existing output without streaming
282+
output = await result.stdout()
283+
assert output == "complete output"
284+
285+
@pytest.mark.asyncio
286+
async def test_stdout_with_num_lines_no_truncation(self, mock_async_client: AsyncMock) -> None:
287+
"""Test stdout with num_lines when not truncated."""
288+
execution = SimpleNamespace(
289+
execution_id="exec_123",
290+
devbox_id="dev_123",
291+
status="completed",
292+
exit_status=0,
293+
stdout="line1\nline2\nline3\nline4\nline5",
294+
stderr="",
295+
stdout_truncated=False,
296+
stderr_truncated=False,
297+
)
298+
result = AsyncExecutionResult(mock_async_client, "dev_123", execution) # type: ignore[arg-type]
299+
300+
# Should return last 2 lines without streaming
301+
output = await result.stdout(num_lines=2)
302+
assert output == "line4\nline5"
303+
304+
def test_count_non_empty_lines(self, mock_async_client: AsyncMock, execution_view: MockExecutionView) -> None:
305+
"""Test the _count_non_empty_lines helper method."""
306+
result = AsyncExecutionResult(mock_async_client, "dev_123", execution_view) # type: ignore[arg-type]
307+
308+
# Test various input strings
309+
assert result._count_non_empty_lines("") == 0
310+
assert result._count_non_empty_lines("single") == 1
311+
assert result._count_non_empty_lines("line1\nline2") == 2
312+
assert result._count_non_empty_lines("line1\nline2\n") == 2
313+
assert result._count_non_empty_lines("line1\n\nline3") == 2 # Empty line in middle
314+
assert result._count_non_empty_lines("line1\nline2\nline3\n\n") == 3 # Trailing newlines
315+
316+
def test_get_last_n_lines(self, mock_async_client: AsyncMock, execution_view: MockExecutionView) -> None:
317+
"""Test the _get_last_n_lines helper method."""
318+
result = AsyncExecutionResult(mock_async_client, "dev_123", execution_view) # type: ignore[arg-type]
319+
320+
# Test various scenarios
321+
assert result._get_last_n_lines("", 5) == ""
322+
assert result._get_last_n_lines("single", 1) == "single"
323+
assert result._get_last_n_lines("line1\nline2\nline3", 2) == "line2\nline3"
324+
assert result._get_last_n_lines("line1\nline2\nline3\n", 2) == "line2\nline3"
325+
assert result._get_last_n_lines("line1\nline2", 10) == "line1\nline2" # Request more than available
326+
assert result._get_last_n_lines("line1\nline2", 0) == "" # Zero lines

tests/sdk/test_clients.py

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -204,17 +204,6 @@ def test_create(self, mock_client: Mock, object_view: MockObjectView) -> None:
204204
assert obj.upload_url == "https://upload.example.com/obj_123"
205205
mock_client.objects.create.assert_called_once()
206206

207-
def test_create_auto_detect_content_type(self, mock_client: Mock, object_view: MockObjectView) -> None:
208-
"""Test create auto-detects content type."""
209-
mock_client.objects.create.return_value = object_view
210-
211-
client = StorageObjectClient(mock_client)
212-
obj = client.create(name="test.txt")
213-
214-
assert isinstance(obj, StorageObject)
215-
call_kwargs = mock_client.objects.create.call_args[1]
216-
assert "content_type" not in call_kwargs
217-
218207
def test_from_id(self, mock_client: Mock) -> None:
219208
"""Test from_id method."""
220209
client = StorageObjectClient(mock_client)

tests/sdk/test_execution.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ def test_result_needs_polling(self, mock_client: Mock) -> None:
137137
exit_status=0,
138138
stdout="output",
139139
stderr="",
140+
stdout_truncated=False,
141+
stderr_truncated=False,
140142
)
141143

142144
mock_client.devboxes = Mock()
@@ -240,16 +242,3 @@ def test_kill(self, mock_client: Mock, execution_view: MockExecutionView) -> Non
240242
"exec_123",
241243
devbox_id="dev_123",
242244
)
243-
244-
def test_kill_with_process_group(self, mock_client: Mock, execution_view: MockExecutionView) -> None:
245-
"""Test kill with kill_process_group."""
246-
mock_client.devboxes.executions.kill.return_value = None
247-
248-
execution = Execution(mock_client, "dev_123", execution_view) # type: ignore[arg-type]
249-
execution.kill(kill_process_group=True)
250-
251-
mock_client.devboxes.executions.kill.assert_called_once_with(
252-
"exec_123",
253-
devbox_id="dev_123",
254-
kill_process_group=True,
255-
)

0 commit comments

Comments
 (0)