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

This commit is contained in:
2026-03-17 12:56:48 +01:00
parent b505c67968
commit 3d298ce352
101 changed files with 8387 additions and 292 deletions

View File

@@ -0,0 +1,214 @@
"""Focused unit tests for parser service helpers and small query methods."""
from __future__ import annotations
from datetime import date
from decimal import Decimal
from unittest.mock import patch
from apps.parsers.models import ParserLoadLog
from apps.parsers.services import (
FNSReportService,
IndustrialProductService,
InspectionService,
ParserLoadLogService,
ProcurementService,
RegistryOrganizationResolver,
normalize_to_date,
normalize_to_decimal,
)
from django.db import IntegrityError
from django.test import TestCase
from tests.apps.parsers.factories import (
IndustrialProductRecordFactory,
InspectionRecordFactory,
ParserLoadLogFactory,
ProcurementRecordFactory,
)
from tests.apps.registers.factories import OrganizationFactory
class NormalizeHelpersTest(TestCase):
def test_normalize_to_date_handles_direct_and_embedded_formats(self):
self.assertIsNone(normalize_to_date(None))
self.assertIsNone(normalize_to_date(" "))
self.assertEqual(
normalize_to_date("2026-03-17T10:15:30+03:00"),
date(2026, 3, 17),
)
self.assertEqual(
normalize_to_date("report created at 2026-03-17 10:15"),
date(2026, 3, 17),
)
self.assertEqual(
normalize_to_date("актуально на 17.03.2026 10:15"),
date(2026, 3, 17),
)
self.assertIsNone(normalize_to_date("not a date"))
def test_normalize_to_decimal_handles_common_formats_and_invalid_values(self):
self.assertIsNone(normalize_to_decimal(None))
self.assertIsNone(normalize_to_decimal(" "))
self.assertEqual(normalize_to_decimal("1.234,56 руб."), Decimal("1234.56"))
self.assertEqual(normalize_to_decimal("1,234.56"), Decimal("1234.56"))
self.assertIsNone(normalize_to_decimal("руб."))
self.assertIsNone(normalize_to_decimal("--1"))
class RegistryOrganizationResolverTest(TestCase):
def test_normalize_identifier_rejects_non_digits(self):
self.assertIsNone(RegistryOrganizationResolver.normalize_identifier(""))
self.assertIsNone(RegistryOrganizationResolver.normalize_identifier("12 34"))
self.assertIsNone(RegistryOrganizationResolver.normalize_identifier("abc"))
def test_build_lookup_returns_empty_indexes_when_identifiers_absent(self):
lookup = RegistryOrganizationResolver.build_lookup([(None, None), ("", "")])
self.assertEqual(lookup.by_pair, {})
self.assertEqual(lookup.by_inn, {})
self.assertEqual(lookup.by_ogrn, {})
def test_resolve_organization_id_by_unique_inn_and_ogrn(self):
org_by_inn = OrganizationFactory(mn_inn=7_701_001_001, mn_ogrn=10_277_001_000_001)
org_by_ogrn = OrganizationFactory(mn_inn=7_701_001_002, mn_ogrn=10_277_001_000_002)
lookup = RegistryOrganizationResolver.build_lookup(
[
(org_by_inn.mn_inn, None),
(None, org_by_ogrn.mn_ogrn),
]
)
self.assertEqual(
RegistryOrganizationResolver.resolve_organization_id(
lookup=lookup,
inn=str(org_by_inn.mn_inn),
ogrn=None,
),
org_by_inn.id,
)
self.assertEqual(
RegistryOrganizationResolver.resolve_organization_id(
lookup=lookup,
inn=None,
ogrn=str(org_by_ogrn.mn_ogrn),
),
org_by_ogrn.id,
)
class ParserLoadLogServiceRetryTest(TestCase):
def test_create_load_log_with_next_batch_id_retries_after_integrity_error(self):
log = ParserLoadLogFactory.build(
source=ParserLoadLog.Source.INDUSTRIAL,
batch_id=2,
)
with patch.object(ParserLoadLogService, "get_next_batch_id", side_effect=[1, 2]):
with patch.object(
ParserLoadLogService,
"create_load_log",
side_effect=[IntegrityError("duplicate"), log],
) as create_mock:
created_log, batch_id = (
ParserLoadLogService.create_load_log_with_next_batch_id(
source=ParserLoadLog.Source.INDUSTRIAL
)
)
self.assertEqual(created_log, log)
self.assertEqual(batch_id, 2)
self.assertEqual(create_mock.call_count, 2)
def test_create_load_log_with_next_batch_id_raises_after_max_retries(self):
with patch.object(ParserLoadLogService, "get_next_batch_id", return_value=1):
with patch.object(
ParserLoadLogService,
"create_load_log",
side_effect=IntegrityError("duplicate"),
):
with self.assertRaisesMessage(
RuntimeError,
"Failed to allocate unique batch_id",
):
ParserLoadLogService.create_load_log_with_next_batch_id(
source=ParserLoadLog.Source.INDUSTRIAL,
max_retries=2,
)
class SmallParserServiceQueryTest(TestCase):
def test_industrial_product_service_query_helpers(self):
record = IndustrialProductRecordFactory(
inn="7701001001",
ogrn="1027700100001",
load_batch=7,
)
IndustrialProductRecordFactory(
inn="7701001001",
ogrn="1027700100002",
load_batch=8,
)
self.assertEqual(IndustrialProductService.find_by_inn("7701001001").count(), 2)
self.assertEqual(
IndustrialProductService.find_by_inn("7701001001", batch_id=7).count(),
1,
)
self.assertEqual(
IndustrialProductService.find_by_ogrn("1027700100001").first().id,
record.id,
)
def test_inspection_service_has_data_for_period(self):
InspectionRecordFactory(data_year=2026, data_month=3, is_federal_law_248=False)
InspectionRecordFactory(data_year=2026, data_month=4, is_federal_law_248=True)
self.assertTrue(InspectionService.has_data_for_period(2026, 3))
self.assertFalse(InspectionService.has_data_for_period(2026, 3, True))
self.assertTrue(InspectionService.has_data_for_period(2026, 4, True))
def test_procurement_service_find_by_customer_name_with_batch(self):
ProcurementRecordFactory(
customer_name="АО Тестовый заказчик",
load_batch=11,
)
ProcurementRecordFactory(
customer_name="АО Тестовый заказчик",
load_batch=12,
)
self.assertEqual(ProcurementService.find_by_customer_name("Тестовый").count(), 2)
self.assertEqual(
ProcurementService.find_by_customer_name("Тестовый", batch_id=11).count(),
1,
)
class FNSReportServiceHelpersTest(TestCase):
def test_exists_find_and_status_helpers(self):
report = FNSReportService.save_report(
external_id="EXT-100",
ogrn="1027700111111",
file_name="fin_EXT-100_1027700111111.xlsx",
file_hash="a" * 64,
source="api",
batch_id=1,
lines_data=[],
)
self.assertTrue(FNSReportService.exists_by_external_id("EXT-100"))
self.assertFalse(FNSReportService.exists_by_external_id("EXT-404"))
self.assertEqual(FNSReportService.find_by_external_id("EXT-100").id, report.id)
FNSReportService.mark_processing(report)
report.refresh_from_db()
self.assertEqual(report.status, report.Status.PROCESSING)
FNSReportService.mark_success(report)
report.refresh_from_db()
self.assertEqual(report.status, report.Status.SUCCESS)
FNSReportService.mark_failed(report, "boom")
report.refresh_from_db()
self.assertEqual(report.status, report.Status.FAILED)
self.assertEqual(report.error_message, "boom")