Files
2026-08-21 16:00:13 +08:00

154 lines
5.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""人物管理 API 测试(api-spec §6)。
注意:所有需要用户认证的端点使用 ``auth_headers`` 注入 Bearer token。
"""
from collections.abc import AsyncGenerator
import pytest
from httpx import ASGITransport, AsyncClient
from app.main import app
@pytest.fixture
async def client(auth_headers: dict[str, str]) -> AsyncGenerator[AsyncClient, None]:
transport = ASGITransport(app=app)
async with AsyncClient(
transport=transport,
base_url="http://test",
headers=auth_headers,
) as http_client:
yield http_client
@pytest.mark.asyncio
async def test_list_returns_seeded_default(client: AsyncClient) -> None:
resp = await client.get("/api/v1/characters")
assert resp.status_code == 200
body = resp.json()
assert body["total"] >= 1
item = body["items"][0]
assert item["id"].startswith("character_")
assert item["status"] == "active"
assert item["personality_version"] >= 1
assert item["memory_count"] == 0
@pytest.mark.asyncio
async def test_create_character_defaults_to_wuji(client: AsyncClient) -> None:
resp = await client.post("/api/v1/characters", json={"name": "新人物"})
assert resp.status_code == 200
body = resp.json()
assert body["status"] == "wuji"
assert body["name"] == "新人物"
assert body["owner_scope"] is not None
assert body["is_system_template"] is False
assert body["model_policy"]["default_chat_model_hint"]
listed = await client.get("/api/v1/characters")
assert listed.json()["total"] >= 2
@pytest.mark.asyncio
async def test_create_character_rejects_empty_name(client: AsyncClient) -> None:
resp = await client.post("/api/v1/characters", json={"name": ""})
assert resp.status_code == 422
@pytest.mark.asyncio
async def test_list_filter_by_status(client: AsyncClient) -> None:
await client.post("/api/v1/characters", json={"name": "空白人物"})
resp = await client.get("/api/v1/characters?status=wuji")
assert resp.status_code == 200
body = resp.json()
assert body["total"] == 1
assert body["items"][0]["status"] == "wuji"
@pytest.mark.asyncio
async def test_get_character_and_not_found(client: AsyncClient) -> None:
active = await client.get("/api/v1/characters/active")
character_id = active.json()["id"]
resp = await client.get(f"/api/v1/characters/{character_id}")
assert resp.status_code == 200
assert resp.json()["id"] == character_id
missing = await client.get("/api/v1/characters/character_does_not_exist")
assert missing.status_code == 404
@pytest.mark.asyncio
async def test_update_profile_and_model_policy(client: AsyncClient) -> None:
character_id = (await client.get("/api/v1/characters/active")).json()["id"]
profile = await client.patch(
f"/api/v1/characters/{character_id}/profile",
json={"display_name": "昵称", "background": "背景故事"},
)
assert profile.status_code == 200
assert profile.json()["display_name"] == "昵称"
assert profile.json()["profile"]["background"] == "背景故事"
policy = await client.patch(
f"/api/v1/characters/{character_id}/model-policy",
json={"privacy_level": "sensitive", "default_chat_model_hint": "custom-chat"},
)
assert policy.status_code == 200
body = policy.json()
assert body["privacy_level"] == "sensitive"
assert body["model_policy"]["privacy_level"] == "sensitive"
assert body["model_policy"]["default_chat_model_hint"] == "custom-chat"
@pytest.mark.asyncio
async def test_activate_switches_active_character(client: AsyncClient) -> None:
char_a = (await client.get("/api/v1/characters/active")).json()["id"]
char_b = (await client.post("/api/v1/characters", json={"name": "B"})).json()["id"]
resp = await client.post(f"/api/v1/characters/{char_b}/activate")
assert resp.status_code == 200
assert resp.json() == {"active_character_id": char_b, "status": "active"}
# A 被切到 paused(A1 互斥)
a_after = await client.get(f"/api/v1/characters/{char_a}")
assert a_after.json()["status"] == "paused"
# 当前激活指向 B
assert (await client.get("/api/v1/characters/active")).json()["id"] == char_b
@pytest.mark.asyncio
async def test_pause_resume_archive(client: AsyncClient) -> None:
char_b = (await client.post("/api/v1/characters", json={"name": "B"})).json()["id"]
await client.post(f"/api/v1/characters/{char_b}/activate")
paused = await client.post(f"/api/v1/characters/{char_b}/pause")
assert paused.status_code == 200
assert paused.json()["status"] == "paused"
# 暂停当前唯一激活人物后无 active
no_active = await client.get("/api/v1/characters/active")
assert no_active.status_code == 404
resumed = await client.post(f"/api/v1/characters/{char_b}/resume")
assert resumed.status_code == 200
assert resumed.json()["status"] == "active"
archived = await client.post(f"/api/v1/characters/{char_b}/archive")
assert archived.status_code == 200
assert archived.json()["status"] == "archived"
# 归档人物不可直接激活
again = await client.post(f"/api/v1/characters/{char_b}/activate")
assert again.status_code == 409
@pytest.mark.asyncio
async def test_pause_rejects_non_active(client: AsyncClient) -> None:
char_b = (await client.post("/api/v1/characters", json={"name": "B"})).json()["id"]
# wuji 状态不可暂停
resp = await client.post(f"/api/v1/characters/{char_b}/pause")
assert resp.status_code == 409