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 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.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) report = FinancialReport.objects.create( external_id=_digits(5), ogrn=_digits(13), 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) IndustrialProductRecordFactory(inn=shared_inn) ManufacturerRecordFactory(inn=shared_inn) InspectionRecordFactory() ProcurementRecordFactory(customer_inn=shared_inn) 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}, ) 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() )