Финальная стабильная реализация

This commit is contained in:
mi
2026-08-21 11:48:24 +03:00
parent 6e24278b10
commit d2415fcfeb
17 changed files with 662 additions and 57 deletions
@@ -2,6 +2,7 @@ from __future__ import annotations
import uuid
from contextlib import asynccontextmanager
from datetime import UTC, datetime
import pytest
from sqlalchemy.dialects.postgresql import JSONB
@@ -14,9 +15,54 @@ from app.repository import Profile
class Result:
rowcount = 1
def __init__(self, *, row=None, scalar=None) -> None:
self.row = row
self.scalar = scalar
def mappings(self):
return self
def one(self):
return self.row
def scalar_one_or_none(self):
return self.scalar
class Connection:
def __init__(self, statements: list[str]) -> None:
self.statements = statements
async def execute(self, statement, params=None):
sql = str(statement)
self.statements.append(sql)
if "INSERT INTO bitrix_sync.business_alerts" in sql:
now = datetime.now(UTC)
return Result(
row={
"id": uuid.uuid4(),
"alert_number": 1,
"fingerprint": "fingerprint",
"alert_type": params["type"],
"severity": "warning",
"app_user_id": params["user_id"],
"selected_external_id": params["selected_external_id"],
"candidate_external_ids": params["candidates"],
"remote_item_id": None,
"occurrence_count": 1,
"first_occurred_at": now,
"last_occurred_at": now,
}
)
if "SELECT value_json FROM bitrix_sync.settings" in sql:
return Result(
scalar={
"entity_type_id": 178,
"category_id": 5,
"stage_new": "DT178_5:NEW",
"field_ids": {"candidate_external_ids": "ufCrm_999"},
}
)
return Result()
@@ -27,7 +73,7 @@ class FakeRepository:
@asynccontextmanager
async def transaction(self):
yield Connection()
yield Connection(self.statements)
async def active_mapping(self, user_id):
return self.mapping
@@ -37,8 +83,16 @@ class FakeRepository:
class FakeCrm:
def __init__(self, user_id: uuid.UUID) -> None:
def __init__(
self,
user_id: uuid.UUID,
*,
existing_identity: bool = False,
foreign_owner: bool = False,
) -> None:
self.user_id = user_id
self.existing_identity = existing_identity
self.foreign_owner = foreign_owner
self.calls: list[tuple[str, dict]] = []
async def call(self, method, params, *, mutating):
@@ -52,9 +106,19 @@ class FakeCrm:
{
"ID": contact_id,
"CREATED_TIME": "2026-08-06T10:00:00Z",
"UF_CRM_100": None,
"UF_CRM_100": (
str(self.user_id)
if self.existing_identity and contact_id == "10"
else str(uuid.UUID(int=1))
if self.foreign_owner and contact_id == "10"
else None
),
},
)
if method == "crm.contact.add":
return CrmResult(CrmOutcome.SUCCEEDED, "11")
if method == "crm.item.add":
return CrmResult(CrmOutcome.SUCCEEDED, {"item": {"id": "500"}})
return CrmResult(CrmOutcome.SUCCEEDED, True)
@@ -76,3 +140,46 @@ async def test_multiple_contacts_choose_numeric_newest(full_settings) -> None:
updates = [params for method, params in crm.calls if method == "crm.contact.update"]
assert updates[0]["id"] == "10"
assert not any(method == "crm.contact.add" for method, _ in crm.calls)
assert any(method == "crm.item.add" for method, _ in crm.calls)
alert_add = next(params for method, params in crm.calls if method == "crm.item.add")
assert alert_add["fields"]["contactIds"] == [9, 10]
assert alert_add["fields"]["sourceId"] == "WEB"
assert "ufCrm_999" not in alert_add["fields"]
assert any("entity_external_mapping" in statement for statement in repository.statements)
assert not any("digest(" in statement for statement in repository.statements)
@pytest.mark.asyncio
async def test_recovered_duplicate_creates_alert_before_mapping(full_settings) -> None:
user_id = uuid.uuid4()
repository = FakeRepository()
crm = FakeCrm(user_id, existing_identity=True)
engine = WorkflowEngine(repository, crm, full_settings)
await engine._map_or_create(
uuid.uuid4(),
Profile(user_id=user_id, phone="+79001234567", identity_status="A", profile_status="A"),
)
methods = [method for method, _ in crm.calls]
assert "crm.item.add" in methods
assert "crm.contact.update" not in methods
assert any("entity_external_mapping" in statement for statement in repository.statements)
@pytest.mark.asyncio
async def test_foreign_owned_contact_alert_includes_new_contact(full_settings) -> None:
user_id = uuid.uuid4()
repository = FakeRepository()
crm = FakeCrm(user_id, foreign_owner=True)
engine = WorkflowEngine(repository, crm, full_settings)
await engine._map_or_create(
uuid.uuid4(),
Profile(user_id=user_id, phone="+79001234567", identity_status="A", profile_status="A"),
)
methods = [method for method, _ in crm.calls]
alert_add = next(params for method, params in crm.calls if method == "crm.item.add")
assert methods.index("crm.contact.add") < methods.index("crm.item.add")
assert alert_add["fields"]["contactIds"] == [9, 10, 11]