PMFlow/backend/tests/test_mounted_ai_runtime_wri...

164 lines
5.2 KiB
Python

from __future__ import annotations
import os
import pytest
import pytest_asyncio
from sqlalchemy import JSON, select
from sqlalchemy.ext.asyncio import async_sessionmaker, create_async_engine
os.environ["DATABASE_URL"] = "postgresql+asyncpg://tester:tester@localhost:5432/pmflow_test"
from app.models.base import Base
from app.models.mounted_ai_runtime import (
MountedAgentTrace,
MountedConversationRun,
MountedTopicWorkspace,
)
from app.services.mounted_ai_runtime_write_service import MountedAIRuntimeWriteService
def _use_sqlite_json_types() -> None:
for table in (
MountedTopicWorkspace.__table__,
MountedConversationRun.__table__,
MountedAgentTrace.__table__,
):
for column in table.columns:
if column.name in {
"facts_json",
"goals_json",
"constraints_json",
"options_json",
"open_questions",
"pending_actions",
"bound_entities",
"metadata_json",
"context_layers_hit",
"tool_calls",
"execution_outcome",
"input_summary",
"output_summary",
"tool_trace",
}:
column.type = JSON()
@pytest_asyncio.fixture
async def db_session():
engine = create_async_engine("sqlite+aiosqlite:///:memory:")
_use_sqlite_json_types()
async with engine.begin() as conn:
await conn.run_sync(
lambda sync_conn: Base.metadata.create_all(
sync_conn,
tables=[
MountedTopicWorkspace.__table__,
MountedConversationRun.__table__,
MountedAgentTrace.__table__,
],
)
)
maker = async_sessionmaker(engine, expire_on_commit=False)
async with maker() as session:
yield session
await engine.dispose()
@pytest.mark.asyncio
async def test_begin_ingress_run_uses_thread_anchor_and_sets_runtime_context(db_session) -> None:
service = MountedAIRuntimeWriteService(db_session)
tracking = await service.begin_ingress_run(
tenant_id=1,
source_system="feishu",
source_message_id="om_message_1",
message_text="帮我复盘一下这个直播需求。",
sender="林宇航",
channel_ref="chat_1",
thread_context={
"thread_id": "thread_1",
"root_message_id": "om_root_1",
"reply_to_message_id": "om_reply_1",
},
)
await db_session.commit()
saved_topic = (
await db_session.execute(select(MountedTopicWorkspace))
).scalar_one()
saved_run = (
await db_session.execute(select(MountedConversationRun))
).scalar_one()
assert tracking.topic_workspace.id == saved_topic.id
assert saved_topic.topic_key == "feishu:chat_1:root:om_root_1"
assert saved_topic.thread_key == "thread_1"
assert saved_topic.last_message_id == "om_message_1"
assert saved_run.status == "running"
assert saved_run.mode_before is None
assert saved_run.context_layers_hit == {
"current_message": True,
"thread": True,
"topic": False,
}
@pytest.mark.asyncio
async def test_record_decision_and_finalize_run_persist_topic_mode_and_traces(db_session) -> None:
service = MountedAIRuntimeWriteService(db_session)
tracking = await service.begin_ingress_run(
tenant_id=1,
source_system="feishu",
source_message_id="om_message_2",
message_text="分配给谁做比较合适?",
sender="林宇航",
channel_ref="chat_2",
thread_context={"root_message_id": "om_root_2"},
)
await service.record_orchestrator_decision(
tracking=tracking,
workflow_route="project_signal.create",
intent_name="signal_create",
intent_confidence=0.82,
trigger_reason="manual_review",
auto_execute=False,
work_item_id=321,
route_metadata={"workflow_family": "project_signal", "workflow_action": "create"},
)
await service.finalize_run(
tracking=tracking,
result={
"handled": True,
"status": "captured",
"classification": "command",
"work_item_id": 321,
"workflow_route": "project_signal.create",
"direct_reply_text": "我先帮你收敛需求,再给负责人建议。",
},
)
await db_session.commit()
saved_topic = (
await db_session.execute(select(MountedTopicWorkspace))
).scalar_one()
saved_run = (
await db_session.execute(select(MountedConversationRun))
).scalar_one()
traces = (
await db_session.execute(
select(MountedAgentTrace).order_by(MountedAgentTrace.id.asc())
)
).scalars().all()
assert saved_topic.mode == "plan"
assert saved_topic.metadata_json["last_workflow_route"] == "project_signal.create"
assert saved_run.mode_after == "plan"
assert saved_run.status == "completed"
assert saved_run.final_reply == "我先帮你收敛需求,再给负责人建议。"
assert len(traces) == 2
assert traces[0].agent_name == "ConversationOrchestrator"
assert traces[1].agent_name == "ReplyComposer"