"""Индекс и резолв контрагентов (§3 п. 4; spec osv-result-mapping «Резолв контрагента»; design D-8; Review Focus 3). Интеграционные тесты на `db_session` с фабриками: загрузка индекса двумя запросами по всему справочнику (без policy), ИНН → точное имя → нормализованное имя/алиас, неоднозначность → `None`, `owner_names` для `counterparty_check`. """ import datetime import pytest from sqlalchemy.ext.asyncio import AsyncSession from src.domain.aliases import normalize_alias from src.models.directories import LegalEntityAlias from src.repositories.counterparties import CounterpartyIndex from tests.factories import make_legal_entity pytestmark = pytest.mark.integration async def _alias(session: AsyncSession, entity_id, alias: str) -> None: session.add(LegalEntityAlias(legal_entity_id=entity_id, alias=alias, normalized_alias=normalize_alias(alias))) await session.flush() async def test_inn_alias_and_ambiguous(db_session: AsyncSession) -> None: """Сценарий спеки: по ИНН, по алиасу, неоднозначно.""" by_inn = await make_legal_entity(db_session, name="АО «Хлебозавод Ломоносов»", inn="3528052718") by_alias = await make_legal_entity(db_session, name="ООО «РБ Холдинг»") await _alias(db_session, by_alias.id, "Русский бисквит ООО") await make_legal_entity(db_session, name="Альфаомега") await make_legal_entity(db_session, name="АО «Альфаомега»") index = await CounterpartyIndex.load(db_session) assert index.resolve("3528052718", "что угодно") == by_inn.id assert index.resolve(None, "Русский бисквит") == by_alias.id # точного имени нет, два ЮЛ с нормализованным именем `альфаомега` assert index.resolve(None, "ООО Альфаомега") is None async def test_inn_is_compared_without_whitespace(db_session: AsyncSession) -> None: entity = await make_legal_entity(db_session, name="ООО Пробел", inn="7701234567") index = await CounterpartyIndex.load(db_session) assert index.resolve(" 7701 234 567 ", None) == entity.id assert index.resolve("7701234568", None) is None assert index.resolve("", "") is None assert index.resolve(None, None) is None assert index.resolve(None, " ") is None async def test_unknown_inn_falls_through_to_alias_of_other_entity(db_session: AsyncSession) -> None: """Review Focus 3: ИНН строки нет в справочнике — это не блокирует поиск по имени/алиасу другого ЮЛ.""" owner = await make_legal_entity(db_session, name="ООО Совсем Другое", inn="1111111111") await _alias(db_session, owner.id, "Пекарня Счастливочная") index = await CounterpartyIndex.load(db_session) assert index.resolve("9999999999", "Пекарня Счастливочная ООО") == owner.id async def test_exact_name_unique_and_ambiguous(db_session: AsyncSession) -> None: unique = await make_legal_entity(db_session, name="ООО Точное Имя Один") await make_legal_entity(db_session, name="ООО Дубль Имени", inn="2222222221") await make_legal_entity(db_session, name="ООО Дубль Имени", inn="2222222222") index = await CounterpartyIndex.load(db_session) assert index.resolve(None, " ООО Точное Имя Один ") == unique.id # >1 кандидата на шаге точного имени → None, без перехода дальше assert index.resolve(None, "ООО Дубль Имени") is None async def test_exact_name_zero_candidates_goes_to_normalized(db_session: AsyncSession) -> None: entity = await make_legal_entity(db_session, name="ООО «Нормализуемое Имя»") index = await CounterpartyIndex.load(db_session) assert index.resolve(None, "Нормализуемое имя, ООО") == entity.id async def test_alias_ambiguity_between_name_and_alias_of_different_entities(db_session: AsyncSession) -> None: first = await make_legal_entity(db_session, name="ООО Гамма Сервис") second = await make_legal_entity(db_session, name="ООО Дельта") await _alias(db_session, second.id, "Гамма сервис") index = await CounterpartyIndex.load(db_session) assert first.id != second.id assert index.resolve(None, "Гамма Сервис") is None async def test_same_entity_by_name_and_alias_is_not_ambiguous(db_session: AsyncSession) -> None: entity = await make_legal_entity(db_session, name="ООО Эпсилон") await _alias(db_session, entity.id, "Эпсилон") index = await CounterpartyIndex.load(db_session) assert index.resolve(None, "эпсилон ооо") == entity.id async def test_closed_entity_is_resolved(db_session: AsyncSession) -> None: closed = await make_legal_entity( db_session, name="ООО Закрытое Зета", inn="3333333333", close_date=datetime.date(2023, 1, 1) ) index = await CounterpartyIndex.load(db_session) assert index.resolve("3333333333", None) == closed.id assert index.resolve(None, "закрытое зета, ООО") == closed.id async def test_owner_names(db_session: AsyncSession) -> None: owner = await make_legal_entity(db_session, name="ООО «Пекарня Счастливочная»") await _alias(db_session, owner.id, "Счастливочная пекарня") other = await make_legal_entity(db_session, name="ООО Чужое") await _alias(db_session, other.id, "Чужой алиас") index = await CounterpartyIndex.load(db_session) assert index.owner_names(owner) == frozenset({"пекарня счастливочная", "счастливочная пекарня"}) assert index.owner_names(other) == frozenset({"чужое", "чужой алиас"}) async def test_owner_names_without_aliases_and_empty_normalization(db_session: AsyncSession) -> None: only_form = await make_legal_entity(db_session, name="ООО") plain = await make_legal_entity(db_session, name="ИП Иванов") index = await CounterpartyIndex.load(db_session) assert index.owner_names(only_form) == frozenset() assert index.owner_names(plain) == frozenset({"иванов"}) # нормализация в пустую строку не даёт совпадений: пустой ключ в индексе не хранится assert index.resolve(None, "АО") is None