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` команды
- Тесты используют `config.settings.test` (SQLite in-memory) вместо PostgreSQL service
#### Code Quality
- Исправлены ошибки ruff lint:
- Сортировка импортов (I001)
- Удалены неиспользуемые импорты и переменные (F401, F841)
- Добавлены noqa для тестового кода (S106, S314)
---
## [0.4.0] - 2026-01-28

View File

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

View File

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

View File

@@ -71,6 +71,7 @@ class BulkOperationsIntegrationTest(TestCase):
def test_bulk_create_chunked(self):
"""Тест массового создания чанками."""
# Создаём тестовый сервис с BulkOperationsMixin
class TestService(BulkOperationsMixin):
model = BackgroundJob
@@ -88,10 +89,13 @@ class BulkOperationsIntegrationTest(TestCase):
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):
"""Тест массового удаления."""
class TestService(BulkOperationsMixin):
model = BackgroundJob
@@ -109,10 +113,13 @@ class BulkOperationsIntegrationTest(TestCase):
deleted = TestService.bulk_delete(ids_to_delete)
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):
"""Тест массового обновления полей."""
class TestService(BulkOperationsMixin):
model = BackgroundJob
@@ -138,6 +145,7 @@ class BulkOperationsIntegrationTest(TestCase):
def test_bulk_update_or_create_creates(self):
"""Тест upsert - создание новых."""
class TestService(BulkOperationsMixin):
model = BackgroundJob
@@ -157,6 +165,7 @@ class BulkOperationsIntegrationTest(TestCase):
def test_bulk_update_or_create_updates(self):
"""Тест upsert - обновление существующих."""
class TestService(BulkOperationsMixin):
model = BackgroundJob

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,17 +3,15 @@
from io import BytesIO
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.minpromtorg.industrial import IndustrialProductionClient
from apps.parsers.clients.minpromtorg.manufactures import ManufacturesClient
from apps.parsers.clients.minpromtorg.schemas import IndustrialCertificate, Manufacturer
from apps.parsers.clients.proverki import ProverkiClient
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")
@@ -159,7 +157,10 @@ class IndustrialProductionClientTest(TestCase):
{
"name": "Заключения о подтверждении производства промышленной продукции на территории Российской Федерации",
"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": "Заключения о подтверждении производства промышленной продукции на территории Российской Федерации",
"files": [
{"name": "data_resolutions_20240101.xlsx", "url": "/files/old.xlsx"},
{"name": "data_resolutions_20240315.xlsx", "url": "/files/new.xlsx"},
{"name": "data_resolutions_20240201.xlsx", "url": "/files/mid.xlsx"},
{
"name": "data_resolutions_20240101.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()
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)
@@ -553,7 +563,7 @@ class ProverkiClientTest(TestCase):
client = ProverkiClient()
xml_str = "<empty_record></empty_record>"
element = ET.fromstring(xml_str)
element = ET.fromstring(xml_str) # noqa: S314
result = client._parse_xml_record(element)
@@ -569,9 +579,7 @@ class ProverkiClientTest(TestCase):
<registration_number>TEST001</registration_number>
<organisation_name>Компания</organisation_name>
</inspection>
</inspections>""".encode(
"windows-1251"
)
</inspections>""".encode("windows-1251")
inspections = client._parse_xml_content(xml_content, None)

View File

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

View File

@@ -1,9 +1,5 @@
"""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.proverki.schemas import Inspection
from apps.parsers.models import (
@@ -20,6 +16,8 @@ from apps.parsers.services import (
ParserLoadLogService,
ProxyService,
)
from django.test import TestCase
from faker import Faker
from .factories import (
IndustrialCertificateRecordFactory,
@@ -147,7 +145,9 @@ class ParserLoadLogServiceTest(TestCase):
def test_get_next_batch_id_first(self):
"""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)
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=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)
def test_get_next_batch_id_per_source(self):
@@ -272,7 +274,9 @@ class IndustrialCertificateServiceTest(TestCase):
results = IndustrialCertificateService.find_by_inn("1111111111")
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)
def test_find_by_certificate_number(self):
@@ -313,7 +317,7 @@ class IndustrialCertificateServiceTest(TestCase):
ogrn="9999999999999",
)
]
count2 = IndustrialCertificateService.save_certificates(duplicate, batch_id=2)
IndustrialCertificateService.save_certificates(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped)
self.assertEqual(IndustrialCertificateRecord.objects.count(), 1)
@@ -420,7 +424,7 @@ class ManufacturerServiceTest(TestCase):
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)
self.assertEqual(ManufacturerRecord.objects.count(), 1)
@@ -567,7 +571,7 @@ class InspectionServiceTest(TestCase):
result="выявлены нарушения",
)
]
count2 = InspectionService.save_inspections(duplicate, batch_id=2)
InspectionService.save_inspections(duplicate, batch_id=2)
# Should still be 1 record (duplicate skipped)
self.assertEqual(InspectionRecord.objects.count(), 1)
@@ -581,10 +585,9 @@ class InspectionServiceTest(TestCase):
self.assertEqual(record.load_batch, 1) # Original batch
from django.test import tag
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")
@@ -674,4 +677,3 @@ class EndToEndIntegrationTest(TestCase):
except HTTPClientError as 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())
username = factory.LazyAttribute(lambda _: fake.unique.user_name())
phone = factory.LazyAttribute(
lambda _: f"+7{fake.numerify('##########')}"
)
phone = factory.LazyAttribute(lambda _: f"+7{fake.numerify('##########')}")
is_verified = False
is_staff = False
is_superuser = False
@@ -58,7 +56,9 @@ class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = Profile
django_get_or_create = ("user",) # Используем get_or_create для избежания дубликатов
django_get_or_create = (
"user",
) # Используем get_or_create для избежания дубликатов
user = factory.SubFactory(UserFactory)
first_name = factory.LazyAttribute(lambda _: fake.first_name())