243 lines
9.6 KiB
Python
243 lines
9.6 KiB
Python
"""Tests for direct parser ingestion into organization source storage."""
|
||
|
||
from decimal import Decimal
|
||
|
||
from apps.parsers.models import FinancialReport, GenericParserRecord, ParserLoadLog
|
||
from django.test import TestCase
|
||
from organizations.models import (
|
||
DefenseSupplierExtension,
|
||
FinancialIndicatorsExtension,
|
||
Organization,
|
||
OrganizationSourceFinancialLine,
|
||
OrganizationSourceRecord,
|
||
SourceGroup,
|
||
)
|
||
from organizations.source_ingestion import (
|
||
OrganizationSourceIngestionService,
|
||
SourceFinancialLineInput,
|
||
SourceRecordInput,
|
||
)
|
||
|
||
|
||
class OrganizationSourceIngestionServiceTest(TestCase):
|
||
"""Checks runtime parser writes bypass legacy parser record tables."""
|
||
|
||
def test_save_generic_records_writes_source_records_without_legacy_rows(self):
|
||
result = OrganizationSourceIngestionService.save_records(
|
||
source=ParserLoadLog.Source.UNFAIR_SUPPLIERS,
|
||
load_batch=42,
|
||
records=[
|
||
SourceRecordInput(
|
||
external_id="unfair-42",
|
||
title="Недобросовестный поставщик",
|
||
organization_name='ООО "ГОЗ"',
|
||
inn="7707083803",
|
||
ogrn="",
|
||
record_date="2026-05-18",
|
||
amount=Decimal("100.50"),
|
||
status="active",
|
||
url="https://example.test/unfair-42",
|
||
payload={"reason": "contract breach"},
|
||
)
|
||
],
|
||
)
|
||
|
||
self.assertEqual(result.scanned, 1)
|
||
self.assertEqual(result.created_records, 1)
|
||
self.assertEqual(result.updated_records, 0)
|
||
self.assertEqual(result.created_extensions, 1)
|
||
self.assertEqual(result.unresolved, 0)
|
||
self.assertEqual(GenericParserRecord.objects.count(), 0)
|
||
|
||
organization = Organization.objects.get(inn="7707083803")
|
||
extension = DefenseSupplierExtension.objects.get(organization=organization)
|
||
self.assertEqual(extension.source_group, SourceGroup.DEFENSE_SUPPLIERS)
|
||
self.assertEqual(extension.records_count, 1)
|
||
|
||
record = OrganizationSourceRecord.objects.get(extension=extension)
|
||
self.assertEqual(record.source, ParserLoadLog.Source.UNFAIR_SUPPLIERS)
|
||
self.assertEqual(record.record_type, "unfair_supplier")
|
||
self.assertEqual(record.external_id, "unfair-42")
|
||
self.assertEqual(record.amount, Decimal("100.50"))
|
||
self.assertEqual(record.payload["reason"], "contract breach")
|
||
self.assertEqual(record.legacy_model, "")
|
||
self.assertEqual(record.legacy_pk, "")
|
||
|
||
def test_save_records_is_idempotent_by_source_external_id(self):
|
||
first = SourceRecordInput(
|
||
external_id="unfair-idempotent",
|
||
title="Old title",
|
||
organization_name='ООО "Идемпотентность"',
|
||
inn="7707083810",
|
||
status="old",
|
||
payload={"version": 1},
|
||
)
|
||
second = SourceRecordInput(
|
||
external_id="unfair-idempotent",
|
||
title="New title",
|
||
organization_name='ООО "Идемпотентность"',
|
||
inn="7707083810",
|
||
status="new",
|
||
payload={"version": 2},
|
||
)
|
||
|
||
OrganizationSourceIngestionService.save_records(
|
||
source=ParserLoadLog.Source.UNFAIR_SUPPLIERS,
|
||
load_batch=43,
|
||
records=[first],
|
||
)
|
||
result = OrganizationSourceIngestionService.save_records(
|
||
source=ParserLoadLog.Source.UNFAIR_SUPPLIERS,
|
||
load_batch=44,
|
||
records=[second],
|
||
)
|
||
|
||
self.assertEqual(result.created_records, 0)
|
||
self.assertEqual(result.updated_records, 1)
|
||
self.assertEqual(OrganizationSourceRecord.objects.count(), 1)
|
||
record = OrganizationSourceRecord.objects.get()
|
||
self.assertEqual(record.title, "New title")
|
||
self.assertEqual(record.status, "new")
|
||
self.assertEqual(record.payload["version"], 2)
|
||
self.assertEqual(record.load_batch, 44)
|
||
|
||
def test_save_records_does_not_attach_identified_record_to_name_only_org(self):
|
||
name_only = Organization.objects.create(name="Acme Security")
|
||
|
||
result = OrganizationSourceIngestionService.save_records(
|
||
source=ParserLoadLog.Source.FSTEC,
|
||
load_batch=45,
|
||
records=[
|
||
SourceRecordInput(
|
||
external_id="fstec-5066",
|
||
title="Weblock",
|
||
organization_name="Acme Security",
|
||
inn="7713497980",
|
||
ogrn="1237700253306",
|
||
payload={"identity_enrichment": {"status": "matched"}},
|
||
)
|
||
],
|
||
)
|
||
|
||
self.assertEqual(result.created_records, 1)
|
||
organization = Organization.objects.get(inn="7713497980")
|
||
self.assertNotEqual(organization.uid, name_only.uid)
|
||
self.assertEqual(organization.ogrn, "1237700253306")
|
||
record = OrganizationSourceRecord.objects.get(external_id="fstec-5066")
|
||
self.assertEqual(record.extension.organization, organization)
|
||
self.assertFalse(name_only.source_extensions.exists())
|
||
|
||
def test_save_records_fills_missing_identity_on_resolved_organization(self):
|
||
partial = Organization.objects.create(
|
||
name="1237700253306",
|
||
ogrn="1237700253306",
|
||
)
|
||
|
||
OrganizationSourceIngestionService.save_records(
|
||
source=ParserLoadLog.Source.FSTEC,
|
||
load_batch=46,
|
||
records=[
|
||
SourceRecordInput(
|
||
external_id="fstec-5067",
|
||
title="Weblock",
|
||
organization_name="Acme Security",
|
||
inn="7713497980",
|
||
ogrn="1237700253306",
|
||
)
|
||
],
|
||
)
|
||
|
||
partial.refresh_from_db()
|
||
self.assertEqual(partial.inn, "7713497980")
|
||
self.assertEqual(partial.ogrn, "1237700253306")
|
||
self.assertEqual(partial.name, "Acme Security")
|
||
record = OrganizationSourceRecord.objects.get(external_id="fstec-5067")
|
||
self.assertEqual(record.extension.organization, partial)
|
||
|
||
def test_save_records_ignores_ogrip_like_ogrn_for_legal_entity_inn(self):
|
||
result = OrganizationSourceIngestionService.save_records(
|
||
source=ParserLoadLog.Source.INDUSTRIAL,
|
||
load_batch=47,
|
||
records=[
|
||
SourceRecordInput(
|
||
external_id="cert-legal-entity-bad-ogrn",
|
||
title="cert-legal-entity-bad-ogrn",
|
||
organization_name='ООО "Металл-Завод"',
|
||
inn="7720525156",
|
||
ogrn="105774446645395",
|
||
)
|
||
],
|
||
)
|
||
|
||
self.assertEqual(result.created_records, 1)
|
||
organization = Organization.objects.get(inn="7720525156")
|
||
self.assertEqual(organization.ogrn, "")
|
||
self.assertEqual(organization.ogrip, "")
|
||
|
||
def test_save_records_does_not_add_ogrip_to_existing_legal_entity(self):
|
||
organization = Organization.objects.create(
|
||
name='ООО "Металл-Завод"',
|
||
inn="7720525156",
|
||
ogrn="1057746645395",
|
||
)
|
||
|
||
result = OrganizationSourceIngestionService.save_records(
|
||
source=ParserLoadLog.Source.INDUSTRIAL,
|
||
load_batch=48,
|
||
records=[
|
||
SourceRecordInput(
|
||
external_id="cert-legal-entity-existing-bad-ogrn",
|
||
title="cert-legal-entity-existing-bad-ogrn",
|
||
organization_name='ООО "Металл-Завод"',
|
||
inn="7720525156",
|
||
ogrn="105774446645395",
|
||
)
|
||
],
|
||
)
|
||
|
||
self.assertEqual(result.created_records, 1)
|
||
organization.refresh_from_db()
|
||
self.assertEqual(organization.ogrn, "1057746645395")
|
||
self.assertEqual(organization.ogrip, "")
|
||
|
||
def test_save_financial_report_writes_financial_lines_without_legacy_report(self):
|
||
result = OrganizationSourceIngestionService.save_records(
|
||
source=ParserLoadLog.Source.FNS_REPORTS,
|
||
load_batch=88,
|
||
records=[
|
||
SourceRecordInput(
|
||
external_id="fns-report-1",
|
||
title="fin_001_1027700132002.xlsx",
|
||
organization_name="",
|
||
ogrn="1027700132002",
|
||
status="success",
|
||
payload={
|
||
"file_name": "fin_001_1027700132002.xlsx",
|
||
"file_hash": "a" * 64,
|
||
},
|
||
financial_lines=[
|
||
SourceFinancialLineInput(
|
||
form_code="1",
|
||
line_code="1100",
|
||
line_name="Нематериальные активы",
|
||
year=2025,
|
||
period_start=100,
|
||
period_end=200,
|
||
)
|
||
],
|
||
)
|
||
],
|
||
)
|
||
|
||
self.assertEqual(result.created_records, 1)
|
||
self.assertEqual(result.created_financial_lines, 1)
|
||
self.assertEqual(FinancialReport.objects.count(), 0)
|
||
|
||
organization = Organization.objects.get(ogrn="1027700132002")
|
||
extension = FinancialIndicatorsExtension.objects.get(organization=organization)
|
||
record = OrganizationSourceRecord.objects.get(extension=extension)
|
||
self.assertEqual(record.source, ParserLoadLog.Source.FNS_REPORTS)
|
||
line = OrganizationSourceFinancialLine.objects.get(source_record=record)
|
||
self.assertEqual(line.line_code, "1100")
|
||
self.assertEqual(line.period_end, 200)
|