feat(parsers): sync RU proxies from proxy-tools
Some checks failed
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled

This commit is contained in:
2026-03-23 10:47:34 +01:00
parent b4260d53cb
commit 7d4c54636b
13 changed files with 705 additions and 15 deletions

View File

@@ -1,5 +1,6 @@
"""Tests for parsers services."""
from unittest.mock import patch
from urllib.parse import urlparse
from apps.parsers.clients.minpromtorg.industrial import IndustrialProductionClient
@@ -27,9 +28,10 @@ from apps.parsers.services import (
ParserLoadLogService,
ProcurementService,
ProxyService,
ProxyToolsSyncService,
)
from apps.registers.models import Organization
from django.test import TestCase, tag
from django.test import TestCase, override_settings, tag
from tests.utils import TestHTTPServer
from tests.utils.fixtures import build_minpromtorg_certificates_excel, fake
@@ -173,6 +175,127 @@ class ProxyServiceTest(TestCase):
self.assertEqual(created, 1)
self.assertEqual(Proxy.objects.count(), 2)
def test_get_runtime_proxies_prefers_proxy_tools_ru(self):
"""Runtime should prefer RU proxies imported from Proxy-Tools."""
manual_ru = ProxyFactory(
source=ProxyService.MANUAL_SOURCE,
country_code="RU",
)
imported_ru = ProxyFactory(
source=ProxyService.PROXY_TOOLS_SOURCE,
country_code="RU",
)
ProxyFactory(
source=ProxyService.PROXY_TOOLS_SOURCE,
country_code="US",
)
result = ProxyService.get_runtime_proxies()
self.assertEqual(result, [imported_ru.address])
self.assertNotIn(manual_ru.address, result)
def test_get_runtime_proxies_falls_back_to_any_ru_proxy(self):
"""Runtime should fall back to any RU proxy when imported list is empty."""
manual_ru = ProxyFactory(
source=ProxyService.MANUAL_SOURCE,
country_code="RU",
)
ProxyFactory(
source=ProxyService.MANUAL_SOURCE,
country_code="US",
)
result = ProxyService.get_runtime_proxies()
self.assertEqual(result, [manual_ru.address])
class ProxyToolsSyncServiceTest(TestCase):
"""Tests for ProxyToolsSyncService."""
def test_sync_ru_proxies_skips_without_api_key(self):
"""Sync should be skipped when API key is missing."""
result = ProxyToolsSyncService.sync_ru_proxies()
self.assertEqual(result["status"], "skipped")
self.assertEqual(result["reason"], "missing_api_key")
@override_settings(
PROXY_TOOLS_API_KEY="test-token",
PROXY_TOOLS_LIMIT=2,
PROXY_TOOLS_MAX_PAGES=2,
)
@patch("apps.parsers.services.ProxyToolsClient.fetch_proxies")
def test_sync_ru_proxies_upserts_and_deactivates(self, fetch_proxies_mock):
"""Sync should create, reactivate and deactivate imported proxies."""
active_stale = ProxyFactory(
address="http://10.0.0.10:8000",
source=ProxyService.PROXY_TOOLS_SOURCE,
country_code="RU",
is_active=True,
)
inactive_existing = ProxyFactory(
address="http://10.0.0.20:8000",
source=ProxyService.PROXY_TOOLS_SOURCE,
country_code="RU",
is_active=False,
)
manual_ru = ProxyFactory(
address="http://10.0.0.30:8000",
source=ProxyService.MANUAL_SOURCE,
country_code="RU",
is_active=True,
)
fetch_proxies_mock.side_effect = [
{
"data": [
{"host": "10.0.0.20", "port": 8000, "type": "4"},
{"proxy": "socks5://10.0.0.40:1080"},
],
"meta": {"total_pages": 2},
},
{
"data": [
"https://10.0.0.50:8443",
],
"meta": {"total_pages": 2},
},
]
result = ProxyToolsSyncService.sync_ru_proxies()
self.assertEqual(result["status"], "success")
self.assertEqual(result["fetched"], 3)
self.assertEqual(result["created"], 2)
self.assertEqual(result["updated"], 1)
self.assertEqual(result["deactivated"], 1)
active_stale.refresh_from_db()
inactive_existing.refresh_from_db()
manual_ru.refresh_from_db()
self.assertFalse(active_stale.is_active)
self.assertTrue(inactive_existing.is_active)
self.assertTrue(manual_ru.is_active)
imported_addresses = set(
Proxy.objects.filter(
source=ProxyService.PROXY_TOOLS_SOURCE,
country_code="RU",
is_active=True,
).values_list("address", flat=True)
)
self.assertSetEqual(
imported_addresses,
{
"http://10.0.0.20:8000",
"socks5://10.0.0.40:1080",
"https://10.0.0.50:8443",
},
)
class ParserLoadLogServiceTest(TestCase):
"""Tests for ParserLoadLogService."""