Files
group_fqcd_jr/tests/unit/service/test_profile_governance_service.py
T

126 lines
3.9 KiB
Python

from datetime import datetime
from decimal import Decimal
import pytest
from app.core.errors import InvalidStateError
from app.model.profile_tag import AdvisorProfileDriftReview, AdvisorProfileTag
from app.service.profile_governance_service import ProfileGovernanceService
class _AsyncContext:
async def __aenter__(self) -> object:
return None
async def __aexit__(self, *_args: object) -> bool:
return False
class _Session:
def __init__(self, pending: object = None) -> None:
self.pending = pending
def begin(self) -> _AsyncContext:
return _AsyncContext()
class _SessionFactory:
def __init__(self, session: _Session) -> None:
self.session = session
def __call__(self) -> "_SessionContext":
return _SessionContext(self.session)
class _SessionContext:
def __init__(self, session: _Session) -> None:
self.session = session
async def __aenter__(self) -> _Session:
return self.session
async def __aexit__(self, *_args: object) -> bool:
return False
@pytest.mark.asyncio
async def test_pending_profile_blocks_advisory_operation(monkeypatch) -> None:
session = _Session(pending=object())
async def pending(_self, _customer_id: int, *, lock: bool = False) -> object:
del lock
return session.pending
monkeypatch.setattr(
"app.service.profile_governance_service.RiskQuestionnaireRepository.pending_drift_review",
pending,
)
with pytest.raises(InvalidStateError, match="漂移正在复核"):
await ProfileGovernanceService(_SessionFactory(session)).require_operable(7)
class _Repository:
def __init__(self) -> None:
self.session = self
self.added: list[object] = []
self.sync_targets: list[str] = []
async def execute(self, _statement: object) -> None:
return None
async def deactivate_current_profile(self, _customer_id: int, _now: datetime) -> None:
return None
async def active_tags(
self, _customer_id: int, *, lock: bool = False
) -> list[AdvisorProfileTag]:
del lock
return []
async def supersede_active_tags(
self, _customer_id: int, _tag_keys: tuple[str, ...], _now: datetime
) -> None:
return None
def add_profile(self, profile: object) -> None:
self.added.append(profile)
def add_sync_event(self, event: object) -> None:
self.added.append(event)
self.sync_targets.append(event.target_store)
def _review() -> AdvisorProfileDriftReview:
now = datetime(2026, 9, 11)
return AdvisorProfileDriftReview(
id=4, drift_no="drift-4", customer_id=7, source_assessment_id=9,
candidate_profile_uuid="candidate-uuid", candidate_profile_version=2,
candidate_snapshot={"risk_level": "C3"},
candidate_generation_basis={"source": "assessment"},
changed_tags=[], status="pending_review", reviewer_user_id=None,
reviewed_at=None, review_comment=None, created_at=now, updated_at=now,
)
@pytest.mark.asyncio
async def test_approved_review_activates_candidate_and_enqueues_both_projections() -> None:
repository = _Repository()
tag = AdvisorProfileTag(
id=11, tag_uuid="tag-11", customer_id=7, tag_key="risk_level", tag_value="C3",
tag_value_hash="x", confidence=Decimal("0.9000"), source_type="assessment",
source_reference="assessment:9", source_confidence=Decimal("1.0000"),
profile_version=2, drift_review_id=4, previous_tag_id=1, drift_reason="value_changed",
status="pending_review", active_customer_tag=None,
created_at=datetime(2026, 9, 11), updated_at=datetime(2026, 9, 11),
)
await ProfileGovernanceService()._approve(
repository, _review(), [tag], datetime(2026, 9, 11)
)
assert tag.status == "active"
assert tag.active_customer_tag == "7:risk_level"
assert repository.added[0].is_current is True
assert repository.sync_targets == ["milvus", "neo4j"]