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
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:
214
tests/apps/parsers/test_service_helpers.py
Normal file
214
tests/apps/parsers/test_service_helpers.py
Normal 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")
|
||||
Reference in New Issue
Block a user