2026-04-08 15:37:09 +08:00
|
|
|
|
"""对话落库返回人/助 message id,供 segment lineage 配对。"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
2026-04-08 15:37:09 +08:00
|
|
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
|
|
|
|
|
|
|
from app.features.conversation.history_store import HumanAiTurnIds
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
@asynccontextmanager
|
|
|
|
|
|
async def _capture_transactional(db):
|
|
|
|
|
|
yield db
|
|
|
|
|
|
await db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-04-08 15:37:09 +08:00
|
|
|
|
@pytest.mark.asyncio
|
2026-05-22 13:44:50 +08:00
|
|
|
|
async def test_record_human_ai_turn_returns_both_message_ids() -> None:
|
2026-04-08 15:37:09 +08:00
|
|
|
|
conv_id = "conv-1"
|
|
|
|
|
|
captured: list[object] = []
|
|
|
|
|
|
|
|
|
|
|
|
class FakeMsg:
|
|
|
|
|
|
def __init__(self, **kwargs) -> None:
|
|
|
|
|
|
for k, v in kwargs.items():
|
|
|
|
|
|
setattr(self, k, v)
|
|
|
|
|
|
|
|
|
|
|
|
class _FakeRepo:
|
|
|
|
|
|
@staticmethod
|
|
|
|
|
|
def add_conversation_message(msg: object, db) -> None:
|
|
|
|
|
|
captured.append(msg)
|
|
|
|
|
|
|
|
|
|
|
|
db = MagicMock(spec=AsyncSession)
|
|
|
|
|
|
db.commit = AsyncMock()
|
|
|
|
|
|
|
2026-05-22 13:44:50 +08:00
|
|
|
|
with patch(
|
|
|
|
|
|
"app.features.conversation.history_store.transactional",
|
|
|
|
|
|
_capture_transactional,
|
|
|
|
|
|
), patch(
|
|
|
|
|
|
"app.features.conversation.history_store.ConversationMessage",
|
|
|
|
|
|
FakeMsg,
|
|
|
|
|
|
), patch(
|
|
|
|
|
|
"app.features.conversation.history_store.repo",
|
|
|
|
|
|
_FakeRepo,
|
|
|
|
|
|
):
|
2026-04-08 15:37:09 +08:00
|
|
|
|
from app.features.conversation import history_store as hs
|
|
|
|
|
|
|
|
|
|
|
|
store = hs.ConversationHistoryStore(db)
|
|
|
|
|
|
store._sync_redis_best_effort = AsyncMock() # type: ignore[method-assign]
|
|
|
|
|
|
store._touch_conversation = AsyncMock() # type: ignore[method-assign]
|
|
|
|
|
|
|
|
|
|
|
|
out = await store.record_human_ai_turn(
|
|
|
|
|
|
conv_id,
|
|
|
|
|
|
"hello",
|
|
|
|
|
|
["reply a", "reply b"],
|
|
|
|
|
|
user_message_timestamp=None,
|
|
|
|
|
|
is_from_voice=False,
|
|
|
|
|
|
voice_session_id=None,
|
|
|
|
|
|
audio_duration_seconds=None,
|
|
|
|
|
|
tts_audio_urls=None,
|
|
|
|
|
|
segment_id="seg-1",
|
|
|
|
|
|
memory_retrieval_trace=None,
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
assert isinstance(out, HumanAiTurnIds)
|
|
|
|
|
|
assert len(captured) == 2
|
|
|
|
|
|
assert captured[0].role == "human"
|
|
|
|
|
|
assert captured[1].role == "ai"
|
|
|
|
|
|
assert captured[0].segment_id == "seg-1"
|
|
|
|
|
|
assert out.human_message_id == captured[0].id
|
|
|
|
|
|
assert out.assistant_message_id == captured[1].id
|
2026-05-22 13:44:50 +08:00
|
|
|
|
db.commit.assert_awaited_once()
|