import os import tempfile import unittest from unittest.mock import AsyncMock, patch from sqlalchemy import and_, select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from app.api.ai_steward import FeishuCaptureRequest, capture_feishu_signal from app.models import Base, FeishuReplyOutbox, User class CaptureFeishuSignalApiTest(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: tmp = tempfile.NamedTemporaryFile(prefix="pmflow-ai-steward-capture-", suffix=".db", delete=False) tmp.close() self.db_path = tmp.name self.engine = create_async_engine(f"sqlite+aiosqlite:///{self.db_path}", future=True) self.session_factory = async_sessionmaker(self.engine, expire_on_commit=False, class_=AsyncSession) async with self.engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) async def asyncTearDown(self) -> None: await self.engine.dispose() if getattr(self, "db_path", None): try: os.remove(self.db_path) except FileNotFoundError: pass async def test_capture_feishu_signal_enqueues_outbox_when_direct_reply_required(self) -> None: async with self.session_factory() as db: user = User( tenant_id=1, username="capture_admin", email="capture_admin@example.com", password_hash="hash", name="捕获管理员", role="ADMIN", status="active", is_active=True, ) db.add(user) await db.commit() await db.refresh(user) with patch( "app.api.ai_steward.ProactiveSignalCaptureService.capture_from_feishu", AsyncMock( return_value={ "handled": True, "status": "captured", "classification": "signal", "ai_trace_id": "ai-pmo-123", "direct_reply_required": True, "direct_reply_text": "已收到,正在执行。", } ), ): payload = await capture_feishu_signal( request=FeishuCaptureRequest( group_chat_id="oc_test_001", message_content="给 AI 劝退助手加一个复杂需求", sender="老板", feishu_message_id="om_test_001", ), current_user=user, db=db, tenant_id=1, ) self.assertTrue(payload["reply_outbox_enqueued"]) self.assertIsInstance(payload["reply_outbox_id"], int) row = ( await db.execute( select(FeishuReplyOutbox).where( and_( FeishuReplyOutbox.tenant_id == 1, FeishuReplyOutbox.source_message_id == "om_test_001", ) ) ) ).scalar_one_or_none() self.assertIsNotNone(row) assert row is not None self.assertEqual(row.chat_id, "oc_test_001") self.assertEqual(row.message_text, "已收到,正在执行。")