239 lines
11 KiB
Python
239 lines
11 KiB
Python
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
|
|
from app.api import rag_ask_contract
|
|
from app.api.space_api import _attach_space_operating_conversation
|
|
from app.schemas.rag import RAGAskRequest
|
|
from app.services.customer_answer_presentation_service import CustomerAnswerPresentationService
|
|
from app.services.space_runtime_context_builder import SpaceRuntimeContextBuilder
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_space_runtime_context_builder_merges_backend_projects_into_surface_context(monkeypatch) -> None:
|
|
builder = SpaceRuntimeContextBuilder(object(), tenant_id=1)
|
|
project = SimpleNamespace(
|
|
id=141,
|
|
title="童析-学龄前儿童行为特质AI评估助手",
|
|
name="童析",
|
|
description="儿童行为特质评估",
|
|
status="active",
|
|
updated_at=None,
|
|
)
|
|
|
|
async def load_projects(*, current_user, limit):
|
|
return [project]
|
|
|
|
async def load_task_counts(*, project_ids):
|
|
return {141: {"total": 3, "completed": 1, "in_progress": 1, "pending": 1, "blocked": 0}}
|
|
|
|
async def load_saved_ideas(*, user_id, limit):
|
|
return [
|
|
{
|
|
"id": "idea_1",
|
|
"object_ref": "saved_idea:idea_1",
|
|
"title": "AI 教练想法",
|
|
"summary": "先暂存,等待整理",
|
|
"status": "saved",
|
|
"readiness_score": 0.4,
|
|
"href": "/space",
|
|
}
|
|
]
|
|
|
|
monkeypatch.setattr(builder, "_load_visible_projects", load_projects)
|
|
monkeypatch.setattr(builder, "_load_task_counts", load_task_counts)
|
|
monkeypatch.setattr(builder, "_load_saved_ideas", load_saved_ideas)
|
|
|
|
runtime_context = await builder.build(
|
|
current_user=SimpleNamespace(id=7, tenant_id=1, role="MEMBER"),
|
|
surface_context={"workbench": {"counts": {"pendingConfirmations": 2}}},
|
|
)
|
|
merged = builder.merge_surface_context(surface_context={}, runtime_context=runtime_context)
|
|
|
|
assert runtime_context["source"] == "backend_authoritative"
|
|
assert runtime_context["space_operating_packet"]["packet_version"] == "space_operating_packet.v1"
|
|
assert runtime_context["space_operating_packet"]["visible_projects"][0]["object_ref"] == "project:141"
|
|
assert runtime_context["space_operating_packet"]["attention_queue"][0]["href"] == "/pmflow-os?projectId=141"
|
|
assert runtime_context["space_operating_packet"]["attention_queue"][0]["object_anchor"] == {
|
|
"object_ref": "project:141",
|
|
"object_type": "project",
|
|
"object_id": "141",
|
|
"surface": "command_center",
|
|
"project_id": "141",
|
|
"project_title": "童析-学龄前儿童行为特质AI评估助手",
|
|
"href": "/pmflow-os?projectId=141",
|
|
"parent_object_ref": None,
|
|
}
|
|
forbidden_actions = set(runtime_context["space_operating_packet"]["forbidden_actions"])
|
|
assert {
|
|
"project_create_without_confirmation",
|
|
"project_memory_write",
|
|
"changeset_commit",
|
|
"execution_handoff",
|
|
"external_command_execution",
|
|
"publish_or_deploy",
|
|
"provider_model_prompt_diagnosis",
|
|
}.issubset(forbidden_actions)
|
|
assert runtime_context["workbench"]["counts"]["projects"] == 1
|
|
assert runtime_context["workbench"]["counts"]["savedIdeas"] == 1
|
|
assert runtime_context["workbench"]["attention_queue"][0]["kind"] == "next_action"
|
|
assert merged["workbench"]["recent_projects"][0]["object_ref"] == "project:141"
|
|
assert merged["workbench"]["recent_projects"][0]["next_action"] == "继续推进待处理任务"
|
|
assert merged["workbench"]["top_next_actions"][0]["href"] == "/pmflow-os?projectId=141"
|
|
assert merged["workbench"]["top_next_actions"][0]["object_ref"] == "project:141"
|
|
assert merged["workbench"]["proactive_suggestions"][0]["requires_user_confirm"] is True
|
|
assert merged["workbench"]["proactive_suggestions"][0]["why_now"]
|
|
assert merged["workbench"]["proactive_suggestions"][0]["required_confirmation"] == "human_confirm_on_target_surface"
|
|
assert merged["workbench"]["proactive_suggestions"][0]["audit_event"] == "space_ai.proactive_suggestion.created"
|
|
assert merged["space_operating_packet"]["automation_policy"]["mode"] == "suggest_only"
|
|
assert "external_command_execution" in merged["space_operating_packet"]["forbidden_actions"]
|
|
assert merged["space_runtime_context"]["source_mix"]["projects"] == "backend"
|
|
assert merged["space_operating_packet"]["packet_version"] == "space_operating_packet.v1"
|
|
|
|
|
|
def test_enriched_runtime_context_feeds_surface_direct_project_answer() -> None:
|
|
builder = SpaceRuntimeContextBuilder(object(), tenant_id=1)
|
|
runtime_context = {
|
|
"packet_version": "space_runtime_context.v1",
|
|
"visibility": "customer",
|
|
"source": "backend_authoritative",
|
|
"workbench": {
|
|
"counts": {"projects": 1, "savedIdeas": 0, "nextActions": 1},
|
|
"recent_projects": [
|
|
{
|
|
"id": 141,
|
|
"project_id": 141,
|
|
"object_ref": "project:141",
|
|
"title": "童析-学龄前儿童行为特质AI评估助手",
|
|
"status": "active",
|
|
"stage": "execution",
|
|
"next_action": "检查进行中任务进展",
|
|
"href": "/pmflow-os?projectId=141",
|
|
}
|
|
],
|
|
"top_next_actions": [
|
|
{
|
|
"title": "检查进行中任务进展",
|
|
"project": "童析-学龄前儿童行为特质AI评估助手",
|
|
"priority": "normal",
|
|
"href": "/execution-bridge?projectId=141&taskId=task-1&evidenceId=progress-1",
|
|
"object_ref": "task:task-1",
|
|
"object_anchor": {
|
|
"object_ref": "task:task-1",
|
|
"object_type": "task",
|
|
"object_id": "task-1",
|
|
"surface": "execution_bridge",
|
|
"project_id": "141",
|
|
"project_title": "童析-学龄前儿童行为特质AI评估助手",
|
|
"href": "/execution-bridge?projectId=141&taskId=task-1&evidenceId=progress-1",
|
|
"parent_object_ref": "project:141",
|
|
},
|
|
}
|
|
],
|
|
},
|
|
}
|
|
request = RAGAskRequest(
|
|
question="现在我有哪些项目",
|
|
surface_context=builder.merge_surface_context(surface_context={}, runtime_context=runtime_context),
|
|
)
|
|
payload = rag_ask_contract.build_surface_direct_answer(
|
|
request=request,
|
|
current_user=SimpleNamespace(id=7, tenant_id=1),
|
|
presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1),
|
|
pmflow_ai_route_plan={
|
|
"source_strategy": "internal_only",
|
|
"surface_direct_eligible": True,
|
|
"allowed_executors": ["surface_direct"],
|
|
"internal_plan": {"objects": ["projects"], "expected_answer_shape": "list"},
|
|
},
|
|
)
|
|
|
|
assert payload is not None
|
|
assert "童析-学龄前儿童行为特质AI评估助手" in payload["answer"]
|
|
assert payload["answer_canvas"]["object_refs"][0]["object_ref"] == "project:141"
|
|
assert payload["answer_canvas"]["next_actions"][0]["href"] == "/execution-bridge?projectId=141&taskId=task-1&evidenceId=progress-1"
|
|
assert payload["answer_canvas"]["next_actions"][0]["object_ref"] == "task:task-1"
|
|
assert payload["answer_canvas"]["next_actions"][0]["object_anchor"]["parent_object_ref"] == "project:141"
|
|
|
|
|
|
def test_surface_direct_refuses_project_mvp_gap_contract() -> None:
|
|
request = RAGAskRequest(
|
|
question="这个项目的MVP还差多少做完",
|
|
surface_context={
|
|
"workbench": {
|
|
"recent_projects": [
|
|
{
|
|
"id": 141,
|
|
"object_ref": "project:141",
|
|
"title": "童析-学龄前儿童行为特质AI评估助手",
|
|
"status": "active",
|
|
"next_action": "继续推进待处理任务",
|
|
}
|
|
]
|
|
}
|
|
},
|
|
)
|
|
|
|
payload = rag_ask_contract.build_surface_direct_answer(
|
|
request=request,
|
|
current_user=SimpleNamespace(id=7, tenant_id=1),
|
|
presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1),
|
|
pmflow_ai_route_plan={
|
|
"source_strategy": "internal_only",
|
|
"answer_contract": "project_mvp_gap_assessment",
|
|
"analysis_task": "gap_assessment",
|
|
"business_frame": "mvp_delivery",
|
|
"surface_direct_eligible": True,
|
|
"surface_direct_allowed": False,
|
|
"allowed_executors": ["surface_direct", "internal_query"],
|
|
},
|
|
)
|
|
|
|
assert payload is None
|
|
|
|
|
|
def test_space_operating_conversation_contract_adds_mode_and_blocks() -> None:
|
|
request = RAGAskRequest(
|
|
question="我今天应该先处理什么?",
|
|
surface_context={
|
|
"surface": "personal_space",
|
|
"workbench": {
|
|
"counts": {"nextActions": 1, "pendingConfirmations": 1, "aiProgress": 0, "savedIdeas": 1},
|
|
},
|
|
"space_operating_packet": {
|
|
"attention_queue": [
|
|
{
|
|
"kind": "pending_confirmation",
|
|
"object_ref": "change_item:21",
|
|
"title": "高风险需求待确认",
|
|
"reason": "范围会影响执行计划",
|
|
"urgency": "high",
|
|
"next_action": "进入待确认处理",
|
|
"href": "/change-review?projectId=141&itemId=21",
|
|
"object_anchor": {
|
|
"object_ref": "change_item:21",
|
|
"object_type": "change_item",
|
|
"object_id": "21",
|
|
"surface": "change_review",
|
|
"project_id": "141",
|
|
"project_title": "童析",
|
|
"href": "/change-review?projectId=141&itemId=21",
|
|
"parent_object_ref": "project:141",
|
|
},
|
|
}
|
|
],
|
|
"pending_confirmations": [
|
|
{"object_ref": "change_item:21", "title": "高风险需求待确认", "href": "/change-review?projectId=141&itemId=21"}
|
|
],
|
|
"saved_ideas": [{"object_ref": "saved_idea:idea-1", "title": "AI 教练", "readiness_score": 0.7}],
|
|
},
|
|
},
|
|
)
|
|
|
|
payload = _attach_space_operating_conversation({"answer": "今天先处理高风险需求。", "answer_canvas": {}}, request=request)
|
|
|
|
assert payload["pmflow_ai_mode"] == "space_operating_partner"
|
|
assert payload["conversation_frame"]["frame_type"] == "agent_run"
|
|
assert payload["answer_canvas"]["conversation_mode"] == "space_operating_partner"
|
|
assert "conversation_blocks" not in payload["answer_canvas"]
|