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

- Обновлены клиенты парсеров (checko, fns, minpromtorg, proverki, zakupki)
- Добавлены новые миграции для моделей
- Расширено покрытие тестами
- Обновлены конфигурации и настройки проекта
- Добавлены утилиты для тестирования

Co-Authored-By: Warp <agent@warp.dev>
This commit is contained in:
2026-02-10 10:17:47 +01:00
parent 975d019ba5
commit ee95628a0a
59 changed files with 7292 additions and 2876 deletions

View File

@@ -1,9 +1,12 @@
"""Factories for parsers tests."""
"""Factories for parsers tests (Faker-based)."""
from __future__ import annotations
import random
from datetime import timedelta
import factory
from faker import Faker
from apps.parsers.models import (
IndustrialCertificateRecord,
InspectionRecord,
@@ -13,158 +16,56 @@ from apps.parsers.models import (
)
from django.utils import timezone
fake = Faker("ru_RU")
# === Хелперы для генерации реалистичных данных ===
def _digits(length: int) -> str:
return "".join(str(fake.random_int(0, 9)) for _ in range(length))
def generate_inn_legal() -> str:
"""Генерация ИНН юридического лица (10 цифр)."""
# ИНН юрлица: NNNNXXXXXC (10 цифр)
# NNNN - код налогового органа
# XXXXX - порядковый номер
# C - контрольная цифра
region = random.choice(["77", "78", "50", "52", "63", "16", "66", "74", "54", "61"])
inspection = str(random.randint(1, 99)).zfill(2)
number = str(random.randint(1, 99999)).zfill(5)
base = region + inspection + number
# Контрольная цифра (упрощённо)
control = str(sum(int(d) for d in base) % 10)
return base + control
return _digits(10)
def generate_ogrn() -> str:
"""Генерация ОГРН юридического лица (13 цифр)."""
# ОГРН: СГГККННХХХХХЧ (13 цифр)
# С - признак (1 - юрлицо)
# ГГ - год регистрации
# КК - код региона
# НН - код инспекции
# ХХХХХ - номер записи
# Ч - контрольная цифра
sign = "1"
year = str(random.randint(2, 24)).zfill(2)
region = random.choice(["77", "78", "50", "52", "63", "16", "66", "74", "54", "61"])
inspection = str(random.randint(1, 99)).zfill(2)
number = str(random.randint(1, 99999)).zfill(5)
base = sign + year + region + inspection + number
# Контрольная цифра: остаток от деления на 11, если 10 - то 0
control = str(int(base) % 11 % 10)
return base + control
return _digits(13)
def generate_certificate_number() -> str:
"""Генерация номера сертификата промпроизводства."""
# Формат: ПП-XXXXXXXXXX или аналогичный
prefix = random.choice(["ПП", "СПП", "ЗППП"])
year = random.randint(2020, 2025)
number = random.randint(1, 99999)
return f"{prefix}-{year}-{number:05d}"
prefix = fake.random_element(["ПП", "СПП", "ЗППП"])
year = fake.random_int(min=2020, max=2025)
number = _digits(5)
return f"{prefix}-{year}-{number}"
def generate_company_name() -> str:
"""Генерация реалистичного названия компании."""
forms = ["ООО", "АО", "ПАО", "ЗАО", "ОАО"]
industries = [
"Металлург",
"Промтех",
"Машстрой",
"Агропром",
"Нефтегаз",
"Химпром",
"Электроника",
"Автоком",
"Стройинвест",
"Техносервис",
"Приборостроение",
"Энергомаш",
"Станкопром",
"Спецсталь",
"Трубопрокат",
]
suffixes = ["", " Групп", " Холдинг", " Инвест", " Трейд", " Индустрия", " Про"]
cities = [
"Москва",
"Санкт-Петербург",
"Новосибирск",
"Екатеринбург",
"Казань",
"Челябинск",
]
form = random.choice(forms)
industry = random.choice(industries)
suffix = random.choice(suffixes)
city = random.choice(cities) if random.random() > 0.7 else ""
name = f"{industry}{suffix}"
if city:
name = f"{name}-{city}"
return f'{form} "{name}"'
"""Генерация названия компании."""
return fake.company()
def generate_legal_address() -> str:
"""Генерация юридического адреса."""
regions = [
("г. Москва", ""),
("г. Санкт-Петербург", ""),
("Московская обл.", "г. Подольск"),
("Свердловская обл.", "г. Екатеринбург"),
("Республика Татарстан", "г. Казань"),
("Челябинская обл.", "г. Челябинск"),
("Новосибирская обл.", "г. Новосибирск"),
("Нижегородская обл.", "г. Нижний Новгород"),
]
region, city = random.choice(regions)
street_types = ["ул.", "пр-т", "пер.", "наб.", "ш."]
street_names = [
"Ленина",
"Мира",
"Советская",
"Промышленная",
"Заводская",
"Первомайская",
"Октябрьская",
"Гагарина",
"Кирова",
"Строителей",
]
street = f"{random.choice(street_types)} {random.choice(street_names)}"
building = random.randint(1, 150)
office = random.randint(1, 500) if random.random() > 0.5 else None
postal = f"{random.randint(100, 199)}0{random.randint(10, 99)}"
parts = [postal, region]
if city:
parts.append(city)
parts.append(f"{street}, д. {building}")
if office:
parts.append(f"оф. {office}")
return ", ".join(parts)
return fake.address().replace("\n", ", ")
def generate_proxy_address() -> str:
"""Генерация адреса прокси-сервера."""
protocols = ["http", "https", "socks5"]
hosts = [
f"{random.randint(1, 255)}.{random.randint(1, 255)}."
f"{random.randint(1, 255)}.{random.randint(1, 255)}",
f"proxy{random.randint(1, 50)}.example.com",
f"ru{random.randint(1, 20)}.proxy-service.net",
]
ports = [8080, 3128, 8888, 1080, 8000, 9050]
protocol = random.choice(protocols)
host = random.choice(hosts)
port = random.choice(ports)
return f"{protocol}://{host}:{port}"
"""Генерация адреса прокси."""
return f"http://{fake.ipv4()}:{fake.port_number()}"
# === Фабрики ===
def generate_registration_number() -> str:
"""Генерация номера регистрации проверки."""
return f"{fake.random_int(min=1, max=99)}{fake.random_int(min=2020, max=2025)}{_digits(6)}"
def generate_control_authority() -> str:
"""Генерация названия контролирующего органа."""
return fake.company()
class ProxyFactory(factory.django.DjangoModelFactory):
@@ -174,19 +75,11 @@ class ProxyFactory(factory.django.DjangoModelFactory):
model = Proxy
address = factory.LazyFunction(generate_proxy_address)
description = factory.LazyAttribute(lambda _: fake.sentence(nb_words=3))
is_active = True
fail_count = 0
description = factory.LazyAttribute(
lambda _: random.choice(
[
"Datacenter RU",
"Residential RU",
"Mobile RU",
"Datacenter EU",
"Premium proxy",
"Backup proxy",
]
)
last_used_at = factory.LazyAttribute(
lambda _: timezone.now() - timedelta(hours=fake.random_int(min=1, max=72))
)
@@ -198,14 +91,9 @@ class ParserLoadLogFactory(factory.django.DjangoModelFactory):
batch_id = factory.Sequence(lambda n: n + 1)
source = factory.LazyAttribute(
lambda _: random.choice(
[
ParserLoadLog.Source.INDUSTRIAL,
ParserLoadLog.Source.MANUFACTURES,
]
)
lambda _: fake.random_element([s[0] for s in ParserLoadLog.Source.choices])
)
records_count = factory.LazyAttribute(lambda _: random.randint(100, 5000))
records_count = factory.LazyAttribute(lambda _: fake.random_int(min=0, max=5000))
status = "success"
error_message = ""
@@ -217,21 +105,10 @@ class IndustrialCertificateRecordFactory(factory.django.DjangoModelFactory):
model = IndustrialCertificateRecord
load_batch = factory.Sequence(lambda n: n + 1)
issue_date = factory.LazyAttribute(
lambda _: (timezone.now() - timedelta(days=random.randint(30, 365))).strftime(
"%d.%m.%Y"
)
)
issue_date = factory.LazyAttribute(lambda _: str(fake.date()))
certificate_number = factory.LazyFunction(generate_certificate_number)
expiry_date = factory.LazyAttribute(
lambda _: (timezone.now() + timedelta(days=random.randint(180, 730))).strftime(
"%d.%m.%Y"
)
)
certificate_file_url = factory.LazyAttribute(
lambda obj: f"https://minpromtorg.gov.ru/docs/certificates/"
f"{obj.certificate_number.replace('-', '_')}.pdf"
)
expiry_date = factory.LazyAttribute(lambda _: str(fake.date()))
certificate_file_url = factory.LazyAttribute(lambda _: fake.url())
organisation_name = factory.LazyFunction(generate_company_name)
inn = factory.LazyFunction(generate_inn_legal)
ogrn = factory.LazyFunction(generate_ogrn)
@@ -250,58 +127,6 @@ class ManufacturerRecordFactory(factory.django.DjangoModelFactory):
address = factory.LazyFunction(generate_legal_address)
def generate_registration_number() -> str:
"""Генерация учётного номера проверки."""
# Формат: 772020123456 или подобный
region = random.choice(["77", "78", "50", "52", "63", "16", "66", "74", "54", "61"])
year = random.randint(2020, 2025)
number = random.randint(1, 999999)
return f"{region}{year}{number:06d}"
def generate_control_authority() -> str:
"""Генерация наименования контрольного органа."""
authorities = [
"Роспотребнадзор",
"Ростехнадзор",
"Росприроднадзор",
"МЧС России",
"Роструд",
"ФНС России",
"ФАС России",
"Россельхознадзор",
"Роскомнадзор",
"Росздравнадзор",
]
prefixes = [
"Управление",
"Территориальное управление",
"Межрегиональное управление",
"Отдел",
]
regions = [
"по г. Москве",
"по Санкт-Петербургу",
"по Московской области",
"по Свердловской области",
"по Республике Татарстан",
"по Челябинской области",
"по Новосибирской области",
]
authority = random.choice(authorities)
prefix = random.choice(prefixes) if random.random() > 0.3 else ""
region = random.choice(regions) if random.random() > 0.4 else ""
if prefix and region:
return f"{prefix} {authority} {region}"
elif prefix:
return f"{prefix} {authority}"
elif region:
return f"{authority} {region}"
return authority
class InspectionRecordFactory(factory.django.DjangoModelFactory):
"""Factory for InspectionRecord model."""
@@ -314,32 +139,10 @@ class InspectionRecordFactory(factory.django.DjangoModelFactory):
ogrn = factory.LazyFunction(generate_ogrn)
organisation_name = factory.LazyFunction(generate_company_name)
control_authority = factory.LazyFunction(generate_control_authority)
inspection_type = factory.LazyAttribute(
lambda _: random.choice(["плановая", "внеплановая"])
)
inspection_form = factory.LazyAttribute(
lambda _: random.choice(
["документарная", "выездная", "документарная и выездная"]
)
)
start_date = factory.LazyAttribute(
lambda _: (timezone.now() - timedelta(days=random.randint(1, 180))).strftime(
"%Y-%m-%d"
)
)
end_date = factory.LazyAttribute(
lambda _: (timezone.now() + timedelta(days=random.randint(1, 30))).strftime(
"%Y-%m-%d"
)
)
status = factory.LazyAttribute(
lambda _: random.choice(["завершена", "в процессе", "запланирована"])
)
legal_basis = factory.LazyAttribute(
lambda _: random.choice(["294-ФЗ", "248-ФЗ", "184-ФЗ"])
)
result = factory.LazyAttribute(
lambda _: random.choice(["нарушения не выявлены", "выявлены нарушения", ""])
if random.random() > 0.3
else ""
)
inspection_type = factory.LazyAttribute(lambda _: fake.word())
inspection_form = factory.LazyAttribute(lambda _: fake.word())
start_date = factory.LazyAttribute(lambda _: str(fake.date()))
end_date = factory.LazyAttribute(lambda _: str(fake.date()))
status = factory.LazyAttribute(lambda _: fake.word())
legal_basis = factory.LazyAttribute(lambda _: fake.sentence(nb_words=4))
result = factory.LazyAttribute(lambda _: fake.sentence(nb_words=3))