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
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:
@@ -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")
|
||||
|
||||
Reference in New Issue
Block a user