完善审计日志展示与导出权限

This commit is contained in:
Cheng Zhou
2026-06-10 10:20:04 +08:00
parent 1fef12cd86
commit ea3f19e241
33 changed files with 1862 additions and 240 deletions
+97 -2
View File
@@ -1,4 +1,5 @@
import uuid
import json
from dataclasses import dataclass
import pytest
@@ -7,11 +8,11 @@ from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession
from app.api.v1.api_permissions import get_my_study_api_permissions, update_study_api_permissions
from app.api.v1.members import update_member
from app.api.v1.members import add_member, update_member
from app.api.v1.permission_monitoring import resolve_monitoring_scope
from app.api.v1.system_permissions import list_system_permissions
from app.core.deps import require_admin_or_any_project_pm, require_system_permission
from app.schemas.member import StudyMemberUpdate
from app.schemas.member import StudyMemberCreate, StudyMemberUpdate
@dataclass
@@ -40,6 +41,26 @@ async def _seed_user(db: AsyncSession, user_id: uuid.UUID, *, is_admin: bool = F
)
async def _seed_named_user(db: AsyncSession, user_id: uuid.UUID, full_name: str, *, is_admin: bool = False) -> None:
await db.execute(
text(
"""
INSERT INTO users (id, email, password_hash, full_name, clinical_department, is_admin, status)
VALUES (:id, :email, :password_hash, :full_name, :clinical_department, :is_admin, :status)
"""
),
{
"id": str(user_id),
"email": f"{user_id.hex}@example.com",
"password_hash": "hash",
"full_name": full_name,
"clinical_department": "Clinical",
"is_admin": is_admin,
"status": "ACTIVE",
},
)
async def _seed_study(db: AsyncSession, study_id: uuid.UUID, code: str) -> None:
await db.execute(
text(
@@ -289,3 +310,77 @@ async def test_project_pm_cannot_grant_peer_pm_role(db_session: AsyncSession):
)
assert exc_info.value.status_code == 403
@pytest.mark.asyncio
async def test_project_member_add_audit_uses_member_display_name(db_session: AsyncSession):
study_id = uuid.uuid4()
admin_id = uuid.uuid4()
target_user_id = uuid.uuid4()
await _seed_study(db_session, study_id, "MEMBER-AUDIT-NAME")
await _seed_user(db_session, admin_id, is_admin=True)
await _seed_named_user(db_session, target_user_id, "张三")
await db_session.commit()
await add_member(
study_id=study_id,
member_in=StudyMemberCreate(user_id=target_user_id, role_in_study="CRA"),
current_user=UserStub(id=admin_id, is_admin=True),
db=db_session,
)
row = (
await db_session.execute(
text(
"""
SELECT detail
FROM audit_logs
WHERE study_id = :study_id AND action = 'PROJECT_MEMBER_ADDED'
ORDER BY created_at DESC
LIMIT 1
"""
),
{"study_id": str(study_id)},
)
).one()
detail = json.loads(row.detail)
assert detail["targetName"] == "张三"
assert str(target_user_id) not in detail["targetName"]
@pytest.mark.asyncio
async def test_project_member_update_audit_uses_member_display_name(db_session: AsyncSession):
study_id = uuid.uuid4()
admin_id = uuid.uuid4()
target_user_id = uuid.uuid4()
await _seed_study(db_session, study_id, "MEMBER-UPDATE-AUDIT-NAME")
await _seed_user(db_session, admin_id, is_admin=True)
await _seed_named_user(db_session, target_user_id, "李四")
member_id = await _seed_member_return_id(db_session, study_id, target_user_id, "CRA")
await db_session.commit()
await update_member(
study_id=study_id,
member_id=member_id,
member_in=StudyMemberUpdate(role_in_study="CTA"),
current_user=UserStub(id=admin_id, is_admin=True),
db=db_session,
)
row = (
await db_session.execute(
text(
"""
SELECT detail
FROM audit_logs
WHERE study_id = :study_id AND action = 'PROJECT_MEMBER_UPDATED'
ORDER BY created_at DESC
LIMIT 1
"""
),
{"study_id": str(study_id)},
)
).one()
detail = json.loads(row.detail)
assert detail["targetName"] == "李四"
assert str(target_user_id) not in detail["targetName"]