fix(lint): resolve ruff errors in tests and run_tests.py

- Fix import sorting (I001)
- Remove unused imports and variables (F401, F841)
- Add noqa for test code (S106 hardcoded passwords, S314 XML parsing)
- Auto-format with ruff format
This commit is contained in:
2026-02-02 12:44:37 +01:00
parent 97a7764155
commit 3f222a9141
12 changed files with 87 additions and 70 deletions

View File

@@ -17,6 +17,12 @@
- `docker/build-push-action@v5` → чистые `docker build/push` команды - `docker/build-push-action@v5` → чистые `docker build/push` команды
- Тесты используют `config.settings.test` (SQLite in-memory) вместо PostgreSQL service - Тесты используют `config.settings.test` (SQLite in-memory) вместо PostgreSQL service
#### Code Quality
- Исправлены ошибки ruff lint:
- Сортировка импортов (I001)
- Удалены неиспользуемые импорты и переменные (F401, F841)
- Добавлены noqa для тестового кода (S106, S314)
--- ---
## [0.4.0] - 2026-01-28 ## [0.4.0] - 2026-01-28

View File

@@ -5,10 +5,9 @@
Поддерживает coverage и дополнительные опции Поддерживает coverage и дополнительные опции
""" """
import argparse
import os import os
import sys import sys
from io import StringIO
import argparse
import django import django
@@ -59,51 +58,47 @@ def run_tests_with_args(test_args, options):
def parse_arguments(): def parse_arguments():
"""Парсинг аргументов командной строки""" """Парсинг аргументов командной строки"""
parser = argparse.ArgumentParser(description="Запуск Django тестов с дополнительными возможностями") parser = argparse.ArgumentParser(
description="Запуск Django тестов с дополнительными возможностями"
)
parser.add_argument( parser.add_argument(
"targets", "targets",
nargs="*", nargs="*",
help="Цели тестирования (по умолчанию: все тесты)", help="Цели тестирования (по умолчанию: все тесты)",
default=["tests"] default=["tests"],
) )
parser.add_argument( parser.add_argument(
"--coverage", "--cov", "--coverage",
"--cov",
action="store_true", action="store_true",
help="Запуск тестов с измерением покрытия кода" help="Запуск тестов с измерением покрытия кода",
) )
parser.add_argument( parser.add_argument(
"--fast", "--fast",
action="store_true", action="store_true",
help="Запуск только быстрых тестов (исключает медленные)" help="Запуск только быстрых тестов (исключает медленные)",
) )
parser.add_argument( parser.add_argument(
"--failfast", "--failfast", action="store_true", help="Остановка при первой ошибке"
action="store_true",
help="Остановка при первой ошибке"
) )
parser.add_argument( parser.add_argument(
"--verbose", "-v", "--verbose", "-v", action="count", default=2, help="Уровень детализации вывода"
action="count",
default=2,
help="Уровень детализации вывода"
) )
parser.add_argument( parser.add_argument(
"--keepdb", "--keepdb", action="store_true", help="Сохранить тестовую базу данных"
action="store_true",
help="Сохранить тестовую базу данных"
) )
parser.add_argument( parser.add_argument(
"--parallel", "--parallel",
type=int, type=int,
metavar="N", metavar="N",
help="Запуск тестов в N параллельных процессах" help="Запуск тестов в N параллельных процессах",
) )
args = parser.parse_args() args = parser.parse_args()
@@ -186,6 +181,7 @@ def setup_coverage():
"""Настройка coverage""" """Настройка coverage"""
try: try:
import coverage import coverage
cov = coverage.Coverage(config_file="pyproject.toml") cov = coverage.Coverage(config_file="pyproject.toml")
cov.start() cov.start()
return cov return cov
@@ -245,7 +241,7 @@ def main():
print(f"\n❌ Тесты завершились с ошибками: {failures} неудачных тестов") print(f"\n❌ Тесты завершились с ошибками: {failures} неудачных тестов")
sys.exit(1) sys.exit(1)
else: else:
print(f"\nВсе тесты прошли успешно!") print("\nВсе тесты прошли успешно!")
if cov: if cov:
print("📊 Отчет о покрытии сохранен") print("📊 Отчет о покрытии сохранен")
sys.exit(0) sys.exit(0)
@@ -260,6 +256,7 @@ def main():
if cov: if cov:
cov.stop() cov.stop()
import traceback import traceback
traceback.print_exc() traceback.print_exc()
sys.exit(1) sys.exit(1)

