import requests from requests.exceptions import ConnectionError, Timeout, SSLError from unittest.mock import patch, MagicMock from utils.cat import fetch_cat class TestFetchCat: """Тесты функции fetch_cat() — получение URL случайного котика.""" @patch("utils.cat._session.get") async def test_fetch_cat_success(self, mock_get) -> None: """Успешный ответ с URL должен вернуть строку.""" mock_response = MagicMock() mock_response.json.return_value = [{"url": "https://example.com/cat.jpg"}] mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response result = await fetch_cat() assert result == "https://example.com/cat.jpg" @patch("utils.cat._session.get") async def test_fetch_cat_empty_array(self, mock_get) -> None: """Пустой массив должен вернуть None.""" mock_response = MagicMock() mock_response.json.return_value = [] mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response result = await fetch_cat() assert result is None @patch("utils.cat._session.get") async def test_fetch_cat_http_error(self, mock_get) -> None: """HTTP-ошибка (raise_for_status) должна вернуть None.""" mock_response = MagicMock() mock_response.raise_for_status.side_effect = requests.HTTPError("404 Not Found") mock_get.return_value = mock_response result = await fetch_cat() assert result is None @patch("utils.cat._session.get") async def test_fetch_cat_connection_error(self, mock_get) -> None: """ConnectionError должна вернуть None.""" mock_get.side_effect = ConnectionError("No connection") result = await fetch_cat() assert result is None @patch("utils.cat._session.get") async def test_fetch_cat_timeout(self, mock_get) -> None: """Timeout должна вернуть None.""" mock_get.side_effect = Timeout("Request timed out") result = await fetch_cat() assert result is None @patch("utils.cat._session.get") async def test_fetch_cat_ssl_error(self, mock_get) -> None: """SSLError должна вернуть None.""" mock_get.side_effect = SSLError("SSL handshake failed") result = await fetch_cat() assert result is None @patch("utils.cat._session.get") async def test_fetch_cat_json_parse_error(self, mock_get) -> None: """Ошибка парсинга JSON должна вернуть None.""" mock_response = MagicMock() mock_response.json.side_effect = requests.JSONDecodeError( "Expecting value", "", 0 ) mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response result = await fetch_cat() assert result is None @patch("utils.cat._session.get") async def test_fetch_cat_missing_url_key(self, mock_get) -> None: """Отсутствие ключа 'url' в ответе должно вернуть None.""" mock_response = MagicMock() mock_response.json.return_value = [{"error": "no image"}] mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response result = await fetch_cat() assert result is None @patch("utils.cat._session.get") async def test_fetch_cat_request_exception(self, mock_get) -> None: """Общий RequestException должен вернуть None.""" mock_get.side_effect = requests.RequestException("Generic error") result = await fetch_cat() assert result is None @patch("utils.cat._session.get") async def test_fetch_cat_url_with_special_chars(self, mock_get) -> None: """URL со спецсимволами должен вернуться как есть.""" mock_response = MagicMock() mock_response.json.return_value = [ {"url": "https://example.com/cat?w=100&h=200"} ] mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response result = await fetch_cat() assert result == "https://example.com/cat?w=100&h=200" @patch("utils.cat._session.get") @patch("utils.cat._cat_api_key", "test-api-key-123") async def test_fetch_cat_sends_api_key_header(self, mock_get) -> None: """При заданном CAT_API_KEY должен передаваться заголовок x-api-key.""" mock_response = MagicMock() mock_response.json.return_value = [{"url": "https://example.com/cat.jpg"}] mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response await fetch_cat() # Проверяем, что headers переданы с x-api-key call_kwargs = mock_get.call_args assert call_kwargs[1].get("headers") == {"x-api-key": "test-api-key-123"} @patch("utils.cat._session.get") @patch("utils.cat._cat_api_key", None) async def test_fetch_cat_no_api_key_header(self, mock_get) -> None: """При отсутствии CAT_API_KEY заголовок x-api-key не передаётся.""" mock_response = MagicMock() mock_response.json.return_value = [{"url": "https://example.com/cat.jpg"}] mock_response.raise_for_status = MagicMock() mock_get.return_value = mock_response await fetch_cat() call_kwargs = mock_get.call_args assert call_kwargs[1].get("headers") is None