diff --git a/backend/app/services/scrapers/ekburg_permits.py b/backend/app/services/scrapers/ekburg_permits.py index 6cf3387f..b5435846 100644 --- a/backend/app/services/scrapers/ekburg_permits.py +++ b/backend/app/services/scrapers/ekburg_permits.py @@ -248,10 +248,13 @@ class EkburgPermitsClient: USER_AGENT = "GenDesign/1.0 (+https://gendsgn.ru) Site Finder permits scraper" def __init__(self, *, timeout: float = DEFAULT_TIMEOUT) -> None: + # verify=False: екатеринбург.рф подписан CA Минцифры РФ (нет в certifi). + # Данные публичные open-data — SSL pinning здесь не требуется. Issue #242. self._client = httpx.Client( timeout=timeout, follow_redirects=True, headers={"User-Agent": self.USER_AGENT}, + verify=False, ) def __enter__(self) -> EkburgPermitsClient: diff --git a/backend/tests/services/test_ekburg_permits.py b/backend/tests/services/test_ekburg_permits.py index 74738d55..4457dfa4 100644 --- a/backend/tests/services/test_ekburg_permits.py +++ b/backend/tests/services/test_ekburg_permits.py @@ -11,6 +11,7 @@ from io import BytesIO from typing import Any from unittest.mock import MagicMock +import httpx import pytest from openpyxl import Workbook @@ -327,13 +328,28 @@ class TestDownloadXlsx: client.download_xlsx(9999) def test_download_raises_on_http_error(self) -> None: - import httpx as httpx_mod - mock_response = MagicMock() - mock_response.raise_for_status.side_effect = httpx_mod.HTTPStatusError( + mock_response.raise_for_status.side_effect = httpx.HTTPStatusError( "404", request=MagicMock(), response=MagicMock() ) client = self._client_with_mock_http(mock_response) - with pytest.raises(httpx_mod.HTTPStatusError): + with pytest.raises(httpx.HTTPStatusError): client.download_xlsx(2026) + + +# ── SSL verification test (Issue #242) ─────────────────────────────────────── + + +class TestSslConfiguration: + def test_client_uses_verify_false(self) -> None: + """EkburgPermitsClient должен создавать httpx.Client с verify=False. + + екатеринбург.рф использует CA Минцифры РФ — не в certifi bundle. + Issue #242: SSL: CERTIFICATE_VERIFY_FAILED для всех 5 годов. + """ + with EkburgPermitsClient() as client: + # httpx.Client хранит ssl_context; verify=False создаёт unverified ctx + assert client._client._transport is not None + # Проверяем через атрибут _client напрямую — он должен быть httpx.Client + assert isinstance(client._client, httpx.Client)