From 0664b779120377b3134d111b3f5a9bbe67c0c559 Mon Sep 17 00:00:00 2001 From: adavyas Date: Wed, 8 Apr 2026 23:39:05 -0400 Subject: [PATCH] Handle audio probe timeouts during upload validation --- src/utils/files.py | 4 +++ tests/routes/test_files.py | 37 +++++++++++++++++++++++++++- tests/utils/test_audio_processing.py | 19 ++++++++++++++ 3 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/utils/files.py b/src/utils/files.py index 1b3eae6c..334b25bb 100644 --- a/src/utils/files.py +++ b/src/utils/files.py @@ -264,6 +264,10 @@ async def is_validated_audio_upload(file: UploadFile) -> bool: if str(exc) == "Uploaded audio is invalid or unreadable": return False raise + except FileProcessingError as exc: + if exc.detail == "Audio validation timed out": + return False + raise finally: await file.seek(0) if temp_path is not None: diff --git a/tests/routes/test_files.py b/tests/routes/test_files.py index 71043c01..a4733342 100644 --- a/tests/routes/test_files.py +++ b/tests/routes/test_files.py @@ -16,7 +16,7 @@ from starlette.datastructures import Headers from src import models, schemas from src.config import settings -from src.exceptions import ValidationException +from src.exceptions import FileProcessingError, ValidationException from src.models import Peer, Workspace from src.routers.messages import create_messages_with_file from src.utils.files import ExtractedFileText @@ -802,6 +802,41 @@ async def test_audio_upload_over_generic_limit_keeps_generic_limit_without_trans assert response.status_code == 413 +@pytest.mark.asyncio +async def test_audio_upload_over_generic_limit_keeps_generic_limit_on_probe_timeout( + client: TestClient, + db_session: AsyncSession, + sample_data: tuple[Workspace, Peer], +): + test_workspace, test_peer = sample_data + test_session = await _create_test_session(db_session, test_workspace) + session_name = test_session.name + + original_generic_max = settings.MAX_FILE_SIZE + original_audio_max = settings.AUDIO.MAX_FILE_SIZE_BYTES + settings.MAX_FILE_SIZE = 5 + settings.AUDIO.MAX_FILE_SIZE_BYTES = 10 + try: + file_data = io.BytesIO(b"123456") + files = {"file": ("call.mp3", file_data, "audio/mpeg")} + form_data = {"peer_id": test_peer.name} + + with ( + patch.dict("src.utils.files.CLIENTS", {"openai": object()}, clear=True), + patch( + "src.utils.files.AudioProcessor.probe_audio_duration_seconds_from_path", + side_effect=FileProcessingError("Audio validation timed out"), + ), + ): + url = _get_upload_url(test_workspace.name, session_name) + response = client.post(url, files=files, data=form_data) + finally: + settings.MAX_FILE_SIZE = original_generic_max + settings.AUDIO.MAX_FILE_SIZE_BYTES = original_audio_max + + assert response.status_code == 413 + + @pytest.mark.asyncio async def test_create_messages_with_file_opens_tracked_db_after_file_processing(): background_tasks = BackgroundTasks() diff --git a/tests/utils/test_audio_processing.py b/tests/utils/test_audio_processing.py index 413bad6c..d1bcfacb 100644 --- a/tests/utils/test_audio_processing.py +++ b/tests/utils/test_audio_processing.py @@ -367,3 +367,22 @@ async def test_validated_audio_upload_cleans_up_temp_file_on_write_failure(): mock_unlink.assert_called_once_with(missing_ok=True) assert file.file.tell() == 0 + + +@pytest.mark.asyncio +async def test_validated_audio_upload_returns_false_on_probe_timeout(): + file = UploadFile( + file=io.BytesIO(b"audio-bytes"), + filename="voice.mp3", + headers=Headers({"content-type": "audio/mpeg"}), + ) + + with patch.object( + AudioProcessor, + "probe_audio_duration_seconds_from_path", + side_effect=FileProcessingError("Audio validation timed out"), + ): + is_valid = await file_utils.is_validated_audio_upload(file) + + assert is_valid is False + assert file.file.tell() == 0