from __future__ import annotations from types import SimpleNamespace from unittest.mock import patch from apps.core.models import BackgroundJob, JobStatus from apps.parsers.models import FinancialReport, FinancialReportLine, ParserLoadLog from django.urls import reverse from organizations.models import Organization from organizations.source_backfill import OrganizationSourceBackfillService from rest_framework import status from rest_framework.test import APITestCase from tests.apps.parsers.factories import ( IndustrialCertificateRecordFactory, IndustrialProductRecordFactory, InspectionRecordFactory, ManufacturerRecordFactory, ParserLoadLogFactory, ProcurementRecordFactory, ) from tests.apps.parsers.organization_helpers import create_directory_organization from tests.apps.user.factories import UserFactory from tests.utils.fixtures import fake def _digits(length: int) -> str: return "".join(str(fake.random_int(0, 9)) for _ in range(length)) class SourcesApiE2ETest(APITestCase): def setUp(self): self.user = UserFactory.create_user() self.admin = UserFactory.create_user(is_staff=True) def test_source_cards_and_statuses_flow(self): shared_inn = _digits(10) shared_ogrn = _digits(13) report_ogrn = _digits(13) create_directory_organization( name="Shared Organization", inn=shared_inn, ogrn=shared_ogrn, ) create_directory_organization( name="Financial Organization", ogrn=report_ogrn, ) report = FinancialReport.objects.create( external_id=_digits(5), ogrn=report_ogrn, file_name=f"fin_{_digits(5)}_{_digits(13)}.xlsx", file_hash=fake.sha256(raw_output=False), load_batch=1, status=FinancialReport.Status.SUCCESS, source=FinancialReport.SourceType.API, ) FinancialReportLine.objects.create( report=report, form_code="1", line_code="1100", line_name="Assets", year=2025, period_start=100, period_end=200, ) IndustrialCertificateRecordFactory(inn=shared_inn, ogrn=shared_ogrn) IndustrialProductRecordFactory(inn=shared_inn, ogrn=shared_ogrn) ManufacturerRecordFactory(inn=shared_inn, ogrn=shared_ogrn) InspectionRecordFactory() ProcurementRecordFactory(customer_inn=shared_inn, customer_ogrn=shared_ogrn) ParserLoadLogFactory( source=ParserLoadLog.Source.FNS_REPORTS, status="success", records_count=1, ) ParserLoadLogFactory( source=ParserLoadLog.Source.INDUSTRIAL, status="success", records_count=1, ) ParserLoadLogFactory( source=ParserLoadLog.Source.INDUSTRIAL_PRODUCTS, status="success", records_count=1, ) ParserLoadLogFactory( source=ParserLoadLog.Source.MANUFACTURES, status="success", records_count=1, ) ParserLoadLogFactory( source=ParserLoadLog.Source.PROCUREMENTS, status="failed", error_message="download error", ) BackgroundJob.objects.create( task_id="job-inspections", task_name="apps.parsers.tasks.sync_inspections", status=JobStatus.STARTED, progress=55, progress_message="sync", user_id=self.user.id, meta={"source": ParserLoadLog.Source.INSPECTIONS}, ) OrganizationSourceBackfillService.backfill() self.client.force_authenticate(self.user) cards_response = self.client.get(reverse("api_v1:sources:source-cards-list")) detail_response = self.client.get( reverse( "api_v1:sources:source-cards-detail", kwargs={"slug": "manufacturers-and-products"}, ) ) statuses_response = self.client.get( reverse("api_v1:sources:source-cards-statuses") ) self.assertEqual(cards_response.status_code, status.HTTP_200_OK) self.assertEqual(detail_response.status_code, status.HTTP_200_OK) self.assertEqual(statuses_response.status_code, status.HTTP_200_OK) cards = {item["slug"]: item for item in cards_response.data["data"]} self.assertEqual(cards["financial-indicators"]["records_count"], 1) self.assertEqual(cards["planned-inspections"]["status"], "in_progress") self.assertEqual(cards["planned-inspections"]["progress"], 55) self.assertEqual(cards["public-procurements"]["status"], "error") self.assertEqual( cards["public-procurements"]["error_message"], "download error" ) minprom_card = detail_response.data["data"] self.assertEqual(minprom_card["records_count"], 3) self.assertEqual(minprom_card["organizations_count"], 1) self.assertEqual(len(minprom_card["source_items"]), 3) statuses = {item["slug"]: item for item in statuses_response.data["data"]} self.assertEqual(statuses["planned-inspections"]["progress"], 55) self.assertEqual(statuses["public-procurements"]["status"], "error") def test_products_endpoint_supports_filter_and_search(self): target = IndustrialProductRecordFactory( registry_number="MPP-12345678", product_name="Laser module", full_organisation_name="Target Company", ) IndustrialProductRecordFactory( registry_number="MPP-87654321", product_name="Other product", full_organisation_name="Another Company", ) self.client.force_authenticate(self.user) filtered_response = self.client.get( reverse("api_v1:minpromtorg:industrial-products-list"), {"registry_number": "MPP-12345678"}, ) search_response = self.client.get( reverse("api_v1:minpromtorg:industrial-products-list"), {"search": "Laser"}, ) detail_response = self.client.get( reverse("api_v1:minpromtorg:industrial-products-detail", args=[target.id]) ) self.assertEqual(filtered_response.status_code, status.HTTP_200_OK) self.assertEqual(search_response.status_code, status.HTTP_200_OK) self.assertEqual(detail_response.status_code, status.HTTP_200_OK) self.assertEqual(len(filtered_response.data["data"]), 1) self.assertEqual(filtered_response.data["data"][0]["id"], target.id) self.assertEqual(len(search_response.data["data"]), 1) self.assertEqual(search_response.data["data"][0]["id"], target.id) self.assertEqual(detail_response.data["id"], target.id) def test_refresh_endpoints_cover_multiple_source_cards(self): self.client.force_authenticate(self.admin) with patch( "apps.parsers.source_cards.uuid.uuid4", side_effect=[ "request-minprom", "task-industrial", "task-products", "task-manufactures", "request-procurements", "task-procurements", ], ), patch( "apps.parsers.tasks.parse_industrial_production.apply_async", return_value=SimpleNamespace(id="task-industrial"), ), patch( "apps.parsers.tasks.parse_industrial_products.apply_async", return_value=SimpleNamespace(id="task-products"), ), patch( "apps.parsers.tasks.parse_manufactures.apply_async", return_value=SimpleNamespace(id="task-manufactures"), ): minprom_response = self.client.post( reverse( "api_v1:sources:source-cards-refresh", kwargs={"slug": "manufacturers-and-products"}, ), {}, format="json", ) with patch( "apps.parsers.tasks.sync_procurements.apply_async", return_value=SimpleNamespace(id="task-procurements"), ): procurements_response = self.client.post( reverse( "api_v1:sources:source-cards-refresh", kwargs={"slug": "public-procurements"}, ), {"params": {"region_code": "77", "current_year": "2025"}}, format="json", ) self.assertEqual(minprom_response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(procurements_response.status_code, status.HTTP_202_ACCEPTED) self.assertEqual(minprom_response.data["status"], "accepted") self.assertEqual(procurements_response.data["status"], "accepted") self.assertEqual(set(minprom_response.data.keys()), {"task_id", "status"}) self.assertEqual(minprom_response.data["task_id"], "task-industrial") self.assertEqual( set(procurements_response.data.keys()), {"task_id", "status"}, ) self.assertEqual(procurements_response.data["task_id"], "task-procurements") self.assertEqual( BackgroundJob.objects.filter( task_id__in=["task-industrial", "task-products", "task-manufactures"], user_id=self.admin.id, ).count(), 3, ) self.assertTrue( BackgroundJob.objects.filter( task_id="task-procurements", task_name="apps.parsers.tasks.sync_procurements", user_id=self.admin.id, ).exists() )