PMFlow/backend/tests/test_ai_steward_project_fro...

110 lines
4.5 KiB
Python

from __future__ import annotations
from types import SimpleNamespace
import pytest
from fastapi import FastAPI
from httpx import ASGITransport, AsyncClient
from app.api.ai_steward_api import routes_capture
from app.api.ai_steward_api.schemas import ProjectFromTextResponse
from app.core.database import get_db
from app.dependencies.auth import get_current_user
from app.dependencies.tenant import get_tenant_id
from app.services.ai_steward_service import AIStewardService
@pytest.mark.asyncio
async def test_project_from_text_returns_enveloped_signal_sources(monkeypatch) -> None:
async def fake_create_project_from_natural_language(self, **kwargs): # noqa: ANN001
_ = (self, kwargs)
return {
"project": {"id": "11", "name": "AI 客服", "title": "AI 客服平台", "description": "demo", "status": "active"},
"signal": {"id": "21", "content": "main signal", "impact_score": 0.91},
"charter": {"id": "31", "title": "宪章", "content": {"summary": "demo"}},
"tasks": [],
"risks": [],
"analysis": {
"project_name": "AI 客服",
"project_title": "AI 客服平台",
"tech_stack": ["FastAPI", "React"],
"timeline": "3 周",
"primary_role": "backend",
"requirement_items": [
{
"section": "核心痛点",
"title": "内容本地化",
"description": "翻译公司贵、慢、不地道",
}
],
},
"signal_sources": [
{
"id": "101",
"source_kind": "custom",
"source_name": "核心痛点 - 内容本地化",
"source_identifier": "custom:core-pain-content-localization",
"display_source_kind": "自定义来源",
"technical_identifier": "custom:core-pain-content-localization",
"description": "翻译公司贵、慢、不地道",
"section": "核心痛点",
}
],
}
async def _fake_get_db():
yield object()
monkeypatch.setattr(AIStewardService, "create_project_from_natural_language", fake_create_project_from_natural_language)
app = FastAPI()
app.include_router(routes_capture.router)
app.dependency_overrides[get_db] = _fake_get_db
app.dependency_overrides[get_tenant_id] = lambda: 1
app.dependency_overrides[get_current_user] = lambda: SimpleNamespace(
id=9,
tenant_id=1,
name="管理员",
username="admin",
role="ADMIN",
)
transport = ASGITransport(app=app)
async with AsyncClient(transport=transport, base_url="http://test") as client:
response = await client.post("/ai-steward/project-from-text", json={"input_text": "我们要做一个 AI 客服工具"})
assert response.status_code == 200
payload = response.json()
assert payload["code"] == 0
assert payload["message"] == "success"
assert payload["data"]["project"]["id"] == "11"
assert payload["data"]["signal_sources"][0]["source_name"] == "核心痛点 - 内容本地化"
assert payload["data"]["signal_sources"][0]["display_source_kind"] == "自定义来源"
assert payload["data"]["signal_sources"][0]["technical_identifier"] == "custom:core-pain-content-localization"
def test_project_from_text_response_schema_exposes_signal_source_display_fields() -> None:
schema = ProjectFromTextResponse.model_json_schema()
signal_source = schema["$defs"]["ProjectFromTextSignalSource"]
assert "display_source_kind" in signal_source["properties"]
assert "technical_identifier" in signal_source["properties"]
@pytest.mark.asyncio
async def test_project_requirement_analysis_uses_canonical_function_name(monkeypatch) -> None:
service = AIStewardService(object())
captured: dict[str, object] = {}
async def fake_generate(prompt: str, **kwargs): # noqa: ANN001
_ = prompt
captured["function_name"] = kwargs.get("function_name")
return '{"project_title":"AI 客服","requirement_items":[]}'
monkeypatch.setattr(service.llm, "generate", fake_generate)
analysis = await service._analyze_project_input("请创建一个 AI 客服项目", tenant_id=1)
assert analysis["project_title"] == "AI 客服"
assert captured["function_name"] == "project_requirement_extraction"