View File

@@ -187,7 +187,7 @@ class BackgroundJobServiceTest(TestCase):
def test_get_user_jobs_with_status_filter(self): def test_get_user_jobs_with_status_filter(self):
"""Тест фильтрации по статусу.""" """Тест фильтрации по статусу."""
user_id = 456 user_id = 456
job1 = BackgroundJobService.create_job( BackgroundJobService.create_job(
task_id="task-pending", task_id="task-pending",
task_name="test.task", task_name="test.task",
user_id=user_id, user_id=user_id,
@@ -212,7 +212,7 @@ class BackgroundJobServiceTest(TestCase):
def test_get_active_jobs(self): def test_get_active_jobs(self):
"""Тест получения активных задач.""" """Тест получения активных задач."""
# Создаём задачи с разными статусами # Создаём задачи с разными статусами
job_pending = BackgroundJobService.create_job( BackgroundJobService.create_job(
task_id="job-active-pending", task_id="job-active-pending",
task_name="test.task", task_name="test.task",
) )

View File

@@ -71,6 +71,7 @@ class BulkOperationsIntegrationTest(TestCase):
def test_bulk_create_chunked(self): def test_bulk_create_chunked(self):
"""Тест массового создания чанками.""" """Тест массового создания чанками."""
# Создаём тестовый сервис с BulkOperationsMixin # Создаём тестовый сервис с BulkOperationsMixin
class TestService(BulkOperationsMixin): class TestService(BulkOperationsMixin):
model = BackgroundJob model = BackgroundJob
@@ -88,10 +89,13 @@ class BulkOperationsIntegrationTest(TestCase):
self.assertEqual(count, 10) self.assertEqual(count, 10)
# Проверяем что все созданы # Проверяем что все созданы
self.assertEqual(BackgroundJob.objects.filter(task_name="test.bulk.task").count(), 10) self.assertEqual(
BackgroundJob.objects.filter(task_name="test.bulk.task").count(), 10
)
def test_bulk_delete(self): def test_bulk_delete(self):
"""Тест массового удаления.""" """Тест массового удаления."""
class TestService(BulkOperationsMixin): class TestService(BulkOperationsMixin):
model = BackgroundJob model = BackgroundJob
@@ -109,10 +113,13 @@ class BulkOperationsIntegrationTest(TestCase):
deleted = TestService.bulk_delete(ids_to_delete) deleted = TestService.bulk_delete(ids_to_delete)
self.assertEqual(deleted, 3) self.assertEqual(deleted, 3)
self.assertEqual(BackgroundJob.objects.filter(task_name="test.delete.task").count(), 2) self.assertEqual(
BackgroundJob.objects.filter(task_name="test.delete.task").count(), 2
)
def test_bulk_update_fields(self): def test_bulk_update_fields(self):
"""Тест массового обновления полей.""" """Тест массового обновления полей."""
class TestService(BulkOperationsMixin): class TestService(BulkOperationsMixin):
model = BackgroundJob model = BackgroundJob
@@ -138,6 +145,7 @@ class BulkOperationsIntegrationTest(TestCase):
def test_bulk_update_or_create_creates(self): def test_bulk_update_or_create_creates(self):
"""Тест upsert - создание новых.""" """Тест upsert - создание новых."""
class TestService(BulkOperationsMixin): class TestService(BulkOperationsMixin):
model = BackgroundJob model = BackgroundJob
@@ -157,6 +165,7 @@ class BulkOperationsIntegrationTest(TestCase):
def test_bulk_update_or_create_updates(self): def test_bulk_update_or_create_updates(self):
"""Тест upsert - обновление существующих.""" """Тест upsert - обновление существующих."""
class TestService(BulkOperationsMixin): class TestService(BulkOperationsMixin):
model = BackgroundJob model = BackgroundJob

View File

@@ -101,9 +101,7 @@ class APIPaginatedResponseTest(TestCase):
def test_paginated_response(self): def test_paginated_response(self):
"""Test paginated response with correct metadata""" """Test paginated response with correct metadata"""
data = [{"id": 1}, {"id": 2}] data = [{"id": 1}, {"id": 2}]
response = api_paginated_response( response = api_paginated_response(data, page=1, page_size=10, total_count=25)
data, page=1, page_size=10, total_count=25
)
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["data"], data) self.assertEqual(response.data["data"], data)

View File

@@ -21,7 +21,7 @@ class BaseServiceTest(TestCase):
self.user = User.objects.create_user( self.user = User.objects.create_user(
username="testuser", username="testuser",
email="test@example.com", email="test@example.com",
password="testpass123", password="testpass123", # noqa: S106
) )
def test_get_by_id_success(self): def test_get_by_id_success(self):
@@ -53,7 +53,7 @@ class BaseServiceTest(TestCase):
User.objects.create_user( User.objects.create_user(
username="testuser2", username="testuser2",
email="test2@example.com", email="test2@example.com",
password="testpass123", password="testpass123", # noqa: S106
) )
result = UserTestService.get_all() result = UserTestService.get_all()

View File

@@ -88,7 +88,7 @@ class SignalDispatcherTest(TestCase):
self.dispatcher.connect_all() self.dispatcher.connect_all()
# Create user to trigger signal # Create user to trigger signal
user = UserFactory.create_user() UserFactory.create_user()
self.assertTrue(handler_called["value"]) self.assertTrue(handler_called["value"])
@@ -114,7 +114,7 @@ class SignalDispatcherTest(TestCase):
# Create user - handler should not be called # Create user - handler should not be called
handler_called["value"] = False handler_called["value"] = False
user = UserFactory.create_user() UserFactory.create_user()
self.assertFalse(handler_called["value"]) self.assertFalse(handler_called["value"])

View File

@@ -3,10 +3,7 @@
import random import random
from datetime import timedelta from datetime import timedelta
from django.utils import timezone
import factory import factory
from apps.parsers.models import ( from apps.parsers.models import (
IndustrialCertificateRecord, IndustrialCertificateRecord,
InspectionRecord, InspectionRecord,
@@ -14,6 +11,7 @@ from apps.parsers.models import (
ParserLoadLog, ParserLoadLog,
Proxy, Proxy,
) )
from django.utils import timezone
# === Хелперы для генерации реалистичных данных === # === Хелперы для генерации реалистичных данных ===
@@ -320,7 +318,9 @@ class InspectionRecordFactory(factory.django.DjangoModelFactory):
lambda _: random.choice(["плановая", "внеплановая"]) lambda _: random.choice(["плановая", "внеплановая"])
) )
inspection_form = factory.LazyAttribute( inspection_form = factory.LazyAttribute(
lambda _: random.choice(["документарная", "выездная", "документарная и выездная"]) lambda _: random.choice(
["документарная", "выездная", "документарная и выездная"]
)
) )
start_date = factory.LazyAttribute( start_date = factory.LazyAttribute(
lambda _: (timezone.now() - timedelta(days=random.randint(1, 180))).strftime( lambda _: (timezone.now() - timedelta(days=random.randint(1, 180))).strftime(
@@ -339,9 +339,7 @@ class InspectionRecordFactory(factory.django.DjangoModelFactory):
lambda _: random.choice(["294-ФЗ", "248-ФЗ", "184-ФЗ"]) lambda _: random.choice(["294-ФЗ", "248-ФЗ", "184-ФЗ"])
) )
result = factory.LazyAttribute( result = factory.LazyAttribute(
lambda _: random.choice( lambda _: random.choice(["нарушения не выявлены", "выявлены нарушения", ""])
["нарушения не выявлены", "выявлены нарушения", ""]
)
if random.random() > 0.3 if random.random() > 0.3
else "" else ""
) )

View File

@@ -3,17 +3,15 @@
from io import BytesIO from io import BytesIO
from unittest.mock import patch from unittest.mock import patch
from django.test import TestCase, tag
from faker import Faker
from openpyxl import Workbook
from apps.parsers.clients.base import BaseHTTPClient, HTTPClientError from apps.parsers.clients.base import BaseHTTPClient, HTTPClientError
from apps.parsers.clients.minpromtorg.industrial import IndustrialProductionClient from apps.parsers.clients.minpromtorg.industrial import IndustrialProductionClient
from apps.parsers.clients.minpromtorg.manufactures import ManufacturesClient from apps.parsers.clients.minpromtorg.manufactures import ManufacturesClient
from apps.parsers.clients.minpromtorg.schemas import IndustrialCertificate, Manufacturer from apps.parsers.clients.minpromtorg.schemas import IndustrialCertificate, Manufacturer
from apps.parsers.clients.proverki import ProverkiClient from apps.parsers.clients.proverki import ProverkiClient
from apps.parsers.clients.proverki.schemas import Inspection from apps.parsers.clients.proverki.schemas import Inspection
from django.test import TestCase, tag
from faker import Faker
from openpyxl import Workbook
fake = Faker("ru_RU") fake = Faker("ru_RU")
@@ -159,7 +157,10 @@ class IndustrialProductionClientTest(TestCase):
{ {
"name": "Заключения о подтверждении производства промышленной продукции на территории Российской Федерации", "name": "Заключения о подтверждении производства промышленной продукции на территории Российской Федерации",
"files": [ "files": [
{"name": "data_resolutions_20240101.xlsx", "url": "/files/test.xlsx"}, {
"name": "data_resolutions_20240101.xlsx",
"url": "/files/test.xlsx",
},
], ],
} }
] ]
@@ -193,9 +194,18 @@ class IndustrialProductionClientTest(TestCase):
{ {
"name": "Заключения о подтверждении производства промышленной продукции на территории Российской Федерации", "name": "Заключения о подтверждении производства промышленной продукции на территории Российской Федерации",
"files": [ "files": [
{"name": "data_resolutions_20240101.xlsx", "url": "/files/old.xlsx"}, {
{"name": "data_resolutions_20240315.xlsx", "url": "/files/new.xlsx"}, "name": "data_resolutions_20240101.xlsx",
{"name": "data_resolutions_20240201.xlsx", "url": "/files/mid.xlsx"}, "url": "/files/old.xlsx",
},
{
"name": "data_resolutions_20240315.xlsx",
"url": "/files/new.xlsx",
},
{
"name": "data_resolutions_20240201.xlsx",
"url": "/files/mid.xlsx",
},
], ],
} }
] ]
@@ -539,7 +549,7 @@ class ProverkiClientTest(TestCase):
client = ProverkiClient() client = ProverkiClient()
xml_str = '<inspection inn="1234567890" registration_number="TEST123" organisation_name="Test Co"/>' xml_str = '<inspection inn="1234567890" registration_number="TEST123" organisation_name="Test Co"/>'
element = ET.fromstring(xml_str) element = ET.fromstring(xml_str) # noqa: S314
result = client._parse_xml_record(element) result = client._parse_xml_record(element)
@@ -553,7 +563,7 @@ class ProverkiClientTest(TestCase):
client = ProverkiClient() client = ProverkiClient()
xml_str = "<empty_record></empty_record>" xml_str = "<empty_record></empty_record>"
element = ET.fromstring(xml_str) element = ET.fromstring(xml_str) # noqa: S314
result = client._parse_xml_record(element) result = client._parse_xml_record(element)
@@ -569,9 +579,7 @@ class ProverkiClientTest(TestCase):
<registration_number>TEST001</registration_number> <registration_number>TEST001</registration_number>
<organisation_name>Компания</organisation_name> <organisation_name>Компания</organisation_name>
</inspection> </inspection>
</inspections>""".encode( </inspections>""".encode("windows-1251")
"windows-1251"
)
inspections = client._parse_xml_content(xml_content, None) inspections = client._parse_xml_content(xml_content, None)

