Files
mostovik-backend/tests/apps/parsers/test_sources_api_e2e.py
Aleksandr Meshchriakov 802257a757
Some checks failed
CI/CD Pipeline / Quality Gate (push) Failing after 32s
CI/CD Pipeline / Build and Push Images (push) Has been skipped
CI/CD Pipeline / Internal Notify (push) Successful in 0s
CI/CD Pipeline / Deploy Dev via Compose (push) Has been skipped
fix: resolve parser organizations from directory only
2026-06-07 16:28:57 +02:00

248 lines
9.6 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.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()
)