feat(registry): add new endpoints for registers, exchange, and backups; update routing and configurations
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 3m10s
CI/CD Pipeline / Run Tests (push) Successful in 3m35s
CI/CD Pipeline / Telegram Notify Success (push) Has been skipped
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m26s
CI/CD Pipeline / Run Tests (pull_request) Successful in 2m46s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped

This commit is contained in:
2026-03-04 15:36:57 +01:00
parent 052389d921
commit a91ed1f1ae
90 changed files with 5488 additions and 622 deletions

View File

@@ -1,13 +1,17 @@
"""Tests for parsers services."""
from urllib.parse import urlparse
from apps.parsers.clients.minpromtorg.industrial import IndustrialProductionClient
from apps.parsers.clients.minpromtorg.schemas import IndustrialCertificate, Manufacturer
from apps.parsers.clients.proverki.schemas import Inspection
from apps.parsers.clients.zakupki.schemas import Procurement
from apps.parsers.models import (
IndustrialCertificateRecord,
InspectionRecord,
ManufacturerRecord,
ParserLoadLog,
ProcurementRecord,
Proxy,
)
from apps.parsers.services import (
@@ -15,12 +19,14 @@ from apps.parsers.services import (
InspectionService,
ManufacturerService,
ParserLoadLogService,
ProcurementService,
ProxyService,
)
from apps.registers.models import Organization
from django.test import TestCase, tag
from tests.utils import TestHTTPServer
from tests.utils.fixtures import build_minpromtorg_certificates_excel, fake
from urllib.parse import urlparse
from .factories import (
IndustrialCertificateRecordFactory,
@@ -38,6 +44,17 @@ def _digits(length: int) -> str:
def _proxy_address() -> str:
return f"http://{fake.ipv4()}:{fake.port_number()}"
def _create_registry_organization(*, inn: str, ogrn: str) -> Organization:
return Organization.objects.create(
pn_name=fake.company(),
mn_ogrn=int(ogrn),
mn_inn=int(inn),
in_kpp=int(_digits(9)),
mn_okpo=_digits(8),
)
class ProxyServiceTest(TestCase):
"""Tests for ProxyService."""
@@ -248,6 +265,59 @@ class IndustrialCertificateServiceTest(TestCase):
self.assertEqual(count, 5)
self.assertEqual(IndustrialCertificateRecord.objects.count(), 5)
record = IndustrialCertificateRecord.objects.first()
self.assertIsNotNone(record.issue_date_normalized)
self.assertIsNotNone(record.expiry_date_normalized)
def test_save_certificates_links_registry_organization_when_exists(self):
"""Test linking to registers organization is created when identifiers match."""
inn = _digits(10)
ogrn = _digits(13)
organization = _create_registry_organization(inn=inn, ogrn=ogrn)
certificate_number = fake.bothify(text="??-####-#####")
certificates = [
IndustrialCertificate(
issue_date=str(fake.date()),
certificate_number=certificate_number,
expiry_date=str(fake.date()),
certificate_file_url=fake.url(),
organisation_name=fake.company(),
inn=inn,
ogrn=ogrn,
)
]
saved = IndustrialCertificateService.save_certificates(certificates, batch_id=1)
self.assertEqual(saved, 1)
record = IndustrialCertificateRecord.objects.get(
certificate_number=certificate_number
)
self.assertEqual(record.registry_organization_id, organization.id)
def test_save_certificates_keeps_null_registry_organization_when_not_found(self):
"""Test parser save does not fail and keeps null when organization is absent."""
certificate_number = fake.bothify(text="??-####-#####")
certificates = [
IndustrialCertificate(
issue_date=str(fake.date()),
certificate_number=certificate_number,
expiry_date=str(fake.date()),
certificate_file_url=fake.url(),
organisation_name=fake.company(),
inn=_digits(10),
ogrn=_digits(13),
)
]
saved = IndustrialCertificateService.save_certificates(certificates, batch_id=1)
self.assertEqual(saved, 1)
record = IndustrialCertificateRecord.objects.get(
certificate_number=certificate_number
)
self.assertIsNone(record.registry_organization_id)
def test_save_certificates_with_chunk_size(self):
"""Test saving certificates in chunks."""
@@ -287,16 +357,16 @@ class IndustrialCertificateServiceTest(TestCase):
results = IndustrialCertificateService.find_by_inn(inn_a)
self.assertEqual(results.count(), 2)
results_batch1 = IndustrialCertificateService.find_by_inn(
inn_a, batch_id=1
)
results_batch1 = IndustrialCertificateService.find_by_inn(inn_a, batch_id=1)
self.assertEqual(results_batch1.count(), 1)
def test_find_by_certificate_number(self):
"""Test finding certificate by number."""
unique_number = fake.bothify(text="CERT-#####")
IndustrialCertificateRecordFactory(certificate_number=unique_number)
IndustrialCertificateRecordFactory(certificate_number=fake.bothify(text="CERT-#####"))
IndustrialCertificateRecordFactory(
certificate_number=fake.bothify(text="CERT-#####")
)
results = IndustrialCertificateService.find_by_certificate_number(unique_number)
self.assertEqual(results.count(), 1)
@@ -332,9 +402,10 @@ class IndustrialCertificateServiceTest(TestCase):
ogrn=_digits(13),
)
]
IndustrialCertificateService.save_certificates(duplicate, batch_id=2)
count2 = IndustrialCertificateService.save_certificates(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped)
self.assertEqual(count2, 0)
self.assertEqual(IndustrialCertificateRecord.objects.count(), 1)
# Verify original data preserved
@@ -369,6 +440,27 @@ class ManufacturerServiceTest(TestCase):
self.assertEqual(count, 5)
self.assertEqual(ManufacturerRecord.objects.count(), 5)
def test_save_manufacturers_links_registry_organization_when_exists(self):
"""Test linking manufacturer to registers organization by INN/ОГРН."""
inn = _digits(10)
ogrn = _digits(13)
organization = _create_registry_organization(inn=inn, ogrn=ogrn)
manufacturers = [
Manufacturer(
full_legal_name=fake.company(),
inn=inn,
ogrn=ogrn,
address=fake.address().replace("\n", ", "),
)
]
saved = ManufacturerService.save_manufacturers(manufacturers, batch_id=1)
self.assertEqual(saved, 1)
record = ManufacturerRecord.objects.get(inn=inn)
self.assertEqual(record.registry_organization_id, organization.id)
def test_save_manufacturers_with_chunk_size(self):
"""Test saving manufacturers in chunks."""
manufacturers = [
@@ -448,9 +540,10 @@ class ManufacturerServiceTest(TestCase):
address=fake.address().replace("\n", ", "),
)
]
ManufacturerService.save_manufacturers(duplicate, batch_id=2)
count2 = ManufacturerService.save_manufacturers(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped)
self.assertEqual(count2, 0)
self.assertEqual(ManufacturerRecord.objects.count(), 1)
# Verify original data preserved
@@ -493,6 +586,39 @@ class InspectionServiceTest(TestCase):
self.assertEqual(count, 5)
self.assertEqual(InspectionRecord.objects.count(), 5)
record = InspectionRecord.objects.first()
self.assertIsNotNone(record.start_date_normalized)
self.assertIsNotNone(record.end_date_normalized)
def test_save_inspections_links_registry_organization_when_exists(self):
"""Test linking inspection to registers organization by INN/ОГРН."""
inn = _digits(10)
ogrn = _digits(13)
organization = _create_registry_organization(inn=inn, ogrn=ogrn)
registration_number = _digits(12)
inspections = [
Inspection(
registration_number=registration_number,
inn=inn,
ogrn=ogrn,
organisation_name=fake.company(),
control_authority=fake.company(),
inspection_type=fake.word(),
inspection_form=fake.word(),
start_date=str(fake.date()),
end_date=str(fake.date()),
status=fake.word(),
legal_basis=fake.sentence(nb_words=3),
result=fake.sentence(nb_words=3),
)
]
saved = InspectionService.save_inspections(inspections, batch_id=1)
self.assertEqual(saved, 1)
record = InspectionRecord.objects.get(registration_number=registration_number)
self.assertEqual(record.registry_organization_id, organization.id)
def test_save_inspections_with_chunk_size(self):
"""Test saving inspections in chunks."""
@@ -612,9 +738,10 @@ class InspectionServiceTest(TestCase):
result=fake.sentence(nb_words=3),
)
]
InspectionService.save_inspections(duplicate, batch_id=2)
count2 = InspectionService.save_inspections(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped)
self.assertEqual(count2, 0)
self.assertEqual(InspectionRecord.objects.count(), 1)
# Verify original data preserved
@@ -626,6 +753,57 @@ class InspectionServiceTest(TestCase):
self.assertEqual(record.load_batch, 1) # Original batch
class ProcurementServiceTest(TestCase):
"""Tests for ProcurementService."""
def _build_procurement(self, **overrides) -> Procurement:
data = {
"purchase_number": _digits(19),
"purchase_name": fake.sentence(nb_words=4),
"customer_inn": _digits(10),
"customer_kpp": _digits(9),
"customer_ogrn": _digits(13),
"customer_name": fake.company(),
"max_price": "1 234 567,89",
"currency_code": "RUB",
"placement_method": fake.word(),
"publish_date": "01.03.2026",
"end_date": "2026-03-15",
"status": fake.word(),
"law_type": "44-FZ",
"purchase_object_info": fake.sentence(nb_words=4),
"href": fake.url(),
}
data.update(overrides)
return Procurement(**data)
def test_save_procurements_sets_normalized_fields(self):
procurement = self._build_procurement()
saved = ProcurementService.save_procurements([procurement], batch_id=1)
self.assertEqual(saved, 1)
record = ProcurementRecord.objects.get(purchase_number=procurement.purchase_number)
self.assertEqual(str(record.max_price_amount), "1234567.89")
self.assertEqual(str(record.publish_date_normalized), "2026-03-01")
self.assertEqual(str(record.end_date_normalized), "2026-03-15")
def test_save_procurements_duplicate_returns_zero(self):
purchase_number = _digits(19)
first = self._build_procurement(purchase_number=purchase_number)
duplicate = self._build_procurement(
purchase_number=purchase_number,
customer_name=fake.company(),
)
saved_first = ProcurementService.save_procurements([first], batch_id=1)
saved_second = ProcurementService.save_procurements([duplicate], batch_id=2)
self.assertEqual(saved_first, 1)
self.assertEqual(saved_second, 0)
self.assertEqual(ProcurementRecord.objects.count(), 1)
@tag("integration", "slow", "e2e")
class EndToEndIntegrationTest(TestCase):
"""
@@ -668,9 +846,7 @@ class EndToEndIntegrationTest(TestCase):
)
server.add_bytes(f"/files/{file_name}", excel_bytes)
host = urlparse(server.base_url)
client_host = (
f"{host.hostname}:{host.port}" if host.port else host.hostname
)
client_host = f"{host.hostname}:{host.port}" if host.port else host.hostname
with IndustrialProductionClient(
host=client_host,
scheme="http",