初始化提交

This commit is contained in:
chenxin committed 2026-08-21 16:00:13 +08:00
commit 887c1f449e
963 files changed
+166869

No files matched your search

+179
View File
@@ -0,0 +1,179 @@
"""默认人物模板测试。"""
from collections.abc import AsyncGenerator
import pytest
from httpx import ASGITransport, AsyncClient
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.errors import AppError
from app.db.init_data import seed_default_data
from app.db.session import AsyncSessionLocal
from app.main import app
from app.models.character import Character
from app.models.personality import PersonalityVersion
from app.models.user import User
from app.repositories import characters as character_repo
from app.repositories import personality as personality_repo
from app.repositories.json_utils import dumps
from app.modules.character import (
MODEL_POLICY_CREATE_DEFAULTS,
SYSTEM_TEMPLATE_OWNER_SCOPE,
ensure_character_mutable,
)
@pytest.fixture
async def db_session() -> AsyncGenerator[AsyncSession, None]:
async with AsyncSessionLocal() as session:
yield session
async def _seeded_admin_scope(db: AsyncSession) -> str:
"""获取种子数据中管理员用户的 owner_scope。"""
result = await db.execute(select(User).where(User.is_admin.is_(True)).limit(1))
admin = result.scalar_one_or_none()
assert admin is not None, "种子数据应已创建管理员用户"
return admin.id
@pytest.mark.asyncio
async def test_seed_creates_system_template_and_user_copy(db_session: AsyncSession) -> None:
db = db_session
template = await character_repo.get_system_template_by_key(db, "default")
assert template is not None
assert template.is_system_template is True
assert template.owner_scope == "system"
assert template.template_key == "default"
admin_scope = await _seeded_admin_scope(db)
character = await character_repo.get_active_character(db, admin_scope)
assert character is not None
assert character.is_system_template is False
assert character.source_template_id == template.id
assert character.id != template.id
@pytest.mark.asyncio
async def test_active_character_api_returns_user_copy() -> None:
transport = ASGITransport(app=app)
async with AsyncSessionLocal() as session:
admin_scope = await _seeded_admin_scope(session)
from app.services.auth import create_access_token
token = create_access_token(subject=admin_scope, extra={"owner_scope": admin_scope})
async with AsyncClient(
transport=transport,
base_url="http://test",
headers={"Authorization": f"Bearer {token}"},
) as client:
response = await client.get("/api/v1/characters/active")
assert response.status_code == 200
body = response.json()
assert body["is_system_template"] is False
assert body["source_template_id"]
assert not body["owner_scope"].startswith("local-user")
@pytest.mark.asyncio
async def test_personality_is_copied_from_template(db_session: AsyncSession) -> None:
db = db_session
template = await character_repo.get_system_template_by_key(db, "default")
admin_scope = await _seeded_admin_scope(db)
character = await character_repo.get_active_character(db, admin_scope)
assert template is not None
assert character is not None
assert template.current_personality_version_id is not None
assert character.current_personality_version_id is not None
assert template.current_personality_version_id != character.current_personality_version_id
template_personality = await personality_repo.get_personality(
db, template.current_personality_version_id
)
user_personality = await personality_repo.get_personality(
db, character.current_personality_version_id
)
assert template_personality is not None
assert user_personality is not None
assert template_personality.character_id == template.id
assert user_personality.character_id == character.id
assert user_personality.reason == "system_template_copy"
from app.services.auth import create_access_token
token = create_access_token(subject=admin_scope, extra={"owner_scope": admin_scope})
transport = ASGITransport(app=app)
async with AsyncClient(
transport=transport,
base_url="http://test",
headers={"Authorization": f"Bearer {token}"},
) as client:
response = await client.get("/api/v1/personality/current")
assert response.status_code == 200
assert response.json()["id"] == user_personality.id
@pytest.mark.asyncio
async def test_seed_default_data_is_idempotent(db_session: AsyncSession) -> None:
db = db_session
await seed_default_data(db)
await seed_default_data(db)
# 种子数据幂等:只应有一名管理员用户
result = await db.execute(select(User).where(User.is_admin.is_(True)))
admins = result.scalars().all()
assert len(admins) == 1
admin_scope = admins[0].id
template_count = await db.scalar(
select(func.count()).select_from(Character).where(Character.is_system_template.is_(True))
)
user_count = await db.scalar(
select(func.count())
.select_from(Character)
.where(
Character.owner_scope == admin_scope,
Character.is_system_template.is_(False),
)
)
personality_count = await db.scalar(select(func.count()).select_from(PersonalityVersion))
assert template_count == 5
# is_default=true 模板只有一个(default),所以用户角色数为 1
assert user_count == 1
# 人格版本:每个模板 + 每个角色的复制
assert personality_count >= 7
@pytest.mark.asyncio
async def test_seed_does_not_overwrite_existing_user_character(db_session: AsyncSession) -> None:
db = db_session
admin_scope = await _seeded_admin_scope(db)
character = await character_repo.get_active_character(db, admin_scope)
assert character is not None
character.profile_json = dumps({"background": "用户已自定义"})
character.model_policy_json = dumps({"default_chat_model_hint": "custom-model"})
await db.commit()
await seed_default_data(db)
await db.refresh(character)
assert character.profile_json == dumps({"background": "用户已自定义"})
assert character.model_policy_json == dumps({"default_chat_model_hint": "custom-model"})
@pytest.mark.asyncio
async def test_system_template_is_not_mutable(db_session: AsyncSession) -> None:
db = db_session
template = await character_repo.get_system_template_by_key(db, "default")
admin_scope = await _seeded_admin_scope(db)
character = await character_repo.get_active_character(db, admin_scope)
assert template is not None
assert character is not None
with pytest.raises(ValueError, match="系统人物模板不可修改"):
ensure_character_mutable(template.is_system_template)
ensure_character_mutable(character.is_system_template)