"""资料导入 API 测试(api-spec §8,5a 结构解析)。""" 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.fixture async def character_id(client: AsyncClient) -> str: """从认证用户获取活跃角色 ID。""" resp = await client.get("/api/v1/characters/active") assert resp.status_code == 200 return resp.json()["id"] @pytest.mark.asyncio async def test_create_import_text_parses_paragraphs(client: AsyncClient, character_id: str) -> None: resp = await client.post( f"/api/v1/characters/{character_id}/imports", json={ "material_type": "background_story", "format": "text", "content": "第一段背景。\n\n第二段背景。", "title": "背景资料", }, ) assert resp.status_code == 200 job = resp.json() assert job["id"].startswith("import_") assert job["status"] == "parsed" assert job["item_count"] == 2 assert job["material_type"] == "background_story" items = await client.get(f"/api/v1/characters/{character_id}/imports/{job['id']}/items") assert items.status_code == 200 body = items.json() assert body["total"] == 2 assert body["items"][0]["item_type"] == "paragraph" @pytest.mark.asyncio async def test_create_import_chat_history_json(client: AsyncClient, character_id: str) -> None: resp = await client.post( f"/api/v1/characters/{character_id}/imports", json={ "material_type": "chat_history", "format": "json", "content": '[{"role":"user","content":"你好"},{"role":"assistant","content":"你好呀"}]', }, ) assert resp.status_code == 200 job = resp.json() assert job["item_count"] == 2 items = ( await client.get(f"/api/v1/characters/{character_id}/imports/{job['id']}/items") ).json()["items"] assert items[0]["item_type"] == "message" assert items[0]["role"] == "user" assert items[1]["role"] == "assistant" @pytest.mark.asyncio async def test_get_import_and_not_found(client: AsyncClient, character_id: str) -> None: created = ( await client.post( f"/api/v1/characters/{character_id}/imports", json={"material_type": "diary", "content": "今天天气不错。"}, ) ).json() fetched = await client.get(f"/api/v1/characters/{character_id}/imports/{created['id']}") assert fetched.status_code == 200 assert fetched.json()["id"] == created["id"] missing = await client.get(f"/api/v1/characters/{character_id}/imports/import_missing") assert missing.status_code == 404 @pytest.mark.asyncio async def test_list_imports(client: AsyncClient, character_id: str) -> None: await client.post( f"/api/v1/characters/{character_id}/imports", json={"material_type": "diary", "content": "条目一"}, ) await client.post( f"/api/v1/characters/{character_id}/imports", json={"material_type": "diary", "content": "条目二"}, ) resp = await client.get(f"/api/v1/characters/{character_id}/imports") assert resp.status_code == 200 assert resp.json()["total"] == 2 @pytest.mark.asyncio async def test_upload_text_file(client: AsyncClient, character_id: str) -> None: resp = await client.post( f"/api/v1/characters/{character_id}/imports/upload", files={"file": ("note.txt", b"hello world\n\nsecond paragraph", "text/plain")}, data={"title": "笔记"}, ) assert resp.status_code == 200 job = resp.json() assert job["source_type"] == "uploaded_file" assert job["item_count"] == 2 assert job["title"] == "笔记" @pytest.mark.asyncio async def test_upload_audio_stores_metadata_only(client: AsyncClient, character_id: str) -> None: resp = await client.post( f"/api/v1/characters/{character_id}/imports/upload", files={"file": ("sample.wav", b"fake audio bytes", "audio/wav")}, data={ "speaker_label": "人物本人", "consent_status": "granted", "usage_scope": "reference_only", }, ) assert resp.status_code == 200 job = resp.json() assert job["material_type"] == "voice_sample" assert job["media_type"] == "audio" assert job["status"] == "uploaded" assert job["item_count"] == 0 # 媒体资料不切片 @pytest.mark.asyncio async def test_create_import_rejects_empty_content(client: AsyncClient, character_id: str) -> None: resp = await client.post( f"/api/v1/characters/{character_id}/imports", json={"material_type": "background_story", "content": ""}, ) assert resp.status_code == 422