Skip to content

Commit f2c03d8

Browse files
committed
add tests
1 parent 7675a82 commit f2c03d8

File tree

4 files changed

+356
-0
lines changed

4 files changed

+356
-0
lines changed

tests/sdk/test_async_clients.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,153 @@ async def test_upload_from_file_missing_path(self, mock_async_client: AsyncMock,
327327
with pytest.raises(OSError, match="Failed to read file"):
328328
await client.upload_from_file(missing_file)
329329

330+
@pytest.mark.asyncio
331+
async def test_upload_from_dir(
332+
self, mock_async_client: AsyncMock, object_view: MockObjectView, tmp_path: Path
333+
) -> None:
334+
"""Test upload_from_dir method."""
335+
mock_async_client.objects.create = AsyncMock(return_value=object_view)
336+
mock_async_client.objects.complete = AsyncMock(return_value=object_view)
337+
338+
# Create a temporary directory with some files
339+
test_dir = tmp_path / "test_directory"
340+
test_dir.mkdir()
341+
(test_dir / "file1.txt").write_text("content1")
342+
(test_dir / "file2.txt").write_text("content2")
343+
subdir = test_dir / "subdir"
344+
subdir.mkdir()
345+
(subdir / "file3.txt").write_text("content3")
346+
347+
http_client = AsyncMock()
348+
mock_response = create_mock_httpx_response()
349+
http_client.put = AsyncMock(return_value=mock_response)
350+
mock_async_client._client = http_client
351+
352+
client = AsyncStorageObjectOps(mock_async_client)
353+
obj = await client.upload_from_dir(test_dir, name="archive.tar.gz", metadata={"key": "value"})
354+
355+
assert isinstance(obj, AsyncStorageObject)
356+
assert obj.id == "obj_123"
357+
mock_async_client.objects.create.assert_awaited_once_with(
358+
name="archive.tar.gz",
359+
content_type="tgz",
360+
metadata={"key": "value"},
361+
ttl_ms=None,
362+
)
363+
# Verify that put was called with tarball content
364+
http_client.put.assert_awaited_once()
365+
call_args = http_client.put.call_args
366+
assert call_args[0][0] == object_view.upload_url
367+
# Verify it's a BytesIO object
368+
uploaded_content = call_args[1]["content"]
369+
assert hasattr(uploaded_content, "read")
370+
mock_async_client.objects.complete.assert_awaited_once()
371+
372+
@pytest.mark.asyncio
373+
async def test_upload_from_dir_default_name(
374+
self, mock_async_client: AsyncMock, object_view: MockObjectView, tmp_path: Path
375+
) -> None:
376+
"""Test upload_from_dir uses directory name by default."""
377+
mock_async_client.objects.create = AsyncMock(return_value=object_view)
378+
mock_async_client.objects.complete = AsyncMock(return_value=object_view)
379+
380+
test_dir = tmp_path / "my_folder"
381+
test_dir.mkdir()
382+
(test_dir / "file.txt").write_text("content")
383+
384+
http_client = AsyncMock()
385+
mock_response = create_mock_httpx_response()
386+
http_client.put = AsyncMock(return_value=mock_response)
387+
mock_async_client._client = http_client
388+
389+
client = AsyncStorageObjectOps(mock_async_client)
390+
obj = await client.upload_from_dir(test_dir)
391+
392+
assert isinstance(obj, AsyncStorageObject)
393+
# Name should be directory name + .tar.gz
394+
mock_async_client.objects.create.assert_awaited_once()
395+
call_args = mock_async_client.objects.create.call_args
396+
assert call_args[1]["name"] == "my_folder.tar.gz"
397+
assert call_args[1]["content_type"] == "tgz"
398+
399+
@pytest.mark.asyncio
400+
async def test_upload_from_dir_with_ttl(
401+
self, mock_async_client: AsyncMock, object_view: MockObjectView, tmp_path: Path
402+
) -> None:
403+
"""Test upload_from_dir with TTL."""
404+
from datetime import timedelta
405+
406+
mock_async_client.objects.create = AsyncMock(return_value=object_view)
407+
mock_async_client.objects.complete = AsyncMock(return_value=object_view)
408+
409+
test_dir = tmp_path / "temp_dir"
410+
test_dir.mkdir()
411+
(test_dir / "file.txt").write_text("temporary content")
412+
413+
http_client = AsyncMock()
414+
mock_response = create_mock_httpx_response()
415+
http_client.put = AsyncMock(return_value=mock_response)
416+
mock_async_client._client = http_client
417+
418+
client = AsyncStorageObjectOps(mock_async_client)
419+
obj = await client.upload_from_dir(test_dir, ttl=timedelta(hours=2))
420+
421+
assert isinstance(obj, AsyncStorageObject)
422+
mock_async_client.objects.create.assert_awaited_once()
423+
call_args = mock_async_client.objects.create.call_args
424+
# 2 hours = 7200 seconds = 7200000 milliseconds
425+
assert call_args[1]["ttl_ms"] == 7200000
426+
427+
@pytest.mark.asyncio
428+
async def test_upload_from_dir_empty_directory(
429+
self, mock_async_client: AsyncMock, object_view: MockObjectView, tmp_path: Path
430+
) -> None:
431+
"""Test upload_from_dir with empty directory."""
432+
mock_async_client.objects.create = AsyncMock(return_value=object_view)
433+
mock_async_client.objects.complete = AsyncMock(return_value=object_view)
434+
435+
test_dir = tmp_path / "empty_dir"
436+
test_dir.mkdir()
437+
438+
http_client = AsyncMock()
439+
mock_response = create_mock_httpx_response()
440+
http_client.put = AsyncMock(return_value=mock_response)
441+
mock_async_client._client = http_client
442+
443+
client = AsyncStorageObjectOps(mock_async_client)
444+
obj = await client.upload_from_dir(test_dir)
445+
446+
assert isinstance(obj, AsyncStorageObject)
447+
assert obj.id == "obj_123"
448+
mock_async_client.objects.create.assert_awaited_once()
449+
http_client.put.assert_awaited_once()
450+
mock_async_client.objects.complete.assert_awaited_once()
451+
452+
@pytest.mark.asyncio
453+
async def test_upload_from_dir_with_string_path(
454+
self, mock_async_client: AsyncMock, object_view: MockObjectView, tmp_path: Path
455+
) -> None:
456+
"""Test upload_from_dir with string path instead of Path object."""
457+
mock_async_client.objects.create = AsyncMock(return_value=object_view)
458+
mock_async_client.objects.complete = AsyncMock(return_value=object_view)
459+
460+
test_dir = tmp_path / "string_path_dir"
461+
test_dir.mkdir()
462+
(test_dir / "file.txt").write_text("content")
463+
464+
http_client = AsyncMock()
465+
mock_response = create_mock_httpx_response()
466+
http_client.put = AsyncMock(return_value=mock_response)
467+
mock_async_client._client = http_client
468+
469+
client = AsyncStorageObjectOps(mock_async_client)
470+
# Pass string path instead of Path object
471+
obj = await client.upload_from_dir(str(test_dir))
472+
473+
assert isinstance(obj, AsyncStorageObject)
474+
assert obj.id == "obj_123"
475+
mock_async_client.objects.create.assert_awaited_once()
476+
330477

331478
class TestAsyncRunloopSDK:
332479
"""Tests for AsyncRunloopSDK class."""

tests/sdk/test_clients.py

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,137 @@ def test_upload_from_file_missing_path(self, mock_client: Mock, tmp_path: Path)
306306
with pytest.raises(OSError, match="Failed to read file"):
307307
client.upload_from_file(missing_file)
308308

309+
def test_upload_from_dir(self, mock_client: Mock, object_view: MockObjectView, tmp_path: Path) -> None:
310+
"""Test upload_from_dir method."""
311+
mock_client.objects.create.return_value = object_view
312+
313+
# Create a temporary directory with some files
314+
test_dir = tmp_path / "test_directory"
315+
test_dir.mkdir()
316+
(test_dir / "file1.txt").write_text("content1")
317+
(test_dir / "file2.txt").write_text("content2")
318+
subdir = test_dir / "subdir"
319+
subdir.mkdir()
320+
(subdir / "file3.txt").write_text("content3")
321+
322+
http_client = Mock()
323+
mock_response = create_mock_httpx_response()
324+
http_client.put.return_value = mock_response
325+
mock_client._client = http_client
326+
327+
client = StorageObjectOps(mock_client)
328+
obj = client.upload_from_dir(test_dir, name="archive.tar.gz", metadata={"key": "value"})
329+
330+
assert isinstance(obj, StorageObject)
331+
assert obj.id == "obj_123"
332+
mock_client.objects.create.assert_called_once_with(
333+
name="archive.tar.gz",
334+
content_type="tgz",
335+
metadata={"key": "value"},
336+
ttl_ms=None,
337+
)
338+
# Verify that put was called with tarball content
339+
http_client.put.assert_called_once()
340+
call_args = http_client.put.call_args
341+
assert call_args[0][0] == object_view.upload_url
342+
# Verify it's a BytesIO object
343+
uploaded_content = call_args[1]["content"]
344+
assert hasattr(uploaded_content, "read")
345+
mock_client.objects.complete.assert_called_once()
346+
347+
def test_upload_from_dir_default_name(self, mock_client: Mock, object_view: MockObjectView, tmp_path: Path) -> None:
348+
"""Test upload_from_dir uses directory name by default."""
349+
mock_client.objects.create.return_value = object_view
350+
351+
test_dir = tmp_path / "my_folder"
352+
test_dir.mkdir()
353+
(test_dir / "file.txt").write_text("content")
354+
355+
http_client = Mock()
356+
mock_response = create_mock_httpx_response()
357+
http_client.put.return_value = mock_response
358+
mock_client._client = http_client
359+
360+
client = StorageObjectOps(mock_client)
361+
obj = client.upload_from_dir(test_dir)
362+
363+
assert isinstance(obj, StorageObject)
364+
# Name should be directory name + .tar.gz
365+
mock_client.objects.create.assert_called_once()
366+
call_args = mock_client.objects.create.call_args
367+
assert call_args[1]["name"] == "my_folder.tar.gz"
368+
assert call_args[1]["content_type"] == "tgz"
369+
370+
def test_upload_from_dir_with_ttl(self, mock_client: Mock, object_view: MockObjectView, tmp_path: Path) -> None:
371+
"""Test upload_from_dir with TTL."""
372+
from datetime import timedelta
373+
374+
mock_client.objects.create.return_value = object_view
375+
376+
test_dir = tmp_path / "temp_dir"
377+
test_dir.mkdir()
378+
(test_dir / "file.txt").write_text("temporary content")
379+
380+
http_client = Mock()
381+
mock_response = create_mock_httpx_response()
382+
http_client.put.return_value = mock_response
383+
mock_client._client = http_client
384+
385+
client = StorageObjectOps(mock_client)
386+
obj = client.upload_from_dir(test_dir, ttl=timedelta(hours=2))
387+
388+
assert isinstance(obj, StorageObject)
389+
mock_client.objects.create.assert_called_once()
390+
call_args = mock_client.objects.create.call_args
391+
# 2 hours = 7200 seconds = 7200000 milliseconds
392+
assert call_args[1]["ttl_ms"] == 7200000
393+
394+
def test_upload_from_dir_empty_directory(
395+
self, mock_client: Mock, object_view: MockObjectView, tmp_path: Path
396+
) -> None:
397+
"""Test upload_from_dir with empty directory."""
398+
mock_client.objects.create.return_value = object_view
399+
400+
test_dir = tmp_path / "empty_dir"
401+
test_dir.mkdir()
402+
403+
http_client = Mock()
404+
mock_response = create_mock_httpx_response()
405+
http_client.put.return_value = mock_response
406+
mock_client._client = http_client
407+
408+
client = StorageObjectOps(mock_client)
409+
obj = client.upload_from_dir(test_dir)
410+
411+
assert isinstance(obj, StorageObject)
412+
assert obj.id == "obj_123"
413+
mock_client.objects.create.assert_called_once()
414+
http_client.put.assert_called_once()
415+
mock_client.objects.complete.assert_called_once()
416+
417+
def test_upload_from_dir_with_string_path(
418+
self, mock_client: Mock, object_view: MockObjectView, tmp_path: Path
419+
) -> None:
420+
"""Test upload_from_dir with string path instead of Path object."""
421+
mock_client.objects.create.return_value = object_view
422+
423+
test_dir = tmp_path / "string_path_dir"
424+
test_dir.mkdir()
425+
(test_dir / "file.txt").write_text("content")
426+
427+
http_client = Mock()
428+
mock_response = create_mock_httpx_response()
429+
http_client.put.return_value = mock_response
430+
mock_client._client = http_client
431+
432+
client = StorageObjectOps(mock_client)
433+
# Pass string path instead of Path object
434+
obj = client.upload_from_dir(str(test_dir))
435+
436+
assert isinstance(obj, StorageObject)
437+
assert obj.id == "obj_123"
438+
mock_client.objects.create.assert_called_once()
439+
309440

310441
class TestRunloopSDK:
311442
"""Tests for RunloopSDK class."""

tests/smoketests/sdk/test_async_storage_object.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,45 @@ async def test_upload_from_file(self, async_sdk_client: AsyncRunloopSDK) -> None
157157
finally:
158158
Path(tmp_path).unlink(missing_ok=True)
159159

160+
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
161+
async def test_upload_from_dir(self, async_sdk_client: AsyncRunloopSDK) -> None:
162+
"""Test uploading from directory as tarball."""
163+
# Create temporary directory with files
164+
with tempfile.TemporaryDirectory() as tmp_dir:
165+
tmp_path = Path(tmp_dir)
166+
(tmp_path / "file1.txt").write_text("Async Content 1")
167+
(tmp_path / "file2.txt").write_text("Async Content 2")
168+
subdir = tmp_path / "subdir"
169+
subdir.mkdir()
170+
(subdir / "file3.txt").write_text("Async Content 3")
171+
172+
obj = await async_sdk_client.storage_object.upload_from_dir(
173+
tmp_path,
174+
unique_name("sdk-async-dir-upload"),
175+
metadata={"source": "upload_from_dir"},
176+
)
177+
178+
try:
179+
assert obj.id is not None
180+
181+
# Verify it's a tarball
182+
info = await obj.refresh()
183+
assert info.content_type == "tgz"
184+
185+
# Download and verify tarball can be extracted
186+
import io
187+
import tarfile
188+
189+
tarball_bytes = await obj.download_as_bytes(duration_seconds=120)
190+
with tarfile.open(fileobj=io.BytesIO(tarball_bytes), mode="r:gz") as tar:
191+
# Verify files exist in tarball
192+
names = tar.getnames()
193+
assert any("file1.txt" in name for name in names)
194+
assert any("file2.txt" in name for name in names)
195+
assert any("file3.txt" in name for name in names)
196+
finally:
197+
await obj.delete()
198+
160199

161200
class TestAsyncStorageObjectDownloadMethods:
162201
"""Test async storage object download methods."""

tests/smoketests/sdk/test_storage_object.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,45 @@ def test_upload_from_file(self, sdk_client: RunloopSDK) -> None:
157157
finally:
158158
Path(tmp_path).unlink(missing_ok=True)
159159

160+
@pytest.mark.timeout(THIRTY_SECOND_TIMEOUT)
161+
def test_upload_from_dir(self, sdk_client: RunloopSDK) -> None:
162+
"""Test uploading from directory as tarball."""
163+
# Create temporary directory with files
164+
with tempfile.TemporaryDirectory() as tmp_dir:
165+
tmp_path = Path(tmp_dir)
166+
(tmp_path / "file1.txt").write_text("Content 1")
167+
(tmp_path / "file2.txt").write_text("Content 2")
168+
subdir = tmp_path / "subdir"
169+
subdir.mkdir()
170+
(subdir / "file3.txt").write_text("Content 3")
171+
172+
obj = sdk_client.storage_object.upload_from_dir(
173+
tmp_path,
174+
unique_name("sdk-dir-upload"),
175+
metadata={"source": "upload_from_dir"},
176+
)
177+
178+
try:
179+
assert obj.id is not None
180+
181+
# Verify it's a tarball
182+
info = obj.refresh()
183+
assert info.content_type == "tgz"
184+
185+
# Download and verify tarball can be extracted
186+
import io
187+
import tarfile
188+
189+
tarball_bytes = obj.download_as_bytes(duration_seconds=120)
190+
with tarfile.open(fileobj=io.BytesIO(tarball_bytes), mode="r:gz") as tar:
191+
# Verify files exist in tarball
192+
names = tar.getnames()
193+
assert any("file1.txt" in name for name in names)
194+
assert any("file2.txt" in name for name in names)
195+
assert any("file3.txt" in name for name in names)
196+
finally:
197+
obj.delete()
198+
160199

161200
class TestStorageObjectDownloadMethods:
162201
"""Test storage object download methods."""

0 commit comments

Comments
 (0)