253 lines
9.2 KiB
Python
253 lines
9.2 KiB
Python
from __future__ import annotations
|
|
|
|
from contextlib import ExitStack
|
|
from types import SimpleNamespace
|
|
from unittest.mock import patch
|
|
|
|
from apps.parsers import tasks as parser_tasks
|
|
from apps.parsers.clients.common.schemas import GenericParserItem
|
|
from apps.parsers.models import CheckoCollectionAttempt, ParserLoadLog
|
|
from django.test import TestCase, override_settings
|
|
|
|
from tests.apps.parsers.organization_helpers import create_directory_organization
|
|
|
|
|
|
def _organization(index: int, *, membership: bool = True):
|
|
return create_directory_organization(
|
|
name=f"Организация {index}",
|
|
inn=f"7701000{index:03d}",
|
|
ogrn=f"1027700000{index:03d}",
|
|
opk_registry_membership=membership,
|
|
)
|
|
|
|
|
|
class RegistryProcurementTasksTest(TestCase):
|
|
def test_daily_orchestrator_queues_addressed_sources_with_same_batch_limit(self):
|
|
task_names = (
|
|
"parse_procurements_44fz",
|
|
"parse_procurements_223fz",
|
|
"parse_registry_contracts",
|
|
"parse_unfair_suppliers",
|
|
"parse_arbitration_cases",
|
|
"parse_fedresurs_bankruptcy",
|
|
"parse_registry_inspections",
|
|
"parse_trudvsem_vacancies",
|
|
)
|
|
with ExitStack() as stack:
|
|
mocks = [
|
|
stack.enter_context(
|
|
patch.object(
|
|
getattr(parser_tasks, task_name),
|
|
"delay",
|
|
return_value=SimpleNamespace(id=f"{task_name}-id"),
|
|
)
|
|
)
|
|
for task_name in task_names
|
|
]
|
|
result = parser_tasks.parse_registry_enrichment_sources(
|
|
limit=20,
|
|
proxies=[],
|
|
)
|
|
|
|
self.assertEqual(
|
|
set(result),
|
|
{
|
|
"procurements_44fz",
|
|
"procurements_223fz",
|
|
"contracts",
|
|
"unfair_suppliers",
|
|
"arbitration",
|
|
"bankruptcy",
|
|
"inspections",
|
|
"vacancies",
|
|
},
|
|
)
|
|
for task_mock in mocks[:5]:
|
|
task_mock.assert_called_once_with(
|
|
proxies=[],
|
|
requested_by_id=None,
|
|
limit=20,
|
|
)
|
|
mocks[5].assert_called_once_with(proxies=[], requested_by_id=None)
|
|
mocks[6].assert_called_once_with(proxies=[], requested_by_id=None, limit=20)
|
|
mocks[7].assert_called_once_with(proxies=[], requested_by_id=None)
|
|
|
|
def test_eis_monthly_claims_advance_to_the_next_batch(self):
|
|
organizations = [_organization(index) for index in range(1, 4)]
|
|
outside_registry = _organization(9, membership=False)
|
|
requested_inns: list[str] = []
|
|
|
|
class _Client:
|
|
def __init__(self, **_kwargs):
|
|
return
|
|
|
|
def fetch_for_customer(self, **kwargs):
|
|
requested_inns.append(kwargs["inn"])
|
|
return [
|
|
GenericParserItem(
|
|
source=ParserLoadLog.Source.PROCUREMENTS_44FZ,
|
|
external_id=f"notice-{kwargs['inn']}",
|
|
inn=kwargs["inn"],
|
|
organisation_name=kwargs["organization_name"],
|
|
)
|
|
]
|
|
|
|
with patch.object(parser_tasks, "EisRegistryProcurementClient", _Client):
|
|
first = parser_tasks._fetch_eis_registry_procurement_records(
|
|
source=ParserLoadLog.Source.PROCUREMENTS_44FZ,
|
|
law="44",
|
|
limit=2,
|
|
proxies=[],
|
|
)
|
|
second = parser_tasks._fetch_eis_registry_procurement_records(
|
|
source=ParserLoadLog.Source.PROCUREMENTS_44FZ,
|
|
law="44",
|
|
limit=2,
|
|
proxies=[],
|
|
)
|
|
|
|
self.assertEqual(len(first), 2)
|
|
self.assertEqual(len(second), 1)
|
|
self.assertEqual(requested_inns, [item.inn for item in organizations])
|
|
self.assertNotIn(outside_registry.inn, requested_inns)
|
|
self.assertEqual(
|
|
CheckoCollectionAttempt.objects.filter(
|
|
source=CheckoCollectionAttempt.Source.PROCUREMENTS_44FZ,
|
|
status=CheckoCollectionAttempt.Status.SUCCESS,
|
|
).count(),
|
|
3,
|
|
)
|
|
|
|
def test_eis_request_error_creates_failed_monthly_claim(self):
|
|
organization = _organization(1)
|
|
|
|
class _Client:
|
|
def __init__(self, **_kwargs):
|
|
return
|
|
|
|
def fetch_for_customer(self, **_kwargs):
|
|
raise parser_tasks.HTTPClientError("EIS unavailable")
|
|
|
|
with (
|
|
patch.object(parser_tasks, "EisRegistryProcurementClient", _Client),
|
|
self.assertRaises(parser_tasks.ParserSourceSkipped),
|
|
):
|
|
parser_tasks._fetch_eis_registry_procurement_records(
|
|
source=ParserLoadLog.Source.PROCUREMENTS_223FZ,
|
|
law="223",
|
|
limit=1,
|
|
proxies=[],
|
|
)
|
|
|
|
attempt = CheckoCollectionAttempt.objects.get(
|
|
organization=organization,
|
|
source=CheckoCollectionAttempt.Source.PROCUREMENTS_223FZ,
|
|
)
|
|
self.assertEqual(attempt.status, CheckoCollectionAttempt.Status.FAILED)
|
|
|
|
def test_explicit_organization_ids_do_not_fall_through_to_next_batch(self):
|
|
selected = _organization(1)
|
|
_organization(2)
|
|
calls: list[str] = []
|
|
|
|
class _Client:
|
|
def __init__(self, **_kwargs):
|
|
return
|
|
|
|
def fetch_for_customer(self, **kwargs):
|
|
calls.append(kwargs["inn"])
|
|
return []
|
|
|
|
kwargs = {
|
|
"source": ParserLoadLog.Source.PROCUREMENTS_44FZ,
|
|
"law": "44",
|
|
"limit": 20,
|
|
"proxies": [],
|
|
"organization_ids": [str(selected.uid)],
|
|
}
|
|
with patch.object(parser_tasks, "EisRegistryProcurementClient", _Client):
|
|
self.assertEqual(
|
|
parser_tasks._fetch_eis_registry_procurement_records(**kwargs),
|
|
[],
|
|
)
|
|
with self.assertRaises(parser_tasks.ParserSourceSkipped):
|
|
parser_tasks._fetch_eis_registry_procurement_records(**kwargs)
|
|
|
|
self.assertEqual(calls, [selected.inn])
|
|
|
|
@override_settings(CHECKO_API_KEY="test-key")
|
|
def test_checko_rnp_is_supplier_bound_and_not_requested_twice(self):
|
|
organization = _organization(1)
|
|
calls: list[str] = []
|
|
|
|
class _Client:
|
|
def __init__(self, **_kwargs):
|
|
return
|
|
|
|
def get_company(self, request):
|
|
calls.append(request.inn)
|
|
return SimpleNamespace(
|
|
data=SimpleNamespace(
|
|
inn=organization.inn,
|
|
ogrn=organization.ogrn,
|
|
unfair_supplier=(
|
|
SimpleNamespace(
|
|
registry_number="RNP-1",
|
|
publish_date="2026-07-01",
|
|
approval_date="2026-06-30",
|
|
customer_short_name="Заказчик",
|
|
customer_full_name="Заказчик полный",
|
|
customer_inn="7702000002",
|
|
customer_kpp="770201001",
|
|
purchase_number="PURCHASE-1",
|
|
purchase_description="Поставка оборудования",
|
|
contract_price=1500000,
|
|
),
|
|
),
|
|
)
|
|
)
|
|
|
|
with patch.object(parser_tasks, "CheckoClient", _Client):
|
|
records = parser_tasks._fetch_checko_unfair_supplier_records(
|
|
limit=1,
|
|
proxies=[],
|
|
)
|
|
with self.assertRaises(parser_tasks.ParserSourceSkipped):
|
|
parser_tasks._fetch_checko_unfair_supplier_records(
|
|
limit=1,
|
|
proxies=[],
|
|
)
|
|
|
|
self.assertEqual(calls, [organization.inn])
|
|
self.assertEqual(len(records), 1)
|
|
self.assertEqual(records[0].inn, organization.inn)
|
|
self.assertEqual(records[0].payload["supplier"]["inn"], organization.inn)
|
|
self.assertEqual(records[0].payload["customer"]["inn"], "7702000002")
|
|
self.assertEqual(records[0].amount, 1500000)
|
|
|
|
@override_settings(CHECKO_API_KEY="test-key")
|
|
def test_checko_rnp_api_error_creates_failed_monthly_claim(self):
|
|
organization = _organization(1)
|
|
|
|
class _Client:
|
|
def __init__(self, **_kwargs):
|
|
return
|
|
|
|
def get_company(self, _request):
|
|
raise parser_tasks.CheckoError("Checko unavailable")
|
|
|
|
with (
|
|
patch.object(parser_tasks, "CheckoClient", _Client),
|
|
self.assertRaises(parser_tasks.ParserSourceSkipped),
|
|
):
|
|
parser_tasks._fetch_checko_unfair_supplier_records(
|
|
limit=1,
|
|
proxies=[],
|
|
)
|
|
|
|
attempt = CheckoCollectionAttempt.objects.get(
|
|
organization=organization,
|
|
source=CheckoCollectionAttempt.Source.UNFAIR_SUPPLIERS,
|
|
)
|
|
self.assertEqual(attempt.status, CheckoCollectionAttempt.Status.FAILED)
|