@@ -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
331478class TestAsyncRunloopSDK :
332479 """Tests for AsyncRunloopSDK class."""
0 commit comments