from __future__ import annotations import unittest from types import SimpleNamespace from unittest.mock import AsyncMock, patch from services.conversation_history import ConversationRecorder class ConversationRecorderTest(unittest.IsolatedAsyncioTestCase): async def test_fixed_reply_transcript_keeps_workflow_metadata(self): recorder = ConversationRecorder("conv_test") recorder._append = AsyncMock() await recorder.record_transport_message( { "type": "transcript", "role": "assistant", "content": "请稍等,我正在处理。", "timestamp": "2026-07-14T10:00:00+08:00", "source": "workflow-fixed-reply", "nodeId": "agent_service", } ) recorder._append.assert_awaited_once_with( "assistant", "请稍等,我正在处理。", "2026-07-14T10:00:00+08:00", { "source": "workflow-fixed-reply", "node_id": "agent_service", }, ) async def test_workflow_trace_is_persisted_without_counting_as_message(self): conversation = SimpleNamespace( extra={"workflow": {"revision": "sha256:test"}}, message_count=3, ) class FakeSession: committed = False async def __aenter__(self): return self async def __aexit__(self, *_args): return None async def get(self, _model, _session_id): return conversation async def commit(self): self.committed = True session = FakeSession() recorder = ConversationRecorder("conv_test") event = { "type": "workflow-event", "eventId": "wfe_test", "event": "node_entered", "timestamp": "2026-08-01T10:00:00+08:00", "workflowRevision": "sha256:test", "transitionId": 0, "nodeId": "start", } with patch( "services.conversation_history.SessionLocal", return_value=session, ): await recorder.record_transport_message(event) await recorder.record_transport_message(event) self.assertTrue(session.committed) self.assertEqual(conversation.message_count, 3) self.assertEqual(len(conversation.extra["workflowTrace"]), 1) saved = conversation.extra["workflowTrace"][0] self.assertEqual(saved["sessionId"], "conv_test") self.assertEqual(saved["sequence"], 1) if __name__ == "__main__": unittest.main()