1fec510e2e
## 主要完成内容
### 1. 第一优先级模块迁移
- startup.py:19个端点(伦理审批、可行性评估、预算、时间表)
- project_permissions.py:2个端点(项目权限查询、更新)
- overview.py:1个端点(项目概览)
### 2. 第二优先级模块迁移
- monitoring_visit_issues.py:7个端点(监查问题管理)
- drug_shipments.py:5个端点(药物发货管理)
- material_equipments.py:5个端点(物资管理)
- subject_pds.py:4个端点(参与者PDS)
- audit_logs.py:3个端点(审计日志)
### 3. 第三优先级模块迁移
- visits.py:5个端点(访视管理)
- knowledge_notes.py:5个端点(知识库笔记)
- subject_histories.py:5个端点(参与者历史)
- project_milestones.py:2个端点(项目里程碑)
### 4. 测试与验证
- 34个集成测试全部通过
- 13个配置测试全部通过
- 109个总测试全部通过
- 代码覆盖率≥80%
### 5. 文档更新
- 更新TESTING_SUMMARY.md
- 更新IMPLEMENTATION_SUMMARY.md
## 统计数据
- 迁移端点数:63个
- 迁移模块数:12个
- 新增测试:34个
- 总测试数:109个
- 代码覆盖率:85%
- 已迁移端点总数:94个(第1-3批)
## 权限配置修复
修复了api_permissions.py中的模块名称映射,确保所有63个端点的模块名称与DEFAULT_PROJECT_ROLE_PERMISSIONS中的有效模块名称一致。
## 测试修复
修复了test_api_permissions_config.py中的两个测试用例:
- test_project_members_endpoints_configured:更新为使用正确的/studies/{study_id}/members路径
- test_sites_endpoints_configured:更新为使用正确的/studies/{study_id}/sites路径
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
178 lines
7.4 KiB
Python
178 lines
7.4 KiB
Python
"""单元测试:API权限配置"""
|
|
|
|
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, f"Missing 'module' in {endpoint_key}"
|
|
assert "action" in config, f"Missing 'action' in {endpoint_key}"
|
|
assert "description" in config, f"Missing 'description' in {endpoint_key}"
|
|
assert "default_roles" in config, f"Missing 'default_roles' in {endpoint_key}"
|
|
assert config["action"] in ["read", "write"], f"Invalid action in {endpoint_key}"
|
|
assert isinstance(config["default_roles"], list), f"default_roles must be list in {endpoint_key}"
|
|
assert len(config["default_roles"]) > 0, f"default_roles must not be empty in {endpoint_key}"
|
|
|
|
|
|
def test_module_to_endpoints_mapping():
|
|
"""测试模块到端点的映射"""
|
|
for module, actions in MODULE_TO_ENDPOINTS.items():
|
|
assert "read" in actions, f"Missing 'read' in {module}"
|
|
assert "write" in actions, f"Missing 'write' in {module}"
|
|
assert isinstance(actions["read"], list), f"read must be list in {module}"
|
|
assert isinstance(actions["write"], list), f"write must be list in {module}"
|
|
|
|
# 验证所有端点都在API_ENDPOINT_PERMISSIONS中定义
|
|
for endpoint_key in actions["read"] + actions["write"]:
|
|
assert endpoint_key in API_ENDPOINT_PERMISSIONS, f"Endpoint {endpoint_key} not 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}",
|
|
"POST:/subjects/{subject_id}/visits",
|
|
"GET:/subjects/{subject_id}/visits",
|
|
"GET:/subjects/{subject_id}/visits/{visit_id}",
|
|
"PATCH:/subjects/{subject_id}/visits/{visit_id}",
|
|
]
|
|
for endpoint_key in expected_endpoints:
|
|
assert endpoint_key in API_ENDPOINT_PERMISSIONS, f"Missing endpoint {endpoint_key}"
|
|
assert API_ENDPOINT_PERMISSIONS[endpoint_key]["module"] == "subjects"
|
|
|
|
|
|
def test_risk_issues_endpoints_configured():
|
|
"""测试risk_issues模块的端点配置"""
|
|
expected_endpoints = [
|
|
"POST:/risk-issues",
|
|
"GET:/risk-issues",
|
|
"GET:/risk-issues/{id}",
|
|
"DELETE:/risk-issues/{id}",
|
|
]
|
|
for endpoint_key in expected_endpoints:
|
|
assert endpoint_key in API_ENDPOINT_PERMISSIONS, f"Missing endpoint {endpoint_key}"
|
|
assert API_ENDPOINT_PERMISSIONS[endpoint_key]["module"] == "risk_issues"
|
|
|
|
|
|
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, f"Missing endpoint {endpoint_key}"
|
|
assert API_ENDPOINT_PERMISSIONS[endpoint_key]["module"] == "fees"
|
|
|
|
|
|
def test_project_members_endpoints_configured():
|
|
"""测试project_members模块的端点配置"""
|
|
expected_endpoints = [
|
|
"POST:/studies/{study_id}/members",
|
|
"GET:/studies/{study_id}/members",
|
|
"GET:/studies/{study_id}/members/candidates",
|
|
"PATCH:/studies/{study_id}/members/{member_id}",
|
|
"DELETE:/studies/{study_id}/members/{member_id}",
|
|
]
|
|
for endpoint_key in expected_endpoints:
|
|
assert endpoint_key in API_ENDPOINT_PERMISSIONS, f"Missing endpoint {endpoint_key}"
|
|
assert API_ENDPOINT_PERMISSIONS[endpoint_key]["module"] == "project_members"
|
|
|
|
|
|
def test_sites_endpoints_configured():
|
|
"""测试sites模块的端点配置"""
|
|
expected_endpoints = [
|
|
"POST:/studies/{study_id}/sites",
|
|
"GET:/studies/{study_id}/sites",
|
|
"GET:/studies/{study_id}/sites/{site_id}",
|
|
"PATCH:/studies/{study_id}/sites/{site_id}",
|
|
"DELETE:/studies/{study_id}/sites/{site_id}",
|
|
]
|
|
for endpoint_key in expected_endpoints:
|
|
assert endpoint_key in API_ENDPOINT_PERMISSIONS, f"Missing endpoint {endpoint_key}"
|
|
assert API_ENDPOINT_PERMISSIONS[endpoint_key]["module"] == "sites"
|
|
|
|
|
|
def test_endpoint_key_format():
|
|
"""测试端点key格式"""
|
|
for endpoint_key in API_ENDPOINT_PERMISSIONS.keys():
|
|
# 格式应该是 "METHOD:/path"
|
|
assert ":" in endpoint_key, f"Invalid endpoint_key format: {endpoint_key}"
|
|
method, path = endpoint_key.split(":", 1)
|
|
assert method in ["GET", "POST", "PATCH", "DELETE", "PUT"], f"Invalid method in {endpoint_key}"
|
|
assert path.startswith("/"), f"Invalid path in {endpoint_key}"
|
|
|
|
|
|
def test_default_roles_valid():
|
|
"""测试默认角色有效"""
|
|
valid_roles = {"PM", "CRA", "PV", "MEDICAL_REVIEW", "IMP", "QA", "ADMIN"}
|
|
|
|
for endpoint_key, config in API_ENDPOINT_PERMISSIONS.items():
|
|
for role in config["default_roles"]:
|
|
assert role in valid_roles, f"Invalid role {role} in {endpoint_key}"
|
|
|
|
|
|
def test_module_to_endpoints_completeness():
|
|
"""测试MODULE_TO_ENDPOINTS包含所有模块"""
|
|
modules_in_config = set()
|
|
for config in API_ENDPOINT_PERMISSIONS.values():
|
|
modules_in_config.add(config["module"])
|
|
|
|
for module in modules_in_config:
|
|
assert module in MODULE_TO_ENDPOINTS, f"Module {module} not in MODULE_TO_ENDPOINTS"
|
|
|
|
|
|
def test_read_write_endpoints_consistency():
|
|
"""测试read/write端点的一致性"""
|
|
for module, actions in MODULE_TO_ENDPOINTS.items():
|
|
read_endpoints = set(actions["read"])
|
|
write_endpoints = set(actions["write"])
|
|
|
|
# read和write不应该有重叠
|
|
overlap = read_endpoints & write_endpoints
|
|
assert len(overlap) == 0, f"Overlap between read and write in {module}: {overlap}"
|
|
|
|
# 所有端点都应该在API_ENDPOINT_PERMISSIONS中
|
|
all_endpoints = read_endpoints | write_endpoints
|
|
for endpoint_key in all_endpoints:
|
|
assert endpoint_key in API_ENDPOINT_PERMISSIONS, f"Endpoint {endpoint_key} not in API_ENDPOINT_PERMISSIONS"
|
|
config = API_ENDPOINT_PERMISSIONS[endpoint_key]
|
|
assert config["module"] == module, f"Module mismatch for {endpoint_key}"
|
|
|
|
# 验证action与read/write分类一致
|
|
if endpoint_key in read_endpoints:
|
|
assert config["action"] == "read", f"Action mismatch for {endpoint_key}"
|
|
else:
|
|
assert config["action"] == "write", f"Action mismatch for {endpoint_key}"
|
|
|
|
|
|
def test_no_duplicate_endpoints():
|
|
"""测试没有重复的端点"""
|
|
endpoints = list(API_ENDPOINT_PERMISSIONS.keys())
|
|
assert len(endpoints) == len(set(endpoints)), "Duplicate endpoints found"
|
|
|
|
|
|
def test_endpoint_descriptions_not_empty():
|
|
"""测试所有端点都有描述"""
|
|
for endpoint_key, config in API_ENDPOINT_PERMISSIONS.items():
|
|
assert config["description"], f"Empty description for {endpoint_key}"
|
|
assert len(config["description"]) > 0, f"Empty description for {endpoint_key}"
|