feat: expand platform APIs, sources, and test coverage
Some checks failed
CI/CD Pipeline / Run Tests (pull_request) Successful in 1m53s
CI/CD Pipeline / Telegram Notify Success (push) Has been cancelled
CI/CD Pipeline / Run Tests (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (push) Has been cancelled
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 2m54s
CI/CD Pipeline / Telegram Notify Success (pull_request) Has been skipped

This commit is contained in:
2026-03-17 12:56:48 +01:00
parent b505c67968
commit 3d298ce352
101 changed files with 8387 additions and 292 deletions

View File

@@ -3,11 +3,16 @@
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.minpromtorg.schemas import (
IndustrialCertificate,
IndustrialProduct,
Manufacturer,
)
from apps.parsers.clients.proverki.schemas import Inspection
from apps.parsers.clients.zakupki.schemas import Procurement
from apps.parsers.models import (
IndustrialCertificateRecord,
IndustrialProductRecord,
InspectionRecord,
ManufacturerRecord,
ParserLoadLog,
@@ -16,6 +21,7 @@ from apps.parsers.models import (
)
from apps.parsers.services import (
IndustrialCertificateService,
IndustrialProductService,
InspectionService,
ManufacturerService,
ParserLoadLogService,
@@ -30,6 +36,7 @@ from tests.utils.fixtures import build_minpromtorg_certificates_excel, fake
from .factories import (
IndustrialCertificateRecordFactory,
IndustrialProductRecordFactory,
InspectionRecordFactory,
ManufacturerRecordFactory,
ParserLoadLogFactory,
@@ -512,8 +519,8 @@ class ManufacturerServiceTest(TestCase):
results = ManufacturerService.find_by_ogrn(ogrn_target)
self.assertEqual(results.count(), 1)
def test_save_manufacturers_deduplication(self):
"""Test saving manufacturers skips duplicates by INN."""
def test_save_manufacturers_updates_existing_record(self):
"""Test saving manufacturers refreshes existing record by INN."""
# Create initial manufacturer
inn_value = _digits(10)
ogrn_value = _digits(13)
@@ -531,27 +538,139 @@ class ManufacturerServiceTest(TestCase):
self.assertEqual(count1, 1)
self.assertEqual(ManufacturerRecord.objects.count(), 1)
# Try to save with same INN - should be skipped
updated_name = fake.company()
updated_address = fake.address().replace("\n", ", ")
updated_ogrn = _digits(13)
duplicate = [
Manufacturer(
full_legal_name=fake.company(),
inn=inn_value, # Same INN - will be skipped
ogrn=_digits(13),
address=fake.address().replace("\n", ", "),
full_legal_name=updated_name,
inn=inn_value,
ogrn=updated_ogrn,
address=updated_address,
)
]
count2 = ManufacturerService.save_manufacturers(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped)
self.assertEqual(count2, 0)
# Existing record should be updated in place.
self.assertEqual(count2, 1)
self.assertEqual(ManufacturerRecord.objects.count(), 1)
# Verify original data preserved
# Verify latest data preserved
record = ManufacturerRecord.objects.first()
self.assertEqual(record.full_legal_name, company_name)
self.assertEqual(record.ogrn, ogrn_value)
self.assertEqual(record.address, address_value)
self.assertEqual(record.load_batch, 1) # Original batch
self.assertEqual(record.full_legal_name, updated_name)
self.assertEqual(record.ogrn, updated_ogrn)
self.assertEqual(record.address, updated_address)
self.assertEqual(record.load_batch, 2)
class IndustrialProductServiceTest(TestCase):
"""Tests for IndustrialProductService."""
def test_save_products_empty(self):
"""Test saving empty list returns 0."""
count = IndustrialProductService.save_products([], batch_id=1)
self.assertEqual(count, 0)
def test_save_products(self):
"""Test saving industrial products from dataclass."""
products = [
IndustrialProduct(
full_organisation_name=fake.company(),
ogrn=_digits(13),
inn=_digits(10),
registry_number=f"MPP-{_digits(8)}",
product_name=fake.sentence(nb_words=4),
product_model=fake.bothify(text="MODEL-###"),
okpd2_code=f"{fake.random_int(min=10, max=99)}.{fake.random_int(min=10, max=99)}",
tnved_code=_digits(10),
regulatory_document=fake.sentence(nb_words=5),
)
for _ in range(5)
]
count = IndustrialProductService.save_products(products, batch_id=1)
self.assertEqual(count, 5)
self.assertEqual(IndustrialProductRecord.objects.count(), 5)
def test_save_products_links_registry_organization_when_exists(self):
"""Test linking industrial product to registers organization."""
inn = _digits(10)
ogrn = _digits(13)
organization = _create_registry_organization(inn=inn, ogrn=ogrn)
registry_number = f"MPP-{_digits(8)}"
products = [
IndustrialProduct(
full_organisation_name=fake.company(),
ogrn=ogrn,
inn=inn,
registry_number=registry_number,
product_name=fake.sentence(nb_words=4),
product_model=fake.bothify(text="MODEL-###"),
okpd2_code=f"{fake.random_int(min=10, max=99)}.{fake.random_int(min=10, max=99)}",
tnved_code=_digits(10),
regulatory_document=fake.sentence(nb_words=5),
)
]
saved = IndustrialProductService.save_products(products, batch_id=1)
self.assertEqual(saved, 1)
record = IndustrialProductRecord.objects.get(registry_number=registry_number)
self.assertEqual(record.registry_organization_id, organization.id)
def test_find_by_registry_number(self):
"""Test finding industrial product by registry number."""
registry_number = f"MPP-{_digits(8)}"
IndustrialProductRecordFactory(registry_number=registry_number)
IndustrialProductRecordFactory(registry_number=f"MPP-{_digits(8)}")
results = IndustrialProductService.find_by_registry_number(registry_number)
self.assertEqual(results.count(), 1)
def test_save_products_updates_existing_record(self):
"""Test saving products refreshes existing record by registry number."""
registry_number = f"MPP-{_digits(8)}"
initial = [
IndustrialProduct(
full_organisation_name=fake.company(),
ogrn=_digits(13),
inn=_digits(10),
registry_number=registry_number,
product_name="Начальное имя",
product_model="MODEL-001",
okpd2_code="25.11",
tnved_code=_digits(10),
regulatory_document="ГОСТ 1",
)
]
count1 = IndustrialProductService.save_products(initial, batch_id=1)
self.assertEqual(count1, 1)
self.assertEqual(IndustrialProductRecord.objects.count(), 1)
updated = [
IndustrialProduct(
full_organisation_name=fake.company(),
ogrn=_digits(13),
inn=_digits(10),
registry_number=registry_number,
product_name="Обновленное имя",
product_model="MODEL-777",
okpd2_code="28.99",
tnved_code=_digits(10),
regulatory_document="ГОСТ 2",
)
]
count2 = IndustrialProductService.save_products(updated, batch_id=2)
self.assertEqual(count2, 1)
self.assertEqual(IndustrialProductRecord.objects.count(), 1)
record = IndustrialProductRecord.objects.first()
self.assertEqual(record.product_name, "Обновленное имя")
self.assertEqual(record.product_model, "MODEL-777")
self.assertEqual(record.load_batch, 2)
class InspectionServiceTest(TestCase):
@@ -686,8 +805,8 @@ class InspectionServiceTest(TestCase):
)
self.assertEqual(results_batch1.count(), 2)
def test_save_inspections_deduplication(self):
"""Test saving inspections skips duplicates by registration_number."""
def test_save_inspections_updates_existing_record(self):
"""Test saving inspections refreshes existing record by registration_number."""
# Create initial inspection
reg_number = _digits(12)
inn_value = _digits(10)
@@ -721,36 +840,38 @@ class InspectionServiceTest(TestCase):
self.assertEqual(count1, 1)
self.assertEqual(InspectionRecord.objects.count(), 1)
# Try to save with same registration_number - should be skipped
updated_name = fake.company()
updated_authority = fake.company()
updated_status = fake.word()
duplicate = [
Inspection(
registration_number=reg_number, # Same number - will be skipped
registration_number=reg_number,
inn=_digits(10),
ogrn=_digits(13),
organisation_name=fake.company(),
control_authority=fake.company(),
organisation_name=updated_name,
control_authority=updated_authority,
inspection_type=fake.word(),
inspection_form=fake.word(),
start_date=str(fake.date()),
end_date=str(fake.date()),
status=fake.word(),
status=updated_status,
legal_basis=fake.sentence(nb_words=3),
result=fake.sentence(nb_words=3),
)
]
count2 = InspectionService.save_inspections(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped)
self.assertEqual(count2, 0)
# Existing record should be updated in place.
self.assertEqual(count2, 1)
self.assertEqual(InspectionRecord.objects.count(), 1)
# Verify original data preserved
# Verify latest data preserved
record = InspectionRecord.objects.first()
self.assertEqual(record.organisation_name, org_name)
self.assertEqual(record.inn, inn_value)
self.assertEqual(record.control_authority, control_authority)
self.assertEqual(record.status, status)
self.assertEqual(record.load_batch, 1) # Original batch
self.assertEqual(record.organisation_name, updated_name)
self.assertNotEqual(record.inn, inn_value)
self.assertEqual(record.control_authority, updated_authority)
self.assertEqual(record.status, updated_status)
self.assertEqual(record.load_batch, 2)
class ProcurementServiceTest(TestCase):
@@ -783,25 +904,34 @@ class ProcurementServiceTest(TestCase):
saved = ProcurementService.save_procurements([procurement], batch_id=1)
self.assertEqual(saved, 1)
record = ProcurementRecord.objects.get(purchase_number=procurement.purchase_number)
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):
def test_save_procurements_duplicate_updates_existing_record(self):
purchase_number = _digits(19)
first = self._build_procurement(purchase_number=purchase_number)
updated_customer_name = fake.company()
updated_status = fake.word()
duplicate = self._build_procurement(
purchase_number=purchase_number,
customer_name=fake.company(),
customer_name=updated_customer_name,
status=updated_status,
)
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(saved_second, 1)
self.assertEqual(ProcurementRecord.objects.count(), 1)
record = ProcurementRecord.objects.get(purchase_number=purchase_number)
self.assertEqual(record.customer_name, updated_customer_name)
self.assertEqual(record.status, updated_status)
self.assertEqual(record.load_batch, 2)
@tag("integration", "slow", "e2e")