142 lines
5.0 KiB
Python
142 lines
5.0 KiB
Python
from __future__ import annotations
|
|
|
|
import os
|
|
|
|
import pytest
|
|
import pytest_asyncio
|
|
from sqlalchemy import JSON
|
|
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 (
|
|
MountedConversationRun,
|
|
MountedDraftWorkspace,
|
|
MountedTopicWorkspace,
|
|
)
|
|
from app.services.ai_pmo.semantic_context.assembler import SemanticContextAssembler
|
|
|
|
|
|
def _use_sqlite_json_types() -> None:
|
|
for table in (
|
|
MountedTopicWorkspace.__table__,
|
|
MountedDraftWorkspace.__table__,
|
|
MountedConversationRun.__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",
|
|
"requirement_card",
|
|
"draft_payload",
|
|
"required_confirmations",
|
|
"context_layers_hit",
|
|
"tool_calls",
|
|
"execution_outcome",
|
|
}:
|
|
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__,
|
|
MountedDraftWorkspace.__table__,
|
|
MountedConversationRun.__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_semantic_context_assembler_builds_packet_from_runtime_state(db_session) -> None:
|
|
topic = MountedTopicWorkspace(
|
|
tenant_id=7,
|
|
topic_key="feishu:chat_semantic:root:om_root_semantic",
|
|
source_system="feishu",
|
|
channel_ref="chat_semantic",
|
|
conversation_scope_key="feishu:chat_semantic:ou_semantic",
|
|
thread_key="thread_semantic",
|
|
root_message_id="om_root_semantic",
|
|
mode="plan",
|
|
status="active",
|
|
plan_status="collecting",
|
|
facts_json={"title": "OpenCow 风格语义上下文迁移"},
|
|
goals_json=["统一语义入口"],
|
|
constraints_json={"must_not": ["db.write"]},
|
|
open_questions=["当前是不是新需求 topic"],
|
|
pending_actions=["continue_requirement_plan"],
|
|
readiness_score=0.4,
|
|
metadata_json={"last_message_preview": "这次先把语义上下文统一掉"},
|
|
)
|
|
db_session.add(topic)
|
|
await db_session.flush()
|
|
|
|
draft = MountedDraftWorkspace(
|
|
tenant_id=7,
|
|
topic_workspace_id=int(topic.id),
|
|
state="collecting",
|
|
draft_version=2,
|
|
requirement_card={"goal": "统一语义入口", "scope": "先做 shadow mode"},
|
|
draft_payload={"draft_text": "当前先保留 mounted runtime 作为底座"},
|
|
required_confirmations=["需求草稿需要人工确认后才允许正式创建"],
|
|
)
|
|
db_session.add(draft)
|
|
db_session.add(
|
|
MountedConversationRun(
|
|
tenant_id=7,
|
|
trace_id="mounted:feishu:prev",
|
|
topic_workspace_id=int(topic.id),
|
|
source_system="feishu",
|
|
source_message_id="om_prev",
|
|
mode_before="plan",
|
|
mode_after="plan",
|
|
active_agent="PlanCoach",
|
|
status="completed",
|
|
trigger_reason="shadow",
|
|
context_layers_hit={"topic": True},
|
|
execution_outcome={
|
|
"source_message_preview": "先别执行,先整理需求上下文",
|
|
"semantic_decision": {"mode_decision": "stay_plan"},
|
|
},
|
|
final_reply="我先把上下文和草稿整理出来。",
|
|
)
|
|
)
|
|
await db_session.commit()
|
|
|
|
packet = await SemanticContextAssembler(db_session).assemble(
|
|
tenant_id=7,
|
|
operator_user_id=99,
|
|
topic_workspace_id=int(topic.id),
|
|
latest_user_message="这轮继续按 OpenCow 风格整理语义上下文。",
|
|
)
|
|
|
|
assert packet.packet_version == "v1"
|
|
assert packet.topic.topic_key == "feishu:chat_semantic:root:om_root_semantic"
|
|
assert packet.topic.plan_status == "collecting"
|
|
assert packet.draft is not None
|
|
assert packet.draft.draft_version == 2
|
|
assert packet.mode_card.workflow_mode == "PLAN"
|
|
assert "draft.patch" in packet.mode_card.allowed_actions
|
|
assert "project.create" in packet.mode_card.forbidden_actions
|
|
assert packet.thread.recent_turns[0].role == "user"
|
|
assert packet.thread.recent_turns[-1].role == "assistant"
|
|
assert packet.thread.open_questions == ["当前是不是新需求 topic"]
|