Files
mostovik-backend/tests/apps/parsers/test_sources_api_e2e.py
Aleksandr Meshchriakov 6ba8fa8d88
Some checks failed
CI/CD Pipeline / Quality Gate (push) Failing after 9s
CI/CD Pipeline / Build and Push Images (push) Has been skipped
CI/CD Pipeline / Deploy Dev via Compose (push) Has been skipped
CI/CD Pipeline / Internal Notify (push) Successful in 0s
feat: use authoritative organization directory
2026-06-07 16:04:43 +02:00

247 lines
9.5 KiB
Python

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.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)
Organization.objects.create(
name="Shared Organization",
inn=shared_inn,
ogrn=shared_ogrn,
)
Organization.objects.create(
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()
)