"""内部公文权限控制测试。""" import pytest from starlette.responses import JSONResponse from fastapi_modules.fastapi_leaudit.controllers.govdocController import GovdocController from fastapi_modules.fastapi_leaudit.domian.vo.rbacVo import RbacRouteVO from fastapi_modules.fastapi_leaudit.services.impl.rbacServiceImpl import RbacServiceImpl class _DenyPermissionService: """拒绝所有权限的测试权限服务。""" async def CheckPermission(self, user_id: int, permission_key: str) -> bool: """检查权限。""" return False class _AllowPermissionService: """允许所有权限的测试权限服务。""" async def CheckPermission(self, user_id: int, permission_key: str) -> bool: """检查权限。""" return True class _FakeGovdocService: """记录调用的测试公文服务。""" def __init__(self) -> None: self.list_called = False self.upload_called = False async def ListDocuments(self, **kwargs): """记录列表调用。""" self.list_called = True return {"items": [], "total": 0, "page": kwargs["page"], "pageSize": kwargs["pageSize"]} async def UploadDocument(self, **kwargs): """记录上传调用。""" self.upload_called = True return {"documentId": 1} def _find_endpoint(controller: GovdocController, path: str, method: str): """根据路径和方法查找路由 endpoint。""" full_path = f"{controller.router.prefix}{path}" for route in controller.router.routes: if getattr(route, "path", "") == full_path and method in getattr(route, "methods", set()): return route.endpoint raise AssertionError(f"未找到路由 {method} {full_path}") @pytest.mark.asyncio async def test_govdoc_list_requires_document_read_permission(): """公文列表无查看权限时返回 403,且不调用业务服务。""" controller = GovdocController() service = _FakeGovdocService() controller.GovdocService = service controller.PermissionService = _DenyPermissionService() endpoint = _find_endpoint(controller, "/documents", "GET") response = await endpoint( page=1, pageSize=20, keyword=None, fileExt=None, region=None, tenant_code=None, entry_module_id=None, type_ids=None, document_type_id=None, status=None, resultStatus=None, createdBy=None, dateFrom=None, dateTo=None, payload={"user_id": 7}, ) assert isinstance(response, JSONResponse) assert response.status_code == 403 assert service.list_called is False @pytest.mark.asyncio async def test_govdoc_upload_requires_document_create_permission(): """公文上传无创建权限时返回 403,且不调用业务服务。""" controller = GovdocController() service = _FakeGovdocService() controller.GovdocService = service controller.PermissionService = _DenyPermissionService() endpoint = _find_endpoint(controller, "/documents", "POST") response = await endpoint(file=object(), payload={"user_id": 7}) assert isinstance(response, JSONResponse) assert response.status_code == 403 assert service.upload_called is False @pytest.mark.asyncio async def test_govdoc_list_calls_service_when_permission_granted(): """公文列表有查看权限时正常调用业务服务。""" controller = GovdocController() service = _FakeGovdocService() controller.GovdocService = service controller.PermissionService = _AllowPermissionService() endpoint = _find_endpoint(controller, "/documents", "GET") response = await endpoint( page=1, pageSize=20, keyword=None, fileExt=None, region=None, tenant_code=None, entry_module_id=None, type_ids=None, document_type_id=None, status=None, resultStatus=None, createdBy=None, dateFrom=None, dateTo=None, payload={"user_id": 7}, ) assert response.data["total"] == 0 assert service.list_called is True def test_govdoc_root_route_marks_frontend_route_set_ready(): """只有内部公文模块路由时也视为新版路由,避免兼容菜单补出列表和上传。""" service = RbacServiceImpl() routes = [ RbacRouteVO( id=1, route_path="/govdoc", route_name="govdoc", component="govdoc", parent_id=None, route_title="内部公文处理", ) ] assert service._isFrontendRouteSetReady(routes) is True def test_govdoc_parent_route_does_not_expose_ungranted_child_routes(): """只有内部公文父路由和接口权限时,不应补出未勾选的列表/上传子路由。""" service = RbacServiceImpl() routes = [ RbacRouteVO( id=1, route_path="/govdoc", route_name="govdoc", component="govdoc", parent_id=None, route_title="内部公文处理", children=[ RbacRouteVO( id=2, route_path="/govdoc/audits", route_name="govdoc-audits", component="govdoc.audits", parent_id=1, route_title="公文列表", ), RbacRouteVO( id=3, route_path="/govdoc/upload", route_name="govdoc-upload", component="govdoc.upload", parent_id=1, route_title="公文上传", ), ], ) ] filtered = service._filterRoutesByRouteAndPermissionScope( routes, {"/govdoc"}, {"govdoc:document:read", "govdoc:document:create"}, ) paths = service._collectRoutePaths(filtered) assert "/govdoc" in paths assert "/govdoc/audits" not in paths assert "/govdoc/upload" not in paths def test_legacy_govdoc_audit_route_does_not_grant_current_govdoc_child_route(): """旧 /govdoc-audit 残留授权不应继续放行当前 /govdoc 子路由。""" service = RbacServiceImpl() routes = [ RbacRouteVO( id=1, route_path="/govdoc", route_name="govdoc", component="govdoc", parent_id=None, route_title="内部公文处理", ), RbacRouteVO( id=2, route_path="/govdoc-audit/audits", route_name="legacy-govdoc-audits", component="govdoc-audit.audits", parent_id=None, route_title="旧公文列表", ), RbacRouteVO( id=3, route_path="/govdoc-audit/upload", route_name="legacy-govdoc-upload", component="govdoc-audit.upload", parent_id=None, route_title="旧公文上传", ), ] filtered = service._filterRoutesByRouteAndPermissionScope( routes, service._collectCurrentFrontendRoutePaths(routes), {"govdoc:document:read", "govdoc:document:create"}, ) paths = service._collectRoutePaths(filtered) assert "/govdoc" in paths assert "/govdoc-audit/audits" not in paths assert "/govdoc-audit/upload" not in paths assert "/govdoc/audits" not in paths assert "/govdoc/upload" not in paths