audio streaming app plyr.fm
at main 186 lines 6.2 kB view raw
1"""tests for transcoder client.""" 2 3import tempfile 4from pathlib import Path 5from unittest.mock import AsyncMock, Mock, patch 6 7import httpx 8import pytest 9 10from backend._internal.clients.transcoder import ( 11 TranscoderClient, 12 get_transcoder_client, 13) 14 15 16@pytest.fixture 17def transcoder_client() -> TranscoderClient: 18 """test transcoder client with mock settings.""" 19 return TranscoderClient( 20 service_url="https://test-transcoder.example.com", 21 auth_token="test-token", 22 timeout_seconds=30, 23 target_format="mp3", 24 ) 25 26 27@pytest.fixture 28def temp_audio_file() -> Path: 29 """create a temporary audio file for testing.""" 30 with tempfile.NamedTemporaryFile(mode="wb", suffix=".aiff", delete=False) as f: 31 # write some fake audio data 32 f.write(b"FORM" + b"\x00" * 100) 33 return Path(f.name) 34 35 36async def test_transcoder_client_success( 37 transcoder_client: TranscoderClient, temp_audio_file: Path 38) -> None: 39 """test TranscoderClient.transcode_file() with successful response.""" 40 mock_response = Mock() 41 mock_response.content = b"transcoded mp3 data" 42 mock_response.headers = {"content-type": "audio/mpeg"} 43 mock_response.raise_for_status.return_value = None 44 45 with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post: 46 mock_post.return_value = mock_response 47 48 result = await transcoder_client.transcode_file(temp_audio_file, "aiff") 49 50 assert result.success is True 51 assert result.data == b"transcoded mp3 data" 52 assert result.content_type == "audio/mpeg" 53 assert result.error is None 54 mock_post.assert_called_once() 55 56 # verify the call included auth header 57 call_kwargs = mock_post.call_args.kwargs 58 assert call_kwargs["headers"] == {"X-Transcoder-Key": "test-token"} 59 assert call_kwargs["params"] == {"target": "mp3"} 60 61 # cleanup 62 temp_audio_file.unlink(missing_ok=True) 63 64 65async def test_transcoder_client_timeout( 66 transcoder_client: TranscoderClient, temp_audio_file: Path 67) -> None: 68 """test TranscoderClient.transcode_file() timeout handling.""" 69 with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post: 70 mock_post.side_effect = httpx.TimeoutException("timeout") 71 72 result = await transcoder_client.transcode_file(temp_audio_file, "aiff") 73 74 assert result.success is False 75 assert result.data is None 76 assert result.error is not None 77 assert "timed out" in result.error 78 79 # cleanup 80 temp_audio_file.unlink(missing_ok=True) 81 82 83async def test_transcoder_client_http_error( 84 transcoder_client: TranscoderClient, temp_audio_file: Path 85) -> None: 86 """test TranscoderClient.transcode_file() HTTP error handling.""" 87 mock_response = Mock() 88 mock_response.status_code = 500 89 mock_response.text = "Internal Server Error" 90 91 with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post: 92 mock_post.side_effect = httpx.HTTPStatusError( 93 "Server Error", 94 request=Mock(), 95 response=mock_response, 96 ) 97 98 result = await transcoder_client.transcode_file(temp_audio_file, "aiff") 99 100 assert result.success is False 101 assert result.data is None 102 assert result.error is not None 103 assert "500" in result.error 104 assert "Internal Server Error" in result.error 105 106 # cleanup 107 temp_audio_file.unlink(missing_ok=True) 108 109 110async def test_transcoder_client_unexpected_error( 111 transcoder_client: TranscoderClient, temp_audio_file: Path 112) -> None: 113 """test TranscoderClient.transcode_file() unexpected error handling.""" 114 with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post: 115 mock_post.side_effect = RuntimeError("unexpected error") 116 117 result = await transcoder_client.transcode_file(temp_audio_file, "aiff") 118 119 assert result.success is False 120 assert result.data is None 121 assert result.error is not None 122 assert "unexpected" in result.error.lower() 123 124 # cleanup 125 temp_audio_file.unlink(missing_ok=True) 126 127 128async def test_transcoder_client_custom_target_format( 129 transcoder_client: TranscoderClient, temp_audio_file: Path 130) -> None: 131 """test TranscoderClient.transcode_file() with custom target format.""" 132 mock_response = Mock() 133 mock_response.content = b"transcoded aac data" 134 mock_response.headers = {"content-type": "audio/aac"} 135 mock_response.raise_for_status.return_value = None 136 137 with patch("httpx.AsyncClient.post", new_callable=AsyncMock) as mock_post: 138 mock_post.return_value = mock_response 139 140 result = await transcoder_client.transcode_file( 141 temp_audio_file, "flac", target_format="aac" 142 ) 143 144 assert result.success is True 145 call_kwargs = mock_post.call_args.kwargs 146 assert call_kwargs["params"] == {"target": "aac"} 147 148 # cleanup 149 temp_audio_file.unlink(missing_ok=True) 150 151 152def test_transcoder_client_from_settings() -> None: 153 """test TranscoderClient.from_settings() creates client correctly.""" 154 with patch("backend._internal.clients.transcoder.settings") as mock_settings: 155 mock_settings.transcoder.service_url = "https://transcoder.example.com" 156 mock_settings.transcoder.auth_token = "secret-token" 157 mock_settings.transcoder.timeout_seconds = 120 158 mock_settings.transcoder.target_format = "mp3" 159 160 client = TranscoderClient.from_settings() 161 162 assert client.service_url == "https://transcoder.example.com" 163 assert client.auth_token == "secret-token" 164 assert client.target_format == "mp3" 165 166 167def test_get_transcoder_client_singleton() -> None: 168 """test get_transcoder_client() returns singleton.""" 169 import backend._internal.clients.transcoder as module 170 171 # reset singleton 172 module._client = None 173 174 with patch("backend._internal.clients.transcoder.settings") as mock_settings: 175 mock_settings.transcoder.service_url = "https://transcoder.example.com" 176 mock_settings.transcoder.auth_token = "token" 177 mock_settings.transcoder.timeout_seconds = 60 178 mock_settings.transcoder.target_format = "mp3" 179 180 client1 = get_transcoder_client() 181 client2 = get_transcoder_client() 182 183 assert client1 is client2 184 185 # cleanup 186 module._client = None