feat: обновления парсеров, тестов и миграций
Some checks failed
CI/CD Pipeline / Run Tests (push) Failing after 37s
CI/CD Pipeline / Code Quality Checks (push) Failing after 43s
CI/CD Pipeline / Build & Push Images (push) Has been skipped
CI/CD Pipeline / Deploy (dev) (push) Has been skipped
CI/CD Pipeline / Deploy (prod) (push) Has been skipped
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 0s
CI/CD Pipeline / Run Tests (pull_request) Failing after 0s
CI/CD Pipeline / Build & Push Images (pull_request) Has been skipped
CI/CD Pipeline / Deploy (dev) (pull_request) Has been skipped
CI/CD Pipeline / Deploy (prod) (pull_request) Has been skipped
Some checks failed
CI/CD Pipeline / Run Tests (push) Failing after 37s
CI/CD Pipeline / Code Quality Checks (push) Failing after 43s
CI/CD Pipeline / Build & Push Images (push) Has been skipped
CI/CD Pipeline / Deploy (dev) (push) Has been skipped
CI/CD Pipeline / Deploy (prod) (push) Has been skipped
CI/CD Pipeline / Code Quality Checks (pull_request) Failing after 0s
CI/CD Pipeline / Run Tests (pull_request) Failing after 0s
CI/CD Pipeline / Build & Push Images (pull_request) Has been skipped
CI/CD Pipeline / Deploy (dev) (pull_request) Has been skipped
CI/CD Pipeline / Deploy (prod) (pull_request) Has been skipped
- Обновлены клиенты парсеров (checko, fns, minpromtorg, proverki, zakupki) - Добавлены новые миграции для моделей - Расширено покрытие тестами - Обновлены конфигурации и настройки проекта - Добавлены утилиты для тестирования Co-Authored-By: Warp <agent@warp.dev>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
"""Tests for parsers services."""
|
||||
|
||||
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.models import (
|
||||
@@ -16,8 +17,10 @@ from apps.parsers.services import (
|
||||
ParserLoadLogService,
|
||||
ProxyService,
|
||||
)
|
||||
from django.test import TestCase
|
||||
from faker import Faker
|
||||
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,
|
||||
@@ -27,8 +30,13 @@ from .factories import (
|
||||
ProxyFactory,
|
||||
)
|
||||
|
||||
fake = Faker("ru_RU")
|
||||
|
||||
def _digits(length: int) -> str:
|
||||
return "".join(str(fake.random_int(0, 9)) for _ in range(length))
|
||||
|
||||
|
||||
def _proxy_address() -> str:
|
||||
return f"http://{fake.ipv4()}:{fake.port_number()}"
|
||||
|
||||
class ProxyServiceTest(TestCase):
|
||||
"""Tests for ProxyService."""
|
||||
@@ -66,7 +74,7 @@ class ProxyServiceTest(TestCase):
|
||||
|
||||
def test_mark_used(self):
|
||||
"""Test marking proxy as used updates timestamp."""
|
||||
proxy = ProxyFactory()
|
||||
proxy = ProxyFactory(last_used_at=None)
|
||||
self.assertIsNone(proxy.last_used_at)
|
||||
|
||||
ProxyService.mark_used(proxy.address)
|
||||
@@ -94,8 +102,8 @@ class ProxyServiceTest(TestCase):
|
||||
|
||||
def test_add_proxy(self):
|
||||
"""Test adding new proxy."""
|
||||
address = "http://new-proxy:8080"
|
||||
description = "Test proxy"
|
||||
address = _proxy_address()
|
||||
description = fake.sentence(nb_words=3)
|
||||
|
||||
proxy = ProxyService.add_proxy(address, description)
|
||||
|
||||
@@ -105,21 +113,19 @@ class ProxyServiceTest(TestCase):
|
||||
|
||||
def test_add_proxy_idempotent(self):
|
||||
"""Test adding existing proxy returns existing record."""
|
||||
address = "http://existing:8080"
|
||||
existing = ProxyFactory(address=address, description="Original")
|
||||
address = _proxy_address()
|
||||
existing_description = fake.sentence(nb_words=3)
|
||||
existing = ProxyFactory(address=address, description=existing_description)
|
||||
|
||||
proxy = ProxyService.add_proxy(address, "New description")
|
||||
new_description = fake.sentence(nb_words=3)
|
||||
proxy = ProxyService.add_proxy(address, new_description)
|
||||
|
||||
self.assertEqual(proxy.id, existing.id)
|
||||
self.assertEqual(proxy.description, "Original") # Not updated
|
||||
self.assertEqual(proxy.description, existing_description) # Not updated
|
||||
|
||||
def test_add_proxies(self):
|
||||
"""Test bulk adding proxies."""
|
||||
addresses = [
|
||||
"http://proxy1:8080",
|
||||
"http://proxy2:8080",
|
||||
"http://proxy3:8080",
|
||||
]
|
||||
addresses = [_proxy_address() for _ in range(3)]
|
||||
|
||||
created = ProxyService.add_proxies(addresses)
|
||||
|
||||
@@ -128,10 +134,14 @@ class ProxyServiceTest(TestCase):
|
||||
|
||||
def test_add_proxies_skips_existing(self):
|
||||
"""Test bulk add skips existing proxies."""
|
||||
ProxyFactory(address="http://existing:8080")
|
||||
existing_address = _proxy_address()
|
||||
new_address = _proxy_address()
|
||||
while new_address == existing_address:
|
||||
new_address = _proxy_address()
|
||||
ProxyFactory(address=existing_address)
|
||||
addresses = [
|
||||
"http://existing:8080", # Already exists
|
||||
"http://new:8080",
|
||||
existing_address, # Already exists
|
||||
new_address,
|
||||
]
|
||||
|
||||
created = ProxyService.add_proxies(addresses)
|
||||
@@ -194,11 +204,12 @@ class ParserLoadLogServiceTest(TestCase):
|
||||
"""Test marking log as failed."""
|
||||
log = ParserLoadLogFactory(status="success")
|
||||
|
||||
ParserLoadLogService.mark_failed(log, "Connection error")
|
||||
error_message = fake.sentence(nb_words=4)
|
||||
ParserLoadLogService.mark_failed(log, error_message)
|
||||
|
||||
log.refresh_from_db()
|
||||
self.assertEqual(log.status, "failed")
|
||||
self.assertEqual(log.error_message, "Connection error")
|
||||
self.assertEqual(log.error_message, error_message)
|
||||
|
||||
def test_update_records_count(self):
|
||||
"""Test updating records count."""
|
||||
@@ -222,13 +233,13 @@ class IndustrialCertificateServiceTest(TestCase):
|
||||
"""Test saving certificates from dataclass."""
|
||||
certificates = [
|
||||
IndustrialCertificate(
|
||||
issue_date="2024-01-01",
|
||||
certificate_number=f"CERT-{i}",
|
||||
expiry_date="2025-01-01",
|
||||
certificate_file_url=f"https://example.com/cert{i}.pdf",
|
||||
organisation_name=f"Company {i}",
|
||||
inn=f"123456789{i}",
|
||||
ogrn=f"123456789012{i}",
|
||||
issue_date=str(fake.date()),
|
||||
certificate_number=fake.bothify(text="??-####-#####"),
|
||||
expiry_date=str(fake.date()),
|
||||
certificate_file_url=fake.url(),
|
||||
organisation_name=fake.company(),
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
)
|
||||
for i in range(5)
|
||||
]
|
||||
@@ -242,13 +253,13 @@ class IndustrialCertificateServiceTest(TestCase):
|
||||
"""Test saving certificates in chunks."""
|
||||
certificates = [
|
||||
IndustrialCertificate(
|
||||
issue_date="2024-01-01",
|
||||
certificate_number=f"CERT-{i}",
|
||||
expiry_date="2025-01-01",
|
||||
certificate_file_url=f"https://example.com/cert{i}.pdf",
|
||||
organisation_name=f"Company {i}",
|
||||
inn=f"12345678{i:02d}",
|
||||
ogrn=f"1234567890{i:03d}",
|
||||
issue_date=str(fake.date()),
|
||||
certificate_number=fake.bothify(text="??-####-#####"),
|
||||
expiry_date=str(fake.date()),
|
||||
certificate_file_url=fake.url(),
|
||||
organisation_name=fake.company(),
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
)
|
||||
for i in range(10)
|
||||
]
|
||||
@@ -261,44 +272,48 @@ class IndustrialCertificateServiceTest(TestCase):
|
||||
|
||||
def test_find_by_inn(self):
|
||||
"""Test finding certificates by INN."""
|
||||
inn_a = _digits(10)
|
||||
inn_b = _digits(10)
|
||||
IndustrialCertificateRecordFactory(
|
||||
inn="1111111111", certificate_number="CERT-A1", load_batch=1
|
||||
inn=inn_a, certificate_number=fake.bothify(text="CERT-####"), load_batch=1
|
||||
)
|
||||
IndustrialCertificateRecordFactory(
|
||||
inn="1111111111", certificate_number="CERT-A2", load_batch=2
|
||||
inn=inn_a, certificate_number=fake.bothify(text="CERT-####"), load_batch=2
|
||||
)
|
||||
IndustrialCertificateRecordFactory(
|
||||
inn="2222222222", certificate_number="CERT-B1", load_batch=1
|
||||
inn=inn_b, certificate_number=fake.bothify(text="CERT-####"), load_batch=1
|
||||
)
|
||||
|
||||
results = IndustrialCertificateService.find_by_inn("1111111111")
|
||||
results = IndustrialCertificateService.find_by_inn(inn_a)
|
||||
self.assertEqual(results.count(), 2)
|
||||
|
||||
results_batch1 = IndustrialCertificateService.find_by_inn(
|
||||
"1111111111", batch_id=1
|
||||
inn_a, batch_id=1
|
||||
)
|
||||
self.assertEqual(results_batch1.count(), 1)
|
||||
|
||||
def test_find_by_certificate_number(self):
|
||||
"""Test finding certificate by number."""
|
||||
IndustrialCertificateRecordFactory(certificate_number="CERT-UNIQUE")
|
||||
IndustrialCertificateRecordFactory(certificate_number="CERT-OTHER")
|
||||
unique_number = fake.bothify(text="CERT-#####")
|
||||
IndustrialCertificateRecordFactory(certificate_number=unique_number)
|
||||
IndustrialCertificateRecordFactory(certificate_number=fake.bothify(text="CERT-#####"))
|
||||
|
||||
results = IndustrialCertificateService.find_by_certificate_number("CERT-UNIQUE")
|
||||
results = IndustrialCertificateService.find_by_certificate_number(unique_number)
|
||||
self.assertEqual(results.count(), 1)
|
||||
|
||||
def test_save_certificates_deduplication(self):
|
||||
"""Test saving certificates skips duplicates by certificate_number."""
|
||||
# Create initial certificate
|
||||
cert_number = fake.bothify(text="CERT-DEDUP-#####")
|
||||
initial = [
|
||||
IndustrialCertificate(
|
||||
issue_date="2024-01-01",
|
||||
certificate_number="CERT-DEDUP-001",
|
||||
expiry_date="2025-01-01",
|
||||
certificate_file_url="https://example.com/old.pdf",
|
||||
organisation_name="Old Company Name",
|
||||
inn="1234567890",
|
||||
ogrn="1234567890123",
|
||||
issue_date=str(fake.date()),
|
||||
certificate_number=cert_number,
|
||||
expiry_date=str(fake.date()),
|
||||
certificate_file_url=fake.url(),
|
||||
organisation_name=fake.company(),
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
)
|
||||
]
|
||||
count1 = IndustrialCertificateService.save_certificates(initial, batch_id=1)
|
||||
@@ -308,13 +323,13 @@ class IndustrialCertificateServiceTest(TestCase):
|
||||
# Try to save with same certificate_number - should be skipped
|
||||
duplicate = [
|
||||
IndustrialCertificate(
|
||||
issue_date="2024-06-01",
|
||||
certificate_number="CERT-DEDUP-001", # Same number - will be skipped
|
||||
expiry_date="2026-01-01",
|
||||
certificate_file_url="https://example.com/new.pdf",
|
||||
organisation_name="New Company Name",
|
||||
inn="9999999999",
|
||||
ogrn="9999999999999",
|
||||
issue_date=str(fake.date()),
|
||||
certificate_number=cert_number, # Same number - will be skipped
|
||||
expiry_date=str(fake.date()),
|
||||
certificate_file_url=fake.url(),
|
||||
organisation_name=fake.company(),
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
)
|
||||
]
|
||||
IndustrialCertificateService.save_certificates(duplicate, batch_id=2)
|
||||
@@ -324,8 +339,8 @@ class IndustrialCertificateServiceTest(TestCase):
|
||||
|
||||
# Verify original data preserved
|
||||
record = IndustrialCertificateRecord.objects.first()
|
||||
self.assertEqual(record.organisation_name, "Old Company Name")
|
||||
self.assertEqual(record.inn, "1234567890")
|
||||
self.assertEqual(record.organisation_name, initial[0].organisation_name)
|
||||
self.assertEqual(record.inn, initial[0].inn)
|
||||
self.assertEqual(record.load_batch, 1) # Original batch
|
||||
|
||||
|
||||
@@ -341,10 +356,10 @@ class ManufacturerServiceTest(TestCase):
|
||||
"""Test saving manufacturers from dataclass."""
|
||||
manufacturers = [
|
||||
Manufacturer(
|
||||
full_legal_name=f"Company {i} LLC",
|
||||
inn=f"123456789{i}",
|
||||
ogrn=f"123456789012{i}",
|
||||
address=f"Address {i}",
|
||||
full_legal_name=fake.company(),
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
address=fake.address().replace("\n", ", "),
|
||||
)
|
||||
for i in range(5)
|
||||
]
|
||||
@@ -358,10 +373,10 @@ class ManufacturerServiceTest(TestCase):
|
||||
"""Test saving manufacturers in chunks."""
|
||||
manufacturers = [
|
||||
Manufacturer(
|
||||
full_legal_name=f"Company {i}",
|
||||
inn=f"12345678{i:02d}",
|
||||
ogrn=f"1234567890{i:03d}",
|
||||
address=f"Address {i}",
|
||||
full_legal_name=fake.company(),
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
address=fake.address().replace("\n", ", "),
|
||||
)
|
||||
for i in range(10)
|
||||
]
|
||||
@@ -374,41 +389,50 @@ class ManufacturerServiceTest(TestCase):
|
||||
|
||||
def test_find_by_inn(self):
|
||||
"""Test finding manufacturers by INN."""
|
||||
ManufacturerRecordFactory(inn="1111111111", load_batch=1)
|
||||
ManufacturerRecordFactory(inn="2222222222", load_batch=1)
|
||||
ManufacturerRecordFactory(inn="3333333333", load_batch=2)
|
||||
inn_target = _digits(10)
|
||||
inn_other = _digits(10)
|
||||
inn_third = _digits(10)
|
||||
ManufacturerRecordFactory(inn=inn_target, load_batch=1)
|
||||
ManufacturerRecordFactory(inn=inn_other, load_batch=1)
|
||||
ManufacturerRecordFactory(inn=inn_third, load_batch=2)
|
||||
|
||||
results = ManufacturerService.find_by_inn("1111111111")
|
||||
results = ManufacturerService.find_by_inn(inn_target)
|
||||
self.assertEqual(results.count(), 1)
|
||||
|
||||
def test_find_by_inn_with_batch_filter(self):
|
||||
"""Test finding manufacturers by INN with batch filter."""
|
||||
ManufacturerRecordFactory(inn="4444444444", load_batch=1)
|
||||
ManufacturerRecordFactory(inn="5555555555", load_batch=2)
|
||||
inn_value = _digits(10)
|
||||
ManufacturerRecordFactory(inn=inn_value, load_batch=1)
|
||||
ManufacturerRecordFactory(inn=_digits(10), load_batch=2)
|
||||
|
||||
results_batch1 = ManufacturerService.find_by_inn("4444444444", batch_id=1)
|
||||
results_batch1 = ManufacturerService.find_by_inn(inn_value, batch_id=1)
|
||||
self.assertEqual(results_batch1.count(), 1)
|
||||
|
||||
results_batch2 = ManufacturerService.find_by_inn("4444444444", batch_id=2)
|
||||
results_batch2 = ManufacturerService.find_by_inn(inn_value, batch_id=2)
|
||||
self.assertEqual(results_batch2.count(), 0)
|
||||
|
||||
def test_find_by_ogrn(self):
|
||||
"""Test finding manufacturers by OGRN."""
|
||||
ManufacturerRecordFactory(ogrn="1234567890123")
|
||||
ManufacturerRecordFactory(ogrn="9999999999999")
|
||||
ogrn_target = _digits(13)
|
||||
ManufacturerRecordFactory(ogrn=ogrn_target)
|
||||
ManufacturerRecordFactory(ogrn=_digits(13))
|
||||
|
||||
results = ManufacturerService.find_by_ogrn("1234567890123")
|
||||
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."""
|
||||
# Create initial manufacturer
|
||||
inn_value = _digits(10)
|
||||
ogrn_value = _digits(13)
|
||||
address_value = fake.address().replace("\n", ", ")
|
||||
company_name = fake.company()
|
||||
initial = [
|
||||
Manufacturer(
|
||||
full_legal_name="Old Company Name LLC",
|
||||
inn="7777777777",
|
||||
ogrn="1234567890123",
|
||||
address="Old Address",
|
||||
full_legal_name=company_name,
|
||||
inn=inn_value,
|
||||
ogrn=ogrn_value,
|
||||
address=address_value,
|
||||
)
|
||||
]
|
||||
count1 = ManufacturerService.save_manufacturers(initial, batch_id=1)
|
||||
@@ -418,10 +442,10 @@ class ManufacturerServiceTest(TestCase):
|
||||
# Try to save with same INN - should be skipped
|
||||
duplicate = [
|
||||
Manufacturer(
|
||||
full_legal_name="New Company Name LLC",
|
||||
inn="7777777777", # Same INN - will be skipped
|
||||
ogrn="9999999999999",
|
||||
address="New Address",
|
||||
full_legal_name=fake.company(),
|
||||
inn=inn_value, # Same INN - will be skipped
|
||||
ogrn=_digits(13),
|
||||
address=fake.address().replace("\n", ", "),
|
||||
)
|
||||
]
|
||||
ManufacturerService.save_manufacturers(duplicate, batch_id=2)
|
||||
@@ -431,9 +455,9 @@ class ManufacturerServiceTest(TestCase):
|
||||
|
||||
# Verify original data preserved
|
||||
record = ManufacturerRecord.objects.first()
|
||||
self.assertEqual(record.full_legal_name, "Old Company Name LLC")
|
||||
self.assertEqual(record.ogrn, "1234567890123")
|
||||
self.assertEqual(record.address, "Old Address")
|
||||
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
|
||||
|
||||
|
||||
@@ -449,18 +473,18 @@ class InspectionServiceTest(TestCase):
|
||||
"""Test saving inspections from dataclass."""
|
||||
inspections = [
|
||||
Inspection(
|
||||
registration_number=f"77202400000{i}",
|
||||
inn=f"770{i}234567",
|
||||
ogrn=f"102770000000{i}",
|
||||
organisation_name=f"Компания {i}",
|
||||
control_authority="Роспотребнадзор",
|
||||
inspection_type="плановая",
|
||||
inspection_form="документарная",
|
||||
start_date="2024-01-15",
|
||||
end_date="2024-01-30",
|
||||
status="завершена",
|
||||
legal_basis="294-ФЗ",
|
||||
result="нарушения не выявлены",
|
||||
registration_number=_digits(12),
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
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),
|
||||
)
|
||||
for i in range(5)
|
||||
]
|
||||
@@ -474,17 +498,17 @@ class InspectionServiceTest(TestCase):
|
||||
"""Test saving inspections in chunks."""
|
||||
inspections = [
|
||||
Inspection(
|
||||
registration_number=f"7720240000{i:02d}",
|
||||
inn=f"770{i:02d}34567",
|
||||
ogrn=f"10277000000{i:02d}",
|
||||
organisation_name=f"Компания {i}",
|
||||
control_authority="Ростехнадзор",
|
||||
inspection_type="внеплановая",
|
||||
inspection_form="выездная",
|
||||
start_date="2024-02-01",
|
||||
end_date="2024-02-15",
|
||||
status="завершена",
|
||||
legal_basis="248-ФЗ",
|
||||
registration_number=_digits(12),
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
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),
|
||||
)
|
||||
for i in range(10)
|
||||
]
|
||||
@@ -497,57 +521,74 @@ class InspectionServiceTest(TestCase):
|
||||
|
||||
def test_find_by_inn(self):
|
||||
"""Test finding inspections by INN."""
|
||||
InspectionRecordFactory(inn="1111111111", load_batch=1)
|
||||
InspectionRecordFactory(inn="1111111111", load_batch=2)
|
||||
InspectionRecordFactory(inn="2222222222", load_batch=1)
|
||||
inn_value = _digits(10)
|
||||
InspectionRecordFactory(inn=inn_value, load_batch=1)
|
||||
InspectionRecordFactory(inn=inn_value, load_batch=2)
|
||||
InspectionRecordFactory(inn=_digits(10), load_batch=1)
|
||||
|
||||
results = InspectionService.find_by_inn("1111111111")
|
||||
results = InspectionService.find_by_inn(inn_value)
|
||||
self.assertEqual(results.count(), 2)
|
||||
|
||||
results_batch1 = InspectionService.find_by_inn("1111111111", batch_id=1)
|
||||
results_batch1 = InspectionService.find_by_inn(inn_value, batch_id=1)
|
||||
self.assertEqual(results_batch1.count(), 1)
|
||||
|
||||
def test_find_by_registration_number(self):
|
||||
"""Test finding inspection by registration number."""
|
||||
InspectionRecordFactory(registration_number="772024000001")
|
||||
InspectionRecordFactory(registration_number="772024000002")
|
||||
target_number = _digits(12)
|
||||
other_number = _digits(12)
|
||||
InspectionRecordFactory(registration_number=target_number)
|
||||
InspectionRecordFactory(registration_number=other_number)
|
||||
|
||||
results = InspectionService.find_by_registration_number("772024000001")
|
||||
results = InspectionService.find_by_registration_number(target_number)
|
||||
self.assertEqual(results.count(), 1)
|
||||
|
||||
def test_find_by_control_authority(self):
|
||||
"""Test finding inspections by control authority."""
|
||||
InspectionRecordFactory(control_authority="Роспотребнадзор", load_batch=1)
|
||||
InspectionRecordFactory(
|
||||
control_authority="Управление Роспотребнадзора по г. Москве", load_batch=1
|
||||
)
|
||||
InspectionRecordFactory(control_authority="Ростехнадзор", load_batch=1)
|
||||
authority_key = fake.word()
|
||||
authority_match_1 = f"{fake.company()} {authority_key}"
|
||||
authority_match_2 = f"{authority_key} {fake.company()}"
|
||||
authority_other = fake.company()
|
||||
InspectionRecordFactory(control_authority=authority_match_1, load_batch=1)
|
||||
InspectionRecordFactory(control_authority=authority_match_2, load_batch=1)
|
||||
InspectionRecordFactory(control_authority=authority_other, load_batch=1)
|
||||
|
||||
results = InspectionService.find_by_control_authority("Роспотребнадзор")
|
||||
results = InspectionService.find_by_control_authority(authority_key)
|
||||
self.assertEqual(results.count(), 2)
|
||||
|
||||
results_batch1 = InspectionService.find_by_control_authority(
|
||||
"Роспотребнадзор", batch_id=1
|
||||
authority_key, batch_id=1
|
||||
)
|
||||
self.assertEqual(results_batch1.count(), 2)
|
||||
|
||||
def test_save_inspections_deduplication(self):
|
||||
"""Test saving inspections skips duplicates by registration_number."""
|
||||
# Create initial inspection
|
||||
reg_number = _digits(12)
|
||||
inn_value = _digits(10)
|
||||
ogrn_value = _digits(13)
|
||||
org_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_text = fake.sentence(nb_words=3)
|
||||
initial = [
|
||||
Inspection(
|
||||
registration_number="DEDUP-REG-001",
|
||||
inn="1234567890",
|
||||
ogrn="1234567890123",
|
||||
organisation_name="Old Organisation",
|
||||
control_authority="Роспотребнадзор",
|
||||
inspection_type="плановая",
|
||||
inspection_form="документарная",
|
||||
start_date="2024-01-01",
|
||||
end_date="2024-01-15",
|
||||
status="завершена",
|
||||
legal_basis="294-ФЗ",
|
||||
result="нарушения не выявлены",
|
||||
registration_number=reg_number,
|
||||
inn=inn_value,
|
||||
ogrn=ogrn_value,
|
||||
organisation_name=org_name,
|
||||
control_authority=control_authority,
|
||||
inspection_type=inspection_type,
|
||||
inspection_form=inspection_form,
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
status=status,
|
||||
legal_basis=legal_basis,
|
||||
result=result_text,
|
||||
)
|
||||
]
|
||||
count1 = InspectionService.save_inspections(initial, batch_id=1)
|
||||
@@ -557,18 +598,18 @@ class InspectionServiceTest(TestCase):
|
||||
# Try to save with same registration_number - should be skipped
|
||||
duplicate = [
|
||||
Inspection(
|
||||
registration_number="DEDUP-REG-001", # Same number - will be skipped
|
||||
inn="9999999999",
|
||||
ogrn="9999999999999",
|
||||
organisation_name="New Organisation",
|
||||
control_authority="Ростехнадзор",
|
||||
inspection_type="внеплановая",
|
||||
inspection_form="выездная",
|
||||
start_date="2024-06-01",
|
||||
end_date="2024-06-30",
|
||||
status="в процессе",
|
||||
legal_basis="248-ФЗ",
|
||||
result="выявлены нарушения",
|
||||
registration_number=reg_number, # Same number - will be skipped
|
||||
inn=_digits(10),
|
||||
ogrn=_digits(13),
|
||||
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),
|
||||
)
|
||||
]
|
||||
InspectionService.save_inspections(duplicate, batch_id=2)
|
||||
@@ -578,19 +619,14 @@ class InspectionServiceTest(TestCase):
|
||||
|
||||
# Verify original data preserved
|
||||
record = InspectionRecord.objects.first()
|
||||
self.assertEqual(record.organisation_name, "Old Organisation")
|
||||
self.assertEqual(record.inn, "1234567890")
|
||||
self.assertEqual(record.control_authority, "Роспотребнадзор")
|
||||
self.assertEqual(record.status, "завершена")
|
||||
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
|
||||
|
||||
|
||||
from apps.parsers.clients.base import HTTPClientError
|
||||
from apps.parsers.clients.minpromtorg.industrial import IndustrialProductionClient
|
||||
from django.test import tag
|
||||
|
||||
|
||||
@tag("integration", "slow", "network", "e2e")
|
||||
@tag("integration", "slow", "e2e")
|
||||
class EndToEndIntegrationTest(TestCase):
|
||||
"""
|
||||
End-to-end интеграционные тесты полного flow.
|
||||
@@ -608,72 +644,96 @@ class EndToEndIntegrationTest(TestCase):
|
||||
3. Сохраняем первые N записей в БД
|
||||
4. Проверяем что данные корректно сохранились
|
||||
"""
|
||||
try:
|
||||
# 1. Загружаем данные с API
|
||||
print("\n[E2E] Step 1: Fetching certificates from API...")
|
||||
with IndustrialProductionClient(timeout=120) as client:
|
||||
# 1. Загружаем данные через локальный HTTP сервер (без внешнего API)
|
||||
print("\n[E2E] Step 1: Fetching certificates from local API...")
|
||||
excel_bytes, rows = build_minpromtorg_certificates_excel(count=5)
|
||||
date_str = fake.date_between(start_date="-30d", end_date="today").strftime(
|
||||
"%Y%m%d"
|
||||
)
|
||||
file_name = f"data_resolutions_{date_str}.xlsx"
|
||||
|
||||
with TestHTTPServer() as server:
|
||||
server.add_json(
|
||||
"/api/kss-document-preview",
|
||||
{
|
||||
"data": [
|
||||
{
|
||||
"name": IndustrialProductionClient().query,
|
||||
"files": [
|
||||
{"name": file_name, "url": f"/files/{file_name}"}
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
)
|
||||
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
|
||||
)
|
||||
with IndustrialProductionClient(
|
||||
host=client_host,
|
||||
scheme="http",
|
||||
timeout=30,
|
||||
http_adapter=server.adapter,
|
||||
) as client:
|
||||
all_certificates = client.fetch_certificates()
|
||||
|
||||
if not all_certificates:
|
||||
self.skipTest("No certificates returned from API")
|
||||
self.assertEqual(len(all_certificates), len(rows))
|
||||
print(f"[E2E] Loaded {len(all_certificates)} certificates from local API")
|
||||
|
||||
print(f"[E2E] Loaded {len(all_certificates)} certificates from API")
|
||||
# Берём все для теста
|
||||
certificates = all_certificates
|
||||
|
||||
# Берём только первые 100 для теста
|
||||
certificates = all_certificates[:100]
|
||||
# 2. Создаём batch_id и лог
|
||||
print("[E2E] Step 2: Creating load log...")
|
||||
batch_id = ParserLoadLogService.get_next_batch_id(
|
||||
ParserLoadLog.Source.INDUSTRIAL
|
||||
)
|
||||
log = ParserLoadLogService.create_load_log(
|
||||
source=ParserLoadLog.Source.INDUSTRIAL,
|
||||
batch_id=batch_id,
|
||||
records_count=0,
|
||||
)
|
||||
print(f"[E2E] Created batch_id={batch_id}")
|
||||
|
||||
# 2. Создаём batch_id и лог
|
||||
print("[E2E] Step 2: Creating load log...")
|
||||
batch_id = ParserLoadLogService.get_next_batch_id(
|
||||
ParserLoadLog.Source.INDUSTRIAL
|
||||
)
|
||||
log = ParserLoadLogService.create_load_log(
|
||||
source=ParserLoadLog.Source.INDUSTRIAL,
|
||||
batch_id=batch_id,
|
||||
records_count=0,
|
||||
)
|
||||
print(f"[E2E] Created batch_id={batch_id}")
|
||||
# 3. Сохраняем в БД
|
||||
print("[E2E] Step 3: Saving certificates to database...")
|
||||
saved_count = IndustrialCertificateService.save_certificates(
|
||||
certificates, batch_id=batch_id
|
||||
)
|
||||
ParserLoadLogService.update_records_count(log, saved_count)
|
||||
|
||||
# 3. Сохраняем в БД
|
||||
print("[E2E] Step 3: Saving certificates to database...")
|
||||
saved_count = IndustrialCertificateService.save_certificates(
|
||||
certificates, batch_id=batch_id
|
||||
)
|
||||
ParserLoadLogService.update_records_count(log, saved_count)
|
||||
print(f"[E2E] Saved {saved_count} certificates")
|
||||
|
||||
print(f"[E2E] Saved {saved_count} certificates")
|
||||
# 4. Проверяем результат
|
||||
print("[E2E] Step 4: Verifying saved data...")
|
||||
|
||||
# 4. Проверяем результат
|
||||
print("[E2E] Step 4: Verifying saved data...")
|
||||
# Проверяем количество
|
||||
db_count = IndustrialCertificateRecord.objects.filter(
|
||||
load_batch=batch_id
|
||||
).count()
|
||||
self.assertEqual(db_count, saved_count)
|
||||
self.assertEqual(db_count, len(certificates))
|
||||
|
||||
# Проверяем количество
|
||||
db_count = IndustrialCertificateRecord.objects.filter(
|
||||
load_batch=batch_id
|
||||
).count()
|
||||
self.assertEqual(db_count, saved_count)
|
||||
self.assertEqual(db_count, len(certificates))
|
||||
# Проверяем первую запись
|
||||
first_cert = certificates[0]
|
||||
db_record = IndustrialCertificateRecord.objects.filter(
|
||||
load_batch=batch_id,
|
||||
certificate_number=first_cert.certificate_number,
|
||||
).first()
|
||||
|
||||
# Проверяем первую запись
|
||||
first_cert = certificates[0]
|
||||
db_record = IndustrialCertificateRecord.objects.filter(
|
||||
load_batch=batch_id,
|
||||
certificate_number=first_cert.certificate_number,
|
||||
).first()
|
||||
self.assertIsNotNone(db_record)
|
||||
self.assertEqual(db_record.inn, first_cert.inn)
|
||||
self.assertEqual(db_record.ogrn, first_cert.ogrn)
|
||||
self.assertEqual(db_record.organisation_name, first_cert.organisation_name)
|
||||
|
||||
self.assertIsNotNone(db_record)
|
||||
self.assertEqual(db_record.inn, first_cert.inn)
|
||||
self.assertEqual(db_record.ogrn, first_cert.ogrn)
|
||||
self.assertEqual(db_record.organisation_name, first_cert.organisation_name)
|
||||
# Проверяем лог
|
||||
log.refresh_from_db()
|
||||
self.assertEqual(log.records_count, saved_count)
|
||||
self.assertEqual(log.status, "success")
|
||||
|
||||
# Проверяем лог
|
||||
log.refresh_from_db()
|
||||
self.assertEqual(log.records_count, saved_count)
|
||||
self.assertEqual(log.status, "success")
|
||||
|
||||
print("[E2E] ✅ All checks passed!")
|
||||
print(f"[E2E] Sample record: {db_record.certificate_number}")
|
||||
print(f"[E2E] Organisation: {db_record.organisation_name}")
|
||||
print(f"[E2E] INN: {db_record.inn}, OGRN: {db_record.ogrn}")
|
||||
|
||||
except HTTPClientError as e:
|
||||
self.skipTest(f"External API unavailable: {e}")
|
||||
print("[E2E] ✅ All checks passed!")
|
||||
print(f"[E2E] Sample record: {db_record.certificate_number}")
|
||||
print(f"[E2E] Organisation: {db_record.organisation_name}")
|
||||
print(f"[E2E] INN: {db_record.inn}, OGRN: {db_record.ogrn}")
|
||||
|
||||
Reference in New Issue
Block a user