整理项目文档并优化权限监控与安装脚本
This commit is contained in:
@@ -0,0 +1,365 @@
|
||||
# 前端权限管理集成指南
|
||||
|
||||
## 概述
|
||||
|
||||
本文档说明如何在前端应用中使用新的权限管理功能,包括接口级权限管理和权限系统监控。
|
||||
|
||||
---
|
||||
|
||||
## 功能特性
|
||||
|
||||
### 1. 权限管理页面
|
||||
|
||||
**路由**: `/admin/projects/:id/api-permissions`
|
||||
|
||||
**功能**:
|
||||
- 模块级权限管理(向后兼容)
|
||||
- 接口级权限管理(新增)
|
||||
- 权限系统监控仪表板
|
||||
|
||||
### 2. 权限管理UI
|
||||
|
||||
#### 模块级权限标签页
|
||||
|
||||
- 显示角色 × 模块 × 读写权限的矩阵
|
||||
- 支持权限编辑和保存
|
||||
- 向后兼容现有权限系统
|
||||
|
||||
#### 接口级权限标签页
|
||||
|
||||
- 显示角色 × 接口端点的权限矩阵
|
||||
- 支持按模块、HTTP方法、端点名称搜索和筛选
|
||||
- 支持权限编辑和保存
|
||||
|
||||
#### 权限监控标签页
|
||||
|
||||
- 系统健康评分和状态
|
||||
- 权限检查性能指标
|
||||
- 缓存效率统计
|
||||
- 告警列表展示
|
||||
|
||||
---
|
||||
|
||||
## API 客户端使用
|
||||
|
||||
### 导入 API 函数
|
||||
|
||||
```typescript
|
||||
import {
|
||||
fetchProjectRolePermissions,
|
||||
updateProjectRolePermissions,
|
||||
fetchApiEndpointPermissions,
|
||||
updateApiEndpointPermissions,
|
||||
fetchPermissionMetrics,
|
||||
fetchCacheStats,
|
||||
fetchPermissionAlerts,
|
||||
fetchPermissionHealth,
|
||||
resetPermissionMetrics,
|
||||
clearPermissionAlerts,
|
||||
} from "@/api/projectPermissions";
|
||||
```
|
||||
|
||||
### 获取权限
|
||||
|
||||
```typescript
|
||||
// 获取模块级权限
|
||||
const modulePerms = await fetchProjectRolePermissions(studyId);
|
||||
|
||||
// 获取接口级权限
|
||||
const apiPerms = await fetchApiEndpointPermissions(studyId);
|
||||
```
|
||||
|
||||
### 更新权限
|
||||
|
||||
```typescript
|
||||
// 更新模块级权限
|
||||
await updateProjectRolePermissions(studyId, {
|
||||
roles: {
|
||||
PM: { subjects: { read: true, write: true } },
|
||||
CRA: { subjects: { read: true, write: true } },
|
||||
},
|
||||
});
|
||||
|
||||
// 更新接口级权限
|
||||
await updateApiEndpointPermissions(studyId, {
|
||||
PM: {
|
||||
"POST:/subjects": true,
|
||||
"GET:/subjects": true,
|
||||
},
|
||||
CRA: {
|
||||
"POST:/subjects": true,
|
||||
"GET:/subjects": true,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
### 获取监控数据
|
||||
|
||||
```typescript
|
||||
// 获取权限检查指标
|
||||
const metrics = await fetchPermissionMetrics();
|
||||
|
||||
// 获取缓存统计
|
||||
const cacheStats = await fetchCacheStats();
|
||||
|
||||
// 获取告警列表
|
||||
const alerts = await fetchPermissionAlerts("warning", 20);
|
||||
|
||||
// 获取系统健康状态
|
||||
const health = await fetchPermissionHealth();
|
||||
```
|
||||
|
||||
### 重置监控数据
|
||||
|
||||
```typescript
|
||||
// 重置指标
|
||||
await resetPermissionMetrics();
|
||||
|
||||
// 清除告警
|
||||
await clearPermissionAlerts();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 组件使用
|
||||
|
||||
### ApiPermissions.vue(主页面)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ApiPermissions />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ApiPermissions from "@/views/admin/ApiPermissions.vue";
|
||||
</script>
|
||||
```
|
||||
|
||||
**Props**: 无(从路由参数获取项目ID)
|
||||
|
||||
**事件**: 无
|
||||
|
||||
### ProjectPermissionsModule.vue(模块级权限)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ProjectPermissionsModule
|
||||
:project="project"
|
||||
:matrix="moduleMatrix"
|
||||
@update="onUpdate"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ProjectPermissionsModule from "@/components/ProjectPermissionsModule.vue";
|
||||
import type { ProjectRolePermissionsResponse } from "@/types/api";
|
||||
|
||||
const onUpdate = (matrix: ProjectRolePermissionsResponse) => {
|
||||
// 处理权限更新
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
**Props**:
|
||||
- `project`: 项目信息
|
||||
- `matrix`: 权限矩阵数据
|
||||
|
||||
**事件**:
|
||||
- `update`: 权限矩阵更新时触发
|
||||
|
||||
### ApiEndpointPermissions.vue(接口级权限)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<ApiEndpointPermissions
|
||||
:project="project"
|
||||
:matrix="apiMatrix"
|
||||
@update="onUpdate"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import ApiEndpointPermissions from "@/components/ApiEndpointPermissions.vue";
|
||||
import type { ApiEndpointPermissionsResponse } from "@/types/api";
|
||||
|
||||
const onUpdate = (matrix: ApiEndpointPermissionsResponse) => {
|
||||
// 处理权限更新
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
**Props**:
|
||||
- `project`: 项目信息
|
||||
- `matrix`: 权限矩阵数据
|
||||
|
||||
**事件**:
|
||||
- `update`: 权限矩阵更新时触发
|
||||
|
||||
### PermissionMonitoring.vue(监控仪表板)
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<PermissionMonitoring
|
||||
:metrics="metrics"
|
||||
:health="health"
|
||||
:alerts="alerts"
|
||||
@refresh="onRefresh"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PermissionMonitoring from "@/components/PermissionMonitoring.vue";
|
||||
import type {
|
||||
PermissionMetricsResponse,
|
||||
HealthResponse,
|
||||
AlertsResponse,
|
||||
} from "@/types/api";
|
||||
|
||||
const onRefresh = () => {
|
||||
// 刷新监控数据
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
**Props**:
|
||||
- `metrics`: 权限检查指标
|
||||
- `health`: 系统健康状态
|
||||
- `alerts`: 告警列表
|
||||
|
||||
**事件**:
|
||||
- `refresh`: 刷新按钮点击时触发
|
||||
|
||||
---
|
||||
|
||||
## 类型定义
|
||||
|
||||
### ApiEndpointPermissionsResponse
|
||||
|
||||
```typescript
|
||||
interface ApiEndpointPermissionsResponse {
|
||||
[role: string]: {
|
||||
[endpoint_key: string]: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### ApiEndpointPermissionsUpdate
|
||||
|
||||
```typescript
|
||||
interface ApiEndpointPermissionsUpdate {
|
||||
[role: string]: {
|
||||
[endpoint_key: string]: boolean;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### PermissionMetricsResponse
|
||||
|
||||
```typescript
|
||||
interface PermissionMetricsResponse {
|
||||
check_metrics: PermissionCheckMetrics;
|
||||
cache_metrics: CacheMetrics;
|
||||
uptime_seconds: number;
|
||||
}
|
||||
```
|
||||
|
||||
### HealthResponse
|
||||
|
||||
```typescript
|
||||
interface HealthResponse {
|
||||
status: "healthy" | "degraded" | "unhealthy";
|
||||
health_score: number;
|
||||
issues: string[];
|
||||
metrics: PermissionMetricsResponse;
|
||||
cache_stats: CacheStatsResponse;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 最佳实践
|
||||
|
||||
### 1. 权限管理
|
||||
|
||||
- 定期检查权限配置的完整性
|
||||
- 使用权限监控仪表板监控权限系统状态
|
||||
- 及时处理告警信息
|
||||
|
||||
### 2. 性能优化
|
||||
|
||||
- 监控缓存命中率,目标 > 80%
|
||||
- 监控权限检查响应时间,目标 < 10ms
|
||||
- 定期重置指标以获取准确的统计数据
|
||||
|
||||
### 3. 错误处理
|
||||
|
||||
```typescript
|
||||
try {
|
||||
await updateApiEndpointPermissions(studyId, permissions);
|
||||
ElMessage.success("权限已保存");
|
||||
} catch (error) {
|
||||
ElMessage.error("保存权限失败");
|
||||
console.error(error);
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 故障排除
|
||||
|
||||
### 问题1: 权限数据加载失败
|
||||
|
||||
**症状**: 权限管理页面显示空白或加载失败
|
||||
|
||||
**解决方案**:
|
||||
1. 检查网络连接
|
||||
2. 验证项目ID是否正确
|
||||
3. 检查用户权限是否足够
|
||||
4. 查看浏览器控制台错误日志
|
||||
|
||||
### 问题2: 权限保存失败
|
||||
|
||||
**症状**: 点击保存按钮后没有反应或显示错误
|
||||
|
||||
**解决方案**:
|
||||
1. 检查权限数据格式是否正确
|
||||
2. 验证API端点是否可用
|
||||
3. 检查用户是否有权限修改权限配置
|
||||
4. 查看服务器日志
|
||||
|
||||
### 问题3: 监控数据不更新
|
||||
|
||||
**症状**: 监控仪表板显示的数据不更新
|
||||
|
||||
**解决方案**:
|
||||
1. 点击"刷新指标"按钮手动刷新
|
||||
2. 检查后端监控API是否正常运行
|
||||
3. 检查网络连接
|
||||
4. 查看浏览器控制台错误日志
|
||||
|
||||
---
|
||||
|
||||
## 后续改进
|
||||
|
||||
### 短期
|
||||
|
||||
- [ ] 权限导入/导出功能
|
||||
- [ ] 权限模板功能
|
||||
- [ ] 权限审计日志查看
|
||||
|
||||
### 中期
|
||||
|
||||
- [ ] 权限预测和建议
|
||||
- [ ] 权限使用分析
|
||||
- [ ] 权限风险评估
|
||||
|
||||
### 长期
|
||||
|
||||
- [ ] 资源级权限控制
|
||||
- [ ] 权限继承机制
|
||||
- [ ] 权限工作流审批
|
||||
|
||||
---
|
||||
|
||||
**文档版本**: 1.0
|
||||
**最后更新**: 2026-05-14
|
||||
@@ -0,0 +1,452 @@
|
||||
# 权限系统测试指南
|
||||
|
||||
## 测试环境准备
|
||||
|
||||
### 1. 数据库迁移
|
||||
```bash
|
||||
cd backend
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
### 2. 启动应用
|
||||
```bash
|
||||
uvicorn app.main:app --reload
|
||||
```
|
||||
|
||||
### 3. 验证API端点注册
|
||||
访问 `http://localhost:8000/api/v1/api-permissions/endpoints` 查看所有已注册的API端点。
|
||||
|
||||
## 单元测试
|
||||
|
||||
### 1. 权限检查函数测试
|
||||
|
||||
**文件**: `backend/tests/test_api_permissions.py`
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from app.core.project_permissions import role_has_api_permission
|
||||
from app.models.api_endpoint_permission import ApiEndpointPermission
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_permission_check_allowed(db_session, study_id):
|
||||
"""测试接口级权限检查 - 允许"""
|
||||
# 创建权限记录
|
||||
perm = ApiEndpointPermission(
|
||||
study_id=study_id,
|
||||
role="CRA",
|
||||
endpoint_key="POST:/subjects",
|
||||
allowed=True,
|
||||
)
|
||||
db_session.add(perm)
|
||||
await db_session.commit()
|
||||
|
||||
# 验证权限
|
||||
result = await role_has_api_permission(
|
||||
db_session, study_id, "CRA", "POST:/subjects"
|
||||
)
|
||||
assert result is True
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_permission_check_denied(db_session, study_id):
|
||||
"""测试接口级权限检查 - 拒绝"""
|
||||
# 创建权限记录
|
||||
perm = ApiEndpointPermission(
|
||||
study_id=study_id,
|
||||
role="PV",
|
||||
endpoint_key="POST:/subjects",
|
||||
allowed=False,
|
||||
)
|
||||
db_session.add(perm)
|
||||
await db_session.commit()
|
||||
|
||||
# 验证权限
|
||||
result = await role_has_api_permission(
|
||||
db_session, study_id, "PV", "POST:/subjects"
|
||||
)
|
||||
assert result is False
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_api_permission_fallback_to_module_level(db_session, study_id):
|
||||
"""测试权限回退 - 接口级权限未配置时回退到模块级权限"""
|
||||
# 不创建接口级权限,应该回退到模块级权限
|
||||
result = await role_has_api_permission(
|
||||
db_session, study_id, "CRA", "POST:/subjects"
|
||||
)
|
||||
# 结果取决于模块级权限配置
|
||||
assert isinstance(result, bool)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_admin_always_allowed(db_session, study_id):
|
||||
"""测试ADMIN角色总是被允许"""
|
||||
result = await role_has_api_permission(
|
||||
db_session, study_id, "ADMIN", "POST:/subjects"
|
||||
)
|
||||
assert result is True
|
||||
```
|
||||
|
||||
### 2. 权限配置测试
|
||||
|
||||
**文件**: `backend/tests/test_api_permissions_config.py`
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from app.core.api_permissions import API_ENDPOINT_PERMISSIONS, MODULE_TO_ENDPOINTS
|
||||
|
||||
def test_api_endpoint_permissions_structure():
|
||||
"""测试API端点权限配置结构"""
|
||||
for endpoint_key, config in API_ENDPOINT_PERMISSIONS.items():
|
||||
assert "module" in config
|
||||
assert "action" in config
|
||||
assert "description" in config
|
||||
assert "default_roles" in config
|
||||
assert config["action"] in ["read", "write"]
|
||||
assert isinstance(config["default_roles"], list)
|
||||
|
||||
def test_module_to_endpoints_mapping():
|
||||
"""测试模块到端点的映射"""
|
||||
for module, actions in MODULE_TO_ENDPOINTS.items():
|
||||
assert "read" in actions
|
||||
assert "write" in actions
|
||||
assert isinstance(actions["read"], list)
|
||||
assert isinstance(actions["write"], list)
|
||||
|
||||
# 验证所有端点都在API_ENDPOINT_PERMISSIONS中定义
|
||||
for endpoint_key in actions["read"] + actions["write"]:
|
||||
assert endpoint_key in API_ENDPOINT_PERMISSIONS
|
||||
|
||||
def test_subjects_endpoints_configured():
|
||||
"""测试subjects模块的端点配置"""
|
||||
expected_endpoints = [
|
||||
"POST:/subjects",
|
||||
"GET:/subjects",
|
||||
"GET:/subjects/{id}",
|
||||
"PATCH:/subjects/{id}",
|
||||
"DELETE:/subjects/{id}",
|
||||
]
|
||||
for endpoint_key in expected_endpoints:
|
||||
assert endpoint_key in API_ENDPOINT_PERMISSIONS
|
||||
assert API_ENDPOINT_PERMISSIONS[endpoint_key]["module"] == "subjects"
|
||||
|
||||
def test_fees_endpoints_configured():
|
||||
"""测试fees模块的端点配置"""
|
||||
expected_endpoints = [
|
||||
"POST:/fees/contracts",
|
||||
"GET:/fees/contracts",
|
||||
"GET:/fees/contracts/{id}",
|
||||
"PATCH:/fees/contracts/{id}",
|
||||
"DELETE:/fees/contracts/{id}",
|
||||
"POST:/fees/contracts/{id}/payments",
|
||||
"PATCH:/fees/payments/{id}",
|
||||
"DELETE:/fees/payments/{id}",
|
||||
"POST:/finance/contracts",
|
||||
"GET:/finance/contracts",
|
||||
"GET:/finance/contracts/{id}",
|
||||
"PATCH:/finance/contracts/{id}",
|
||||
"DELETE:/finance/contracts/{id}",
|
||||
]
|
||||
for endpoint_key in expected_endpoints:
|
||||
assert endpoint_key in API_ENDPOINT_PERMISSIONS
|
||||
assert API_ENDPOINT_PERMISSIONS[endpoint_key]["module"] == "fees"
|
||||
```
|
||||
|
||||
## 集成测试
|
||||
|
||||
### 1. 权限管理API测试
|
||||
|
||||
**文件**: `backend/tests/test_api_permissions_endpoints.py`
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_api_endpoints(client: TestClient, admin_token: str):
|
||||
"""测试获取所有API端点"""
|
||||
response = client.get(
|
||||
"/api/v1/api-permissions/endpoints",
|
||||
headers={"Authorization": f"Bearer {admin_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert "endpoints" in data
|
||||
assert len(data["endpoints"]) > 0
|
||||
|
||||
# 验证端点结构
|
||||
for endpoint in data["endpoints"]:
|
||||
assert "endpoint_key" in endpoint
|
||||
assert "method" in endpoint
|
||||
assert "path" in endpoint
|
||||
assert "module" in endpoint
|
||||
assert "action" in endpoint
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_study_api_permissions(client: TestClient, pm_token: str, study_id: str):
|
||||
"""测试获取项目的权限矩阵"""
|
||||
response = client.get(
|
||||
f"/api/v1/studies/{study_id}/api-permissions",
|
||||
headers={"Authorization": f"Bearer {pm_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
|
||||
# 验证权限矩阵结构
|
||||
for role, endpoints in data.items():
|
||||
assert isinstance(endpoints, dict)
|
||||
for endpoint_key, permission in endpoints.items():
|
||||
assert "allowed" in permission
|
||||
assert isinstance(permission["allowed"], bool)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_study_api_permissions(client: TestClient, pm_token: str, study_id: str):
|
||||
"""测试更新项目的权限矩阵"""
|
||||
payload = {
|
||||
"CRA": {
|
||||
"POST:/subjects": True,
|
||||
"GET:/subjects": True,
|
||||
"PATCH:/subjects/{id}": True,
|
||||
},
|
||||
"PV": {
|
||||
"GET:/subjects": True,
|
||||
"GET:/subjects/{id}": True,
|
||||
}
|
||||
}
|
||||
|
||||
response = client.put(
|
||||
f"/api/v1/studies/{study_id}/api-permissions",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {pm_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# 验证权限已更新
|
||||
response = client.get(
|
||||
f"/api/v1/studies/{study_id}/api-permissions",
|
||||
headers={"Authorization": f"Bearer {pm_token}"}
|
||||
)
|
||||
data = response.json()
|
||||
assert data["CRA"]["POST:/subjects"]["allowed"] is True
|
||||
assert data["PV"]["POST:/subjects"]["allowed"] is False
|
||||
```
|
||||
|
||||
### 2. 迁移模块功能测试
|
||||
|
||||
**文件**: `backend/tests/test_migrated_endpoints.py`
|
||||
|
||||
```python
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_subject_with_api_permission(client: TestClient, cra_token: str, study_id: str):
|
||||
"""测试创建参与者 - 使用接口级权限"""
|
||||
payload = {
|
||||
"subject_no": "SUBJ001",
|
||||
"site_id": "site-uuid",
|
||||
"status": "ACTIVE",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1/studies/{study_id}/subjects",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {cra_token}"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_subject_without_permission(client: TestClient, pv_token: str, study_id: str):
|
||||
"""测试创建参与者 - 权限不足"""
|
||||
payload = {
|
||||
"subject_no": "SUBJ001",
|
||||
"site_id": "site-uuid",
|
||||
"status": "ACTIVE",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1/studies/{study_id}/subjects",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {pv_token}"}
|
||||
)
|
||||
assert response.status_code == 403
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_subjects_with_permission(client: TestClient, cra_token: str, study_id: str):
|
||||
"""测试查询参与者列表 - 有权限"""
|
||||
response = client.get(
|
||||
f"/api/v1/studies/{study_id}/subjects",
|
||||
headers={"Authorization": f"Bearer {cra_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_ae_with_permission(client: TestClient, cra_token: str, study_id: str):
|
||||
"""测试创建不良事件 - 使用接口级权限"""
|
||||
payload = {
|
||||
"term": "Headache",
|
||||
"onset_date": "2026-05-13",
|
||||
"seriousness": "MILD",
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1/studies/{study_id}/aes",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {cra_token}"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_create_contract_fee_with_permission(client: TestClient, pm_token: str):
|
||||
"""测试创建费用合同 - 使用接口级权限"""
|
||||
payload = {
|
||||
"projectId": "project-uuid",
|
||||
"centerId": "center-uuid",
|
||||
"contractAmount": 10000,
|
||||
"contractCases": 100,
|
||||
}
|
||||
|
||||
response = client.post(
|
||||
"/api/v1/fees/contracts",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {pm_token}"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
```
|
||||
|
||||
## 端到端测试
|
||||
|
||||
### 1. 权限变更流程
|
||||
|
||||
**场景**: 管理员修改权限后,用户权限立即生效
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_permission_change_takes_effect_immediately(
|
||||
client: TestClient, pm_token: str, cra_token: str, study_id: str
|
||||
):
|
||||
"""测试权限变更立即生效"""
|
||||
# 1. 初始状态:CRA可以创建参与者
|
||||
payload = {"subject_no": "SUBJ001", "site_id": "site-uuid"}
|
||||
response = client.post(
|
||||
f"/api/v1/studies/{study_id}/subjects",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {cra_token}"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
|
||||
# 2. PM修改权限:禁止CRA创建参与者
|
||||
perm_payload = {
|
||||
"CRA": {
|
||||
"POST:/subjects": False,
|
||||
}
|
||||
}
|
||||
response = client.put(
|
||||
f"/api/v1/studies/{study_id}/api-permissions",
|
||||
json=perm_payload,
|
||||
headers={"Authorization": f"Bearer {pm_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# 3. CRA尝试创建参与者:应该被拒绝
|
||||
payload = {"subject_no": "SUBJ002", "site_id": "site-uuid"}
|
||||
response = client.post(
|
||||
f"/api/v1/studies/{study_id}/subjects",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {cra_token}"}
|
||||
)
|
||||
assert response.status_code == 403
|
||||
```
|
||||
|
||||
### 2. 跨模块数据访问
|
||||
|
||||
**场景**: PV创建不良事件时需要读取参与者信息
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_cross_module_data_access(
|
||||
client: TestClient, pv_token: str, study_id: str, subject_id: str
|
||||
):
|
||||
"""测试跨模块数据访问权限"""
|
||||
# 1. PV查询参与者信息(需要GET:/subjects/{id}权限)
|
||||
response = client.get(
|
||||
f"/api/v1/studies/{study_id}/subjects/{subject_id}",
|
||||
headers={"Authorization": f"Bearer {pv_token}"}
|
||||
)
|
||||
assert response.status_code == 200
|
||||
|
||||
# 2. PV创建不良事件(需要POST:/risk-issues权限)
|
||||
payload = {
|
||||
"term": "Headache",
|
||||
"onset_date": "2026-05-13",
|
||||
"seriousness": "MILD",
|
||||
"subject_id": subject_id,
|
||||
}
|
||||
response = client.post(
|
||||
f"/api/v1/studies/{study_id}/aes",
|
||||
json=payload,
|
||||
headers={"Authorization": f"Bearer {pv_token}"}
|
||||
)
|
||||
assert response.status_code == 201
|
||||
```
|
||||
|
||||
## 性能测试
|
||||
|
||||
### 1. 权限检查性能
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_permission_check_performance(db_session, study_id):
|
||||
"""测试权限检查性能"""
|
||||
import time
|
||||
|
||||
start = time.time()
|
||||
for _ in range(1000):
|
||||
await role_has_api_permission(
|
||||
db_session, study_id, "CRA", "POST:/subjects"
|
||||
)
|
||||
elapsed = time.time() - start
|
||||
|
||||
# 1000次权限检查应该在1秒内完成
|
||||
assert elapsed < 1.0
|
||||
```
|
||||
|
||||
## 向后兼容测试
|
||||
|
||||
### 1. 模块级权限回退
|
||||
|
||||
```python
|
||||
@pytest.mark.asyncio
|
||||
async def test_fallback_to_module_level_permission(db_session, study_id):
|
||||
"""测试回退到模块级权限"""
|
||||
# 不创建接口级权限,应该使用模块级权限
|
||||
result = await role_has_api_permission(
|
||||
db_session, study_id, "CRA", "POST:/subjects"
|
||||
)
|
||||
# 结果应该基于模块级权限配置
|
||||
assert isinstance(result, bool)
|
||||
```
|
||||
|
||||
## 运行测试
|
||||
|
||||
```bash
|
||||
# 运行所有测试
|
||||
pytest backend/tests/
|
||||
|
||||
# 运行特定测试文件
|
||||
pytest backend/tests/test_api_permissions.py
|
||||
|
||||
# 运行特定测试
|
||||
pytest backend/tests/test_api_permissions.py::test_api_permission_check_allowed
|
||||
|
||||
# 运行并显示覆盖率
|
||||
pytest backend/tests/ --cov=app --cov-report=html
|
||||
```
|
||||
|
||||
## 验证清单
|
||||
|
||||
- [ ] 所有单元测试通过
|
||||
- [ ] 所有集成测试通过
|
||||
- [ ] 所有端到端测试通过
|
||||
- [ ] 性能测试通过(权限检查 < 1ms)
|
||||
- [ ] 向后兼容测试通过
|
||||
- [ ] 代码覆盖率 > 80%
|
||||
- [ ] 没有安全漏洞
|
||||
- [ ] 文档完整
|
||||
Reference in New Issue
Block a user