diff --git a/python/lib/sift_client/_internal/low_level_wrappers/upload.py b/python/lib/sift_client/_internal/low_level_wrappers/upload.py index e1c9c6777..4b9e420ef 100644 --- a/python/lib/sift_client/_internal/low_level_wrappers/upload.py +++ b/python/lib/sift_client/_internal/low_level_wrappers/upload.py @@ -93,7 +93,11 @@ async def upload_attachment( file_name, mimetype, content_encoding = self._mime_and_content_type_from_path(posix_path) if not mimetype: - raise ValueError(f"The MIME-type of '{posix_path}' could not be computed.") + extension = posix_path.suffix + if extension: + mimetype = f"application/x-{extension.lstrip('.')}" + else: + mimetype = "application/octet-stream" # fallback to generic 'binary data' MIME type # Run the synchronous file upload in a thread pool to avoid blocking the event loop loop = asyncio.get_event_loop() diff --git a/python/lib/sift_client/_tests/_internal/low_level_wrappers/test_upload.py b/python/lib/sift_client/_tests/_internal/low_level_wrappers/test_upload.py index d40fd9aec..c9b511174 100644 --- a/python/lib/sift_client/_tests/_internal/low_level_wrappers/test_upload.py +++ b/python/lib/sift_client/_tests/_internal/low_level_wrappers/test_upload.py @@ -1,16 +1,62 @@ """Tests for UploadLowLevelClient functionality.""" from pathlib import Path +from unittest.mock import patch + +import pytest from sift_client._internal.low_level_wrappers.upload import UploadLowLevelClient -class TestUploadLowLevelClient: - class TestMimeAndContentTypeFromPath: - def test_parquet_file_extension(self): - _, mime, _ = UploadLowLevelClient._mime_and_content_type_from_path(Path("data.parquet")) - assert mime == "application/vnd.apache.parquet" +class TestUploadAttachment: + @pytest.mark.asyncio + async def test_known_mime_type(self, tmp_path): + test_file = tmp_path / "video.mp4" + test_file.write_bytes(b"fake data") + + client = UploadLowLevelClient.__new__(UploadLowLevelClient) + + with patch.object(client, "_upload_file_sync", return_value="remote-file-123") as mock: + await client.upload_attachment(path=test_file, entity_id="e1", entity_type="runs") + + _, _, mimetype, *_ = mock.call_args[0] + assert mimetype == "video/mp4" + + @pytest.mark.asyncio + async def test_unknown_extension_falls_back_to_application_x_ext(self, tmp_path): + test_file = tmp_path / "data.pcapng" + test_file.write_bytes(b"fake data") + + client = UploadLowLevelClient.__new__(UploadLowLevelClient) + + with patch.object(client, "_upload_file_sync", return_value="remote-file-123") as mock: + await client.upload_attachment(path=test_file, entity_id="e1", entity_type="runs") + + _, _, mimetype, *_ = mock.call_args[0] + assert mimetype == "application/x-pcapng" + + @pytest.mark.asyncio + async def test_no_extension_falls_back_to_octet_stream(self, tmp_path): + test_file = tmp_path / "README" + test_file.write_bytes(b"fake data") + + client = UploadLowLevelClient.__new__(UploadLowLevelClient) + + with patch.object(client, "_upload_file_sync", return_value="remote-file-123") as mock: + await client.upload_attachment(path=test_file, entity_id="e1", entity_type="runs") + + _, _, mimetype, *_ = mock.call_args[0] + assert mimetype == "application/octet-stream" + + @pytest.mark.asyncio + async def test_file_name_preserved(self, tmp_path): + test_file = tmp_path / "my_file.pcapng" + test_file.write_bytes(b"fake data") + + client = UploadLowLevelClient.__new__(UploadLowLevelClient) + + with patch.object(client, "_upload_file_sync", return_value="remote-file-123") as mock: + await client.upload_attachment(path=test_file, entity_id="e1", entity_type="runs") - def test_pqt_file_extension(self): - _, mime, _ = UploadLowLevelClient._mime_and_content_type_from_path(Path("data.pqt")) - assert mime == "application/vnd.apache.parquet" + file_name, *_ = mock.call_args[0] + assert file_name == Path(test_file)