Files
hunjing/backend/tests/api/test_import_review.py
T
2026-08-21 16:00:13 +08:00

143 lines
5.2 KiB
Python

"""资料导入候选审核测试(5c)。"""
from collections.abc import AsyncGenerator
import pytest
from httpx import ASGITransport, AsyncClient
from app.application.imports import drain_pending_extractions
from app.main import app
async def _get_character_id(client: AsyncClient) -> str:
"""通过已认证的 API 获取当前用户活跃角色 ID。"""
resp = await client.get("/api/v1/characters/active")
assert resp.status_code == 200
return resp.json()["id"]
class _FakeChatResult:
def __init__(self, content: str) -> None:
self.content = content
_PROFILE_RESPONSE = (
'[{"field":"background","value":"来自北京,从事设计工作",'
'"confidence":0.9,"reason":"资料提及","evidence":[0]}]'
)
_MEMORY_RESPONSE = (
'[{"type":"preference","title":"喜欢简洁回答","content":"用户偏好简洁直接的回答",'
'"importance":0.8,"sensitivity":"low","reason":"多次提及","evidence":[0]}]'
)
@pytest.fixture
async def client(
monkeypatch: pytest.MonkeyPatch, auth_headers: dict[str, str]
) -> AsyncGenerator[AsyncClient, None]:
async def fake_chat(*_args, model_hint, **_kwargs):
if model_hint == "memory-extraction":
return _FakeChatResult(_MEMORY_RESPONSE)
return _FakeChatResult(_PROFILE_RESPONSE)
class _MockMessage:
def __init__(self, content: str):
self.content = content
class _MockManager:
async def chat(self, _messages, model_hint=None):
return await fake_chat(None, model_hint=model_hint)
def to_message_list(self, system, _messages):
return [_MockMessage(s) for s in system]
monkeypatch.setattr("app.application.imports._get_provider_manager", lambda: _MockManager())
transport = ASGITransport(app=app)
async with AsyncClient(
transport=transport, base_url="http://test", headers=auth_headers
) as http_client:
yield http_client
async def _setup(client: AsyncClient, kind: str) -> tuple[str, str, str]:
character_id = await _get_character_id(client)
import_id = (
await client.post(
f"/api/v1/characters/{character_id}/imports",
json={"material_type": "background_story", "content": "我叫小魂,来自北京。"},
)
).json()["id"]
await client.post(f"/api/v1/characters/{character_id}/imports/{import_id}/{kind}")
await drain_pending_extractions()
candidates = (
await client.get(f"/api/v1/characters/{character_id}/imports/{import_id}/{kind}")
).json()
return character_id, import_id, candidates[0]["id"]
@pytest.mark.asyncio
async def test_batch_review_profile_approve_writes_profile(client: AsyncClient) -> None:
character_id, _import_id, candidate_id = await _setup(client, "profile-candidates")
result = await client.post(
f"/api/v1/characters/{character_id}/profile-candidates/batch-review",
json={"decisions": [{"candidate_id": candidate_id, "decision": "approve"}]},
)
assert result.status_code == 200
assert result.json()["applied"] == 1
character = (await client.get(f"/api/v1/characters/{character_id}")).json()
assert character["profile"]["background"] == "来自北京,从事设计工作"
@pytest.mark.asyncio
async def test_batch_review_memory_approve_creates_memory(client: AsyncClient) -> None:
character_id, _import_id, candidate_id = await _setup(client, "memory-candidates")
result = await client.post(
f"/api/v1/characters/{character_id}/memory-candidates/batch-review",
json={"decisions": [{"candidate_id": candidate_id, "decision": "approve"}]},
)
assert result.json()["applied"] == 1
memories = (await client.get("/api/v1/memory")).json()
assert memories["total"] >= 1
assert any(m["title"] == "喜欢简洁回答" for m in memories["items"])
@pytest.mark.asyncio
async def test_batch_review_reject_marks_candidate(client: AsyncClient) -> None:
character_id, import_id, candidate_id = await _setup(client, "profile-candidates")
result = await client.post(
f"/api/v1/characters/{character_id}/profile-candidates/batch-review",
json={"decisions": [{"candidate_id": candidate_id, "decision": "reject"}]},
)
assert result.json()["rejected"] == 1
rejected = (
await client.get(
f"/api/v1/characters/{character_id}/imports/{import_id}/profile-candidates",
params={"status": "rejected"},
)
).json()
assert any(c["id"] == candidate_id for c in rejected)
@pytest.mark.asyncio
async def test_batch_review_skips_non_pending(client: AsyncClient) -> None:
character_id, _import_id, candidate_id = await _setup(client, "profile-candidates")
# 先 approve
await client.post(
f"/api/v1/characters/{character_id}/profile-candidates/batch-review",
json={"decisions": [{"candidate_id": candidate_id, "decision": "approve"}]},
)
# 再审同一候选(已 applied)→ skipped
result = await client.post(
f"/api/v1/characters/{character_id}/profile-candidates/batch-review",
json={"decisions": [{"candidate_id": candidate_id, "decision": "approve"}]},
)
assert result.json()["skipped"] == 1
assert result.json()["applied"] == 0