Some checks failed
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) Successful in 1m42s
CI/CD Pipeline / Run Tests (pull_request) Successful in 2m25s
CI/CD Pipeline / Telegram Notify Success (pull_request) Successful in 1m34s
223 lines
7.9 KiB
Python
223 lines
7.9 KiB
Python
"""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]
|
||
), 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
|
||
), patch.object(
|
||
ParserLoadLogService,
|
||
"create_load_log",
|
||
side_effect=IntegrityError("duplicate"),
|
||
), 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")
|