2026-07-02 03:28:19 +08:00
|
|
|
|
"""channels application 层单元测试共享 fixture。
|
|
|
|
|
|
|
|
|
|
|
|
提供 application 层被驱动仓储 / 能力端口桩(OutboxRepositoryPort /
|
|
|
|
|
|
MessageRepositoryPort / ChannelSessionRepositoryPort /
|
|
|
|
|
|
ChannelAccountRepositoryPort / PairingRepositoryPort /
|
2026-07-06 20:52:20 +08:00
|
|
|
|
AuditLogRepositoryPort / ConversationPort / TransactionPort / QueuePort /
|
2026-07-02 03:28:19 +08:00
|
|
|
|
ContentReviewRepositoryPort / UserIdentityRepositoryPort /
|
|
|
|
|
|
IdempotencyRepositoryPort / PersistencePort / RealtimeMetricsPort)。
|
|
|
|
|
|
|
|
|
|
|
|
桩使用 ``AsyncMock()``(异步端口)/ ``MagicMock()``(TransactionPort.begin
|
|
|
|
|
|
为同步方法),不实现真实逻辑;方法签名以
|
|
|
|
|
|
``yuxi.channels.contract.ports.driven`` 下的 Protocol 为准。单文件独有的
|
|
|
|
|
|
helper 应保留在对应测试文件内(见 testing-guidelines.md)。
|
|
|
|
|
|
|
|
|
|
|
|
注意:``TransactionPort.begin`` 在端口协议中为同步方法(返回
|
|
|
|
|
|
TransactionContext,由 ``async with`` 消费),因此 ``fake_transaction_port``
|
|
|
|
|
|
使用 ``MagicMock()`` 而非 ``AsyncMock()``,避免 ``begin`` 被误生成为协程。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
|
|
from unittest.mock import AsyncMock, MagicMock
|
|
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_outbox_repo() -> AsyncMock:
|
|
|
|
|
|
"""OutboxRepositoryPort 桩。
|
|
|
|
|
|
|
|
|
|
|
|
预设 ``saveOutboxEntry`` / ``getOutboxEntry`` / ``updateOutboxEntry``
|
|
|
|
|
|
返回 None,``listPendingOutboxEntries`` 返回空列表。
|
|
|
|
|
|
"""
|
|
|
|
|
|
mock = AsyncMock()
|
|
|
|
|
|
mock.saveOutboxEntry.return_value = None
|
|
|
|
|
|
mock.getOutboxEntry.return_value = None
|
|
|
|
|
|
mock.updateOutboxEntry.return_value = None
|
|
|
|
|
|
mock.listPendingOutboxEntries.return_value = []
|
|
|
|
|
|
return mock
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_message_repo() -> AsyncMock:
|
|
|
|
|
|
"""MessageRepositoryPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_session_repo() -> AsyncMock:
|
|
|
|
|
|
"""ChannelSessionRepositoryPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_account_repo() -> AsyncMock:
|
|
|
|
|
|
"""ChannelAccountRepositoryPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_pairing_repo() -> AsyncMock:
|
|
|
|
|
|
"""PairingRepositoryPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_audit_log_repo() -> AsyncMock:
|
|
|
|
|
|
"""AuditLogRepositoryPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_conversation_port() -> AsyncMock:
|
|
|
|
|
|
"""ConversationPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_transaction_port() -> MagicMock:
|
|
|
|
|
|
"""TransactionPort 桩。
|
|
|
|
|
|
|
|
|
|
|
|
``begin`` 为同步方法,返回一个 async context manager 桩
|
|
|
|
|
|
(``__aenter__`` / ``__aexit__`` / ``commit`` / ``rollback`` 均为
|
|
|
|
|
|
AsyncMock),供 ``async with transaction.begin() as tx:`` 使用。
|
|
|
|
|
|
"""
|
|
|
|
|
|
port = MagicMock()
|
|
|
|
|
|
tx_ctx = MagicMock()
|
|
|
|
|
|
tx_ctx.__aenter__ = AsyncMock(return_value=tx_ctx)
|
|
|
|
|
|
tx_ctx.__aexit__ = AsyncMock(return_value=None)
|
|
|
|
|
|
tx_ctx.commit = AsyncMock(return_value=None)
|
|
|
|
|
|
tx_ctx.rollback = AsyncMock(return_value=None)
|
|
|
|
|
|
port.begin.return_value = tx_ctx
|
|
|
|
|
|
return port
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_queue_port() -> AsyncMock:
|
|
|
|
|
|
"""QueuePort 桩,``enqueue`` 默认返回 "job-001"。"""
|
|
|
|
|
|
mock = AsyncMock()
|
|
|
|
|
|
mock.enqueue.return_value = "job-001"
|
|
|
|
|
|
return mock
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_content_review_repo() -> AsyncMock:
|
|
|
|
|
|
"""ContentReviewRepositoryPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_user_identity_repo() -> AsyncMock:
|
|
|
|
|
|
"""UserIdentityRepositoryPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_idempotency_repo() -> AsyncMock:
|
|
|
|
|
|
"""IdempotencyRepositoryPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_persistence_port() -> AsyncMock:
|
|
|
|
|
|
"""PersistencePort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.fixture
|
|
|
|
|
|
def fake_realtime_metrics() -> AsyncMock:
|
|
|
|
|
|
"""RealtimeMetricsPort 桩。"""
|
|
|
|
|
|
return AsyncMock()
|