"""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")