"""Tests for voice note transcription.""" import tempfile from pathlib import Path from unittest.mock import MagicMock, patch import pytest from messaging.transcription import ( MAX_AUDIO_SIZE_BYTES, transcribe_audio, ) def test_transcribe_file_not_found_raises(): """Non-existent file raises FileNotFoundError.""" with pytest.raises(FileNotFoundError, match="not found"): transcribe_audio(Path("/nonexistent/file.ogg"), "audio/ogg") def test_transcribe_file_too_large_raises(): """File exceeding max size raises ValueError.""" with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as f: f.write(b"x" * (MAX_AUDIO_SIZE_BYTES + 1)) path = Path(f.name) try: with pytest.raises(ValueError, match="too large"): transcribe_audio(path, "audio/ogg", whisper_device="auto") finally: path.unlink(missing_ok=True) def test_transcribe_local_success(): """Local backend returns transcribed text.""" with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as f: f.write(b"fake ogg content") path = Path(f.name) try: mock_pipe = MagicMock() mock_pipe.return_value = {"text": "Hello world"} fake_audio = {"array": [0.0], "sampling_rate": 16000} with ( patch("messaging.transcription._load_audio", return_value=fake_audio), patch( "messaging.transcription._get_pipeline", return_value=mock_pipe, ), ): result = transcribe_audio(path, "audio/ogg", whisper_model="base") assert result == "Hello world" mock_pipe.assert_called_once_with( fake_audio, generate_kwargs={"language": "en", "task": "transcribe"} ) finally: path.unlink(missing_ok=True) def test_transcribe_local_empty_segments_returns_no_speech(): """Local backend with no speech returns placeholder.""" with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as f: f.write(b"fake ogg") path = Path(f.name) try: mock_pipe = MagicMock() mock_pipe.return_value = {"text": ""} fake_audio = {"array": [0.0], "sampling_rate": 16000} with ( patch("messaging.transcription._load_audio", return_value=fake_audio), patch( "messaging.transcription._get_pipeline", return_value=mock_pipe, ), ): result = transcribe_audio(path, "audio/ogg", whisper_model="base") assert result == "(no speech detected)" finally: path.unlink(missing_ok=True) def test_transcribe_invalid_device_raises(): """Invalid whisper_device raises ValueError.""" with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as f: f.write(b"fake ogg") path = Path(f.name) try: # Mock settings to return invalid device "auto" mock_settings = MagicMock() mock_settings.whisper_device = "auto" mock_settings.whisper_model = "base" # Patch _load_audio to avoid ImportError from missing librosa # Device validation happens in _get_pipeline before torch import with ( patch("messaging.transcription.get_settings", return_value=mock_settings), patch("messaging.transcription._load_audio"), pytest.raises(ValueError, match="whisper_device must be 'cpu' or 'cuda'"), ): transcribe_audio(path, "audio/ogg", whisper_device="auto") finally: path.unlink(missing_ok=True) def test_transcribe_local_import_error_raises(): """Local backend when voice_local extra not installed raises ImportError.""" with tempfile.NamedTemporaryFile(suffix=".ogg", delete=False) as f: f.write(b"fake ogg") path = Path(f.name) try: with ( patch( "messaging.transcription._get_pipeline", side_effect=ImportError( "Local Whisper requires the voice_local extra. " "Install with: uv sync --extra voice_local" ), ), pytest.raises(ImportError, match="voice_local extra"), ): transcribe_audio(path, "audio/ogg", whisper_device="auto") finally: path.unlink(missing_ok=True)