import time from types import SimpleNamespace import pytest from app.schemas.rag import RAGAskRequest from app.services.customer_answer_presentation_service import CustomerAnswerPresentationService from app.services.pmflow_ai_turn_runtime_service import PMFlowAITurnRuntimeService def _service() -> PMFlowAITurnRuntimeService: return PMFlowAITurnRuntimeService(object(), tenant_id=1) def _assistant_state(): return _service().build_initial_turn_state( request=RAGAskRequest(question="who are you"), route_plan={}, turn_hypothesis={ "normalized_goal": "assistant_identity", "desired_outcome": "说明 PMFlow AI 身份和能力边界", "answer_shape": "general_answer", "goal_frames": [ { "frame_id": "assistant-profile", "operation_kind": "explain_assistant", "target_scope": {"scope_type": "current_space", "binding_strength": "surface"}, "target_object_requirement": "none", "source_policy": "none", "answer_shape": "general_answer", "confidence": 0.9, } ], "operation_gaps": [], }, ) @pytest.mark.asyncio async def test_packet_ao_quality_gate_rewrites_internal_runtime_leak(monkeypatch) -> None: service = _service() state = _assistant_state() async def bad_render(*, request, turn_state): _ = request, turn_state return "problem_frame: assistant_identity; evidence_findings: static contract" monkeypatch.setattr(service, "_render_answer_from_registry", bad_render) payload = await service._run_state_answer_synthesis( request=RAGAskRequest(question="who are you"), current_user=SimpleNamespace(id=1, tenant_id=1), route_plan={}, turn_state=state, presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1), started_at=time.perf_counter(), ) finding_types = {item["finding_type"] for item in payload["answer_runtime_packet"]["quality_findings"]} assert "internal_runtime_leak" in finding_types assert "problem_frame" not in payload["answer"] assert "evidence_findings" not in payload["answer"] assert "PMFlow AI" in payload["answer"] assert payload["answer_runtime_packet"]["draft"]["draft_id"] == "draft_quality_checked" assert any(step.key == "revise_answer_quality" for step in state.runtime_steps) @pytest.mark.asyncio async def test_packet_ao_quality_gate_rewrites_plan_echo(monkeypatch) -> None: service = _service() state = _assistant_state() async def bad_render(*, request, turn_state): _ = request return turn_state.answer_plan.answer_strategy monkeypatch.setattr(service, "_render_answer_from_registry", bad_render) payload = await service._run_state_answer_synthesis( request=RAGAskRequest(question="who are you"), current_user=SimpleNamespace(id=1, tenant_id=1), route_plan={}, turn_state=state, presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1), started_at=time.perf_counter(), ) finding_types = {item["finding_type"] for item in payload["answer_runtime_packet"]["quality_findings"]} assert "plan_echo" in finding_types assert state.answer_plan.answer_strategy not in payload["answer"] assert "项目操作副驾" in payload["answer"] @pytest.mark.asyncio async def test_packet_ao_quality_gate_rewrites_empty_draft(monkeypatch) -> None: service = _service() state = _assistant_state() async def bad_render(*, request, turn_state): _ = request, turn_state return "" monkeypatch.setattr(service, "_render_answer_from_registry", bad_render) payload = await service._run_state_answer_synthesis( request=RAGAskRequest(question="who are you"), current_user=SimpleNamespace(id=1, tenant_id=1), route_plan={}, turn_state=state, presentation_service=CustomerAnswerPresentationService(object(), tenant_id=1), started_at=time.perf_counter(), ) finding_types = {item["finding_type"] for item in payload["answer_runtime_packet"]["quality_findings"]} assert "empty_answer" in finding_types assert payload["answer"] assert "PMFlow AI" in payload["answer"]