View File

@@ -1,13 +1,12 @@
"""Tests for parsers models.""" """Tests for parsers models."""
from django.test import TestCase
from apps.parsers.models import ( from apps.parsers.models import (
IndustrialCertificateRecord, IndustrialCertificateRecord,
ManufacturerRecord, ManufacturerRecord,
ParserLoadLog, ParserLoadLog,
Proxy, Proxy,
) )
from django.test import TestCase
from .factories import ( from .factories import (
IndustrialCertificateRecordFactory, IndustrialCertificateRecordFactory,

View File

@@ -1,9 +1,5 @@
"""Tests for parsers services.""" """Tests for parsers services."""
from django.test import TestCase
from faker import Faker
from apps.parsers.clients.minpromtorg.schemas import IndustrialCertificate, Manufacturer from apps.parsers.clients.minpromtorg.schemas import IndustrialCertificate, Manufacturer
from apps.parsers.clients.proverki.schemas import Inspection from apps.parsers.clients.proverki.schemas import Inspection
from apps.parsers.models import ( from apps.parsers.models import (
@@ -20,6 +16,8 @@ from apps.parsers.services import (
ParserLoadLogService, ParserLoadLogService,
ProxyService, ProxyService,
) )
from django.test import TestCase
from faker import Faker
from .factories import ( from .factories import (
IndustrialCertificateRecordFactory, IndustrialCertificateRecordFactory,
@@ -147,7 +145,9 @@ class ParserLoadLogServiceTest(TestCase):
def test_get_next_batch_id_first(self): def test_get_next_batch_id_first(self):
"""Test getting first batch_id for new source.""" """Test getting first batch_id for new source."""
batch_id = ParserLoadLogService.get_next_batch_id(ParserLoadLog.Source.INDUSTRIAL) batch_id = ParserLoadLogService.get_next_batch_id(
ParserLoadLog.Source.INDUSTRIAL
)
self.assertEqual(batch_id, 1) self.assertEqual(batch_id, 1)
def test_get_next_batch_id_increment(self): def test_get_next_batch_id_increment(self):
@@ -155,7 +155,9 @@ class ParserLoadLogServiceTest(TestCase):
ParserLoadLogFactory(batch_id=5, source=ParserLoadLog.Source.INDUSTRIAL) ParserLoadLogFactory(batch_id=5, source=ParserLoadLog.Source.INDUSTRIAL)
ParserLoadLogFactory(batch_id=3, source=ParserLoadLog.Source.INDUSTRIAL) ParserLoadLogFactory(batch_id=3, source=ParserLoadLog.Source.INDUSTRIAL)
batch_id = ParserLoadLogService.get_next_batch_id(ParserLoadLog.Source.INDUSTRIAL) batch_id = ParserLoadLogService.get_next_batch_id(
ParserLoadLog.Source.INDUSTRIAL
)
self.assertEqual(batch_id, 6) self.assertEqual(batch_id, 6)
def test_get_next_batch_id_per_source(self): def test_get_next_batch_id_per_source(self):
@@ -272,7 +274,9 @@ class IndustrialCertificateServiceTest(TestCase):
results = IndustrialCertificateService.find_by_inn("1111111111") results = IndustrialCertificateService.find_by_inn("1111111111")
self.assertEqual(results.count(), 2) self.assertEqual(results.count(), 2)
results_batch1 = IndustrialCertificateService.find_by_inn("1111111111", batch_id=1) results_batch1 = IndustrialCertificateService.find_by_inn(
"1111111111", batch_id=1
)
self.assertEqual(results_batch1.count(), 1) self.assertEqual(results_batch1.count(), 1)
def test_find_by_certificate_number(self): def test_find_by_certificate_number(self):
@@ -313,7 +317,7 @@ class IndustrialCertificateServiceTest(TestCase):
ogrn="9999999999999", ogrn="9999999999999",
) )
] ]
count2 = IndustrialCertificateService.save_certificates(duplicate, batch_id=2) IndustrialCertificateService.save_certificates(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped) # Should still be 1 record (duplicate skipped)
self.assertEqual(IndustrialCertificateRecord.objects.count(), 1) self.assertEqual(IndustrialCertificateRecord.objects.count(), 1)
@@ -420,7 +424,7 @@ class ManufacturerServiceTest(TestCase):
address="New Address", address="New Address",
) )
] ]
count2 = ManufacturerService.save_manufacturers(duplicate, batch_id=2) ManufacturerService.save_manufacturers(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped) # Should still be 1 record (duplicate skipped)
self.assertEqual(ManufacturerRecord.objects.count(), 1) self.assertEqual(ManufacturerRecord.objects.count(), 1)
@@ -567,7 +571,7 @@ class InspectionServiceTest(TestCase):
result="выявлены нарушения", result="выявлены нарушения",
) )
] ]
count2 = InspectionService.save_inspections(duplicate, batch_id=2) InspectionService.save_inspections(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped) # Should still be 1 record (duplicate skipped)
self.assertEqual(InspectionRecord.objects.count(), 1) self.assertEqual(InspectionRecord.objects.count(), 1)
@@ -581,10 +585,9 @@ class InspectionServiceTest(TestCase):
self.assertEqual(record.load_batch, 1) # Original batch self.assertEqual(record.load_batch, 1) # Original batch
from django.test import tag
from apps.parsers.clients.base import HTTPClientError from apps.parsers.clients.base import HTTPClientError
from apps.parsers.clients.minpromtorg.industrial import IndustrialProductionClient from apps.parsers.clients.minpromtorg.industrial import IndustrialProductionClient
from django.test import tag
@tag("integration", "slow", "network", "e2e") @tag("integration", "slow", "network", "e2e")
@@ -674,4 +677,3 @@ class EndToEndIntegrationTest(TestCase):
except HTTPClientError as e: except HTTPClientError as e:
self.skipTest(f"External API unavailable: {e}") self.skipTest(f"External API unavailable: {e}")

View File

@@ -16,9 +16,7 @@ class UserFactory(factory.django.DjangoModelFactory):
email = factory.LazyAttribute(lambda _: fake.unique.email()) email = factory.LazyAttribute(lambda _: fake.unique.email())
username = factory.LazyAttribute(lambda _: fake.unique.user_name()) username = factory.LazyAttribute(lambda _: fake.unique.user_name())
phone = factory.LazyAttribute( phone = factory.LazyAttribute(lambda _: f"+7{fake.numerify('##########')}")
lambda _: f"+7{fake.numerify('##########')}"
)
is_verified = False is_verified = False
is_staff = False is_staff = False
is_superuser = False is_superuser = False
@@ -58,7 +56,9 @@ class ProfileFactory(factory.django.DjangoModelFactory):
class Meta: class Meta:
model = Profile model = Profile
django_get_or_create = ("user",) # Используем get_or_create для избежания дубликатов django_get_or_create = (
"user",
) # Используем get_or_create для избежания дубликатов
user = factory.SubFactory(UserFactory) user = factory.SubFactory(UserFactory)
first_name = factory.LazyAttribute(lambda _: fake.first_name()) first_name = factory.LazyAttribute(lambda _: fake.first_name())