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)