feat: update audit platform workspace
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.crossReviewServiceImpl import CrossReviewServiceImpl
|
||||
|
||||
|
||||
def test_cross_review_task_item_masks_progress_without_permission():
|
||||
item = CrossReviewServiceImpl._build_task_item_vo(
|
||||
row={
|
||||
"task_id": 10,
|
||||
"task_name": "交叉评查任务",
|
||||
"task_type": "CITY",
|
||||
"doc_type_id": 2,
|
||||
"doc_type_code": "contract",
|
||||
"status": "in_progress",
|
||||
"total_documents": 4,
|
||||
"completed_documents": 1,
|
||||
"current_user_role": "principal",
|
||||
"current_user_can_confirm": True,
|
||||
"create_time": None,
|
||||
"evaluation_tenants": [],
|
||||
"evaluation_regions": [],
|
||||
},
|
||||
CanViewProgress=False,
|
||||
)
|
||||
|
||||
assert item.progress is None
|
||||
assert item.totalDocuments is None
|
||||
assert item.completedDocuments is None
|
||||
assert item.currentUserRole == "principal"
|
||||
assert item.currentUserCanConfirm is True
|
||||
|
||||
|
||||
def test_cross_review_task_item_keeps_progress_with_permission():
|
||||
item = CrossReviewServiceImpl._build_task_item_vo(
|
||||
row={
|
||||
"task_id": 10,
|
||||
"task_name": "交叉评查任务",
|
||||
"task_type": "CITY",
|
||||
"doc_type_id": 2,
|
||||
"doc_type_code": "contract",
|
||||
"status": "in_progress",
|
||||
"total_documents": 4,
|
||||
"completed_documents": 1,
|
||||
"current_user_role": "participant",
|
||||
"current_user_can_confirm": False,
|
||||
"create_time": None,
|
||||
"evaluation_tenants": [],
|
||||
"evaluation_regions": [],
|
||||
},
|
||||
CanViewProgress=True,
|
||||
)
|
||||
|
||||
assert item.progress == 25
|
||||
assert item.totalDocuments == 4
|
||||
assert item.completedDocuments == 1
|
||||
assert item.currentUserRole == "participant"
|
||||
assert item.currentUserCanConfirm is False
|
||||
@@ -0,0 +1,128 @@
|
||||
"""内部公文权限控制测试。"""
|
||||
|
||||
import pytest
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
from fastapi_modules.fastapi_leaudit.controllers.govdocController import GovdocController
|
||||
|
||||
|
||||
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
|
||||
@@ -0,0 +1,73 @@
|
||||
"""首页统计接口测试。"""
|
||||
|
||||
import pytest
|
||||
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.homeServiceImpl import HomeServiceImpl
|
||||
|
||||
|
||||
class _FakeDocument:
|
||||
def __init__(self, *, audit_status: int, failed_count: int = 0, updated_at: str = "2026-05-23T10:00:00") -> None:
|
||||
self.auditStatus = audit_status
|
||||
self.failedCount = failed_count
|
||||
self.updatedAt = updated_at
|
||||
|
||||
|
||||
class _FakePage:
|
||||
def __init__(self, *, documents, total_pages: int) -> None:
|
||||
self.documents = documents
|
||||
self.totalPages = total_pages
|
||||
|
||||
|
||||
class _FakeDocumentService:
|
||||
def __init__(self) -> None:
|
||||
self.calls = []
|
||||
|
||||
async def ListDocuments(self, **kwargs):
|
||||
self.calls.append(kwargs)
|
||||
page = kwargs["Page"]
|
||||
if page == 1:
|
||||
return _FakePage(
|
||||
documents=[
|
||||
_FakeDocument(audit_status=0, updated_at="2026-05-23T09:00:00"),
|
||||
_FakeDocument(audit_status=2, updated_at="2026-05-23T09:30:00"),
|
||||
_FakeDocument(audit_status=1, failed_count=0, updated_at="2026-05-03T10:00:00"),
|
||||
_FakeDocument(audit_status=1, failed_count=2, updated_at="2026-05-20T10:00:00"),
|
||||
],
|
||||
total_pages=2,
|
||||
)
|
||||
if page == 2:
|
||||
return _FakePage(
|
||||
documents=[
|
||||
_FakeDocument(audit_status=1, failed_count=1, updated_at="2026-05-22T10:00:00"),
|
||||
_FakeDocument(audit_status=1, failed_count=0, updated_at="2026-04-18T10:00:00"),
|
||||
_FakeDocument(audit_status=0, updated_at="2026-05-10T10:00:00"),
|
||||
],
|
||||
total_pages=2,
|
||||
)
|
||||
return _FakePage(documents=[], total_pages=2)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_home_dashboard_statistics_uses_entry_scope_and_all_pages():
|
||||
"""首页统计按入口模块与文档类型过滤,并拉取全量分页后计算。"""
|
||||
document_service = _FakeDocumentService()
|
||||
service = HomeServiceImpl(DocumentService=document_service)
|
||||
|
||||
result = await service.GetDashboardStatistics(
|
||||
UserId=7,
|
||||
Today="2026-05-23",
|
||||
TypeIds=[10, 11],
|
||||
EntryModuleId=3,
|
||||
)
|
||||
|
||||
assert result.todayPendingFiles == 2
|
||||
assert result.monthlyReviewedFiles == 3
|
||||
assert result.monthlyPassRate == 33
|
||||
assert result.issuesDetected == 3
|
||||
assert result.monthlyReviewGrowth.value == 200
|
||||
assert result.monthlyReviewGrowth.isUp is True
|
||||
assert {call["EntryModuleId"] for call in document_service.calls} == {3}
|
||||
assert {tuple(call["TypeIds"]) for call in document_service.calls} == {(10, 11)}
|
||||
assert [call["Page"] for call in document_service.calls] == [1, 2]
|
||||
assert all("DateFrom" not in call for call in document_service.calls)
|
||||
assert all("DateTo" not in call for call in document_service.calls)
|
||||
@@ -0,0 +1,66 @@
|
||||
"""企查查配置与客户端测试。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
|
||||
import pytest
|
||||
|
||||
from fastapi_admin import config
|
||||
|
||||
|
||||
def test_qichacha_settings_are_exported_from_app_toml():
|
||||
"""企查查配置应通过 fastapi_admin.config 导出。"""
|
||||
assert config.QICHACHA_BASE_URL == "https://api.qichacha.com"
|
||||
assert config.QICHACHA_ENTERPRISE_PATH == "/ECIV4/GetBasicDetailsByName"
|
||||
assert config.QICHACHA_DISHONESTY_PATH == "/ShixinCheck/GetList"
|
||||
assert config.QICHACHA_TIMEOUT == 30
|
||||
assert config.QICHACHA_CACHE_DAYS == 30
|
||||
|
||||
|
||||
def test_qichacha_client_builds_expected_signature_headers(monkeypatch):
|
||||
"""企查查客户端应按 AppKey + Timespan + SecretKey 生成大写 MD5 Token。"""
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.qichachaClient import QichachaClient
|
||||
|
||||
monkeypatch.setattr("time.time", lambda: 1779433125)
|
||||
client = QichachaClient(AppKey="app-key", SecretKey="secret-key")
|
||||
|
||||
headers = client.BuildHeaders()
|
||||
|
||||
expected = hashlib.md5("app-key1779433125secret-key".encode("utf-8")).hexdigest().upper()
|
||||
assert headers == {"Token": expected, "Timespan": "1779433125"}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_qichacha_client_parses_enterprise_and_dishonesty(monkeypatch):
|
||||
"""企查查客户端应分别解析工商与失信接口结果。"""
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.qichachaClient import QichachaClient
|
||||
|
||||
calls: list[tuple[str, dict[str, str]]] = []
|
||||
|
||||
async def fake_request(self, Url: str, Params: dict[str, str]) -> dict:
|
||||
calls.append((Url, Params))
|
||||
if "GetBasicDetailsByName" in Url:
|
||||
return {
|
||||
"Status": "200",
|
||||
"Message": "成功",
|
||||
"Result": {"Name": "广州测试有限公司", "CreditCode": "91440000TEST"},
|
||||
}
|
||||
return {
|
||||
"Status": "200",
|
||||
"Message": "成功",
|
||||
"Result": {"VerifyResult": 1, "Data": [{"Anno": "案号1"}]},
|
||||
}
|
||||
|
||||
monkeypatch.setattr(QichachaClient, "Request", fake_request)
|
||||
client = QichachaClient(AppKey="app-key", SecretKey="secret-key")
|
||||
|
||||
enterprise = await client.GetEnterpriseInfo("广州测试有限公司")
|
||||
dishonesty = await client.GetDishonestyInfo("广州测试有限公司")
|
||||
|
||||
assert enterprise["Name"] == "广州测试有限公司"
|
||||
assert enterprise["CreditCode"] == "91440000TEST"
|
||||
assert dishonesty["VerifyResult"] == 1
|
||||
assert dishonesty["Data"][0]["Anno"] == "案号1"
|
||||
assert calls[0][1] == {"key": "app-key", "keyword": "广州测试有限公司"}
|
||||
assert calls[1][1] == {"key": "app-key", "searchKey": "广州测试有限公司"}
|
||||
@@ -0,0 +1,194 @@
|
||||
"""企查查服务测试。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import UTC, datetime, timedelta
|
||||
from types import SimpleNamespace
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
class _FakeSession:
|
||||
async def commit(self):
|
||||
return None
|
||||
|
||||
async def refresh(self, obj):
|
||||
return obj
|
||||
|
||||
async def flush(self):
|
||||
return None
|
||||
|
||||
|
||||
class _FakeSessionContext:
|
||||
async def __aenter__(self):
|
||||
return _FakeSession()
|
||||
|
||||
async def __aexit__(self, exc_type, exc, tb):
|
||||
return None
|
||||
|
||||
|
||||
async def _unexpected_upsert(cls, session, **fields):
|
||||
pytest.fail("fresh cache should not upsert")
|
||||
|
||||
|
||||
class _FakeClient:
|
||||
def __init__(self):
|
||||
self.calls: list[tuple[str, str]] = []
|
||||
|
||||
async def QueryCompany(self, Keyword: str):
|
||||
self.calls.append(("company", Keyword))
|
||||
return (
|
||||
{"Name": "广州测试有限公司", "CreditCode": "91440000TEST"},
|
||||
{"VerifyResult": 1, "Data": [{"Anno": "案号1"}, {"Anno": "案号2"}]},
|
||||
"91440000TEST",
|
||||
"广州测试有限公司",
|
||||
)
|
||||
|
||||
async def GetEnterpriseInfo(self, Keyword: str):
|
||||
self.calls.append(("enterprise", Keyword))
|
||||
return {"Name": "广州测试有限公司", "CreditCode": "91440000TEST"}
|
||||
|
||||
async def GetDishonestyInfo(self, Keyword: str):
|
||||
self.calls.append(("dishonesty", Keyword))
|
||||
return {"VerifyResult": 0, "Data": []}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_company_uses_fresh_cache(monkeypatch):
|
||||
"""未过期缓存应直接返回,不调用企查查 API。"""
|
||||
from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl import QichachaServiceImpl
|
||||
|
||||
now = datetime.now(UTC)
|
||||
record = SimpleNamespace(
|
||||
Id=1,
|
||||
searchKey="广州测试有限公司",
|
||||
creditCode="91440000TEST",
|
||||
companyName="广州测试有限公司",
|
||||
enterprise={"Name": "广州测试有限公司", "CreditCode": "91440000TEST"},
|
||||
dishonesty={"VerifyResult": 0, "Data": []},
|
||||
created_at=now,
|
||||
updated_at=now,
|
||||
)
|
||||
fake_client = _FakeClient()
|
||||
|
||||
async def fake_find(cls, session, Keyword):
|
||||
return record
|
||||
|
||||
monkeypatch.setattr(
|
||||
"fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl.GetAsyncSession",
|
||||
lambda: _FakeSessionContext(),
|
||||
)
|
||||
monkeypatch.setattr(QichachaCompanyInfo, "FindByKeyword", classmethod(fake_find))
|
||||
monkeypatch.setattr(QichachaCompanyInfo, "Upsert", classmethod(_unexpected_upsert))
|
||||
|
||||
service = QichachaServiceImpl(Client=fake_client, CacheDays=30)
|
||||
result = await service.QueryCompany(Keyword="广州测试有限公司", ForceRefresh=False)
|
||||
|
||||
assert result.success is True
|
||||
assert result.data is not None
|
||||
assert result.data.companyName == "广州测试有限公司"
|
||||
assert result.data.hasDishonesty is False
|
||||
assert result.data.dishonestyCount == 0
|
||||
assert fake_client.calls == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_company_refreshes_when_forced(monkeypatch):
|
||||
"""强制刷新时应调用企查查 API 并写入缓存。"""
|
||||
from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl import QichachaServiceImpl
|
||||
|
||||
old_record = SimpleNamespace(
|
||||
Id=1,
|
||||
searchKey="旧名称",
|
||||
creditCode=None,
|
||||
companyName=None,
|
||||
enterprise=None,
|
||||
dishonesty=None,
|
||||
created_at=datetime.now(UTC) - timedelta(days=1),
|
||||
updated_at=datetime.now(UTC) - timedelta(days=1),
|
||||
)
|
||||
saved: dict[str, object] = {}
|
||||
fake_client = _FakeClient()
|
||||
|
||||
async def fake_find(cls, session, Keyword):
|
||||
return old_record
|
||||
|
||||
async def fake_upsert(cls, session, **fields):
|
||||
saved.update(fields)
|
||||
return SimpleNamespace(
|
||||
Id=1,
|
||||
searchKey=fields["SearchKey"],
|
||||
creditCode=fields["CreditCode"],
|
||||
companyName=fields["CompanyName"],
|
||||
enterprise=fields["Enterprise"],
|
||||
dishonesty=fields["Dishonesty"],
|
||||
created_at=datetime.now(UTC),
|
||||
updated_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl.GetAsyncSession",
|
||||
lambda: _FakeSessionContext(),
|
||||
)
|
||||
monkeypatch.setattr(QichachaCompanyInfo, "FindByKeyword", classmethod(fake_find))
|
||||
monkeypatch.setattr(QichachaCompanyInfo, "Upsert", classmethod(fake_upsert))
|
||||
|
||||
service = QichachaServiceImpl(Client=fake_client, CacheDays=30)
|
||||
result = await service.QueryCompany(Keyword="广州测试有限公司", ForceRefresh=True)
|
||||
|
||||
assert fake_client.calls == [("company", "广州测试有限公司")]
|
||||
assert saved["SearchKey"] == "广州测试有限公司"
|
||||
assert saved["CreditCode"] == "91440000TEST"
|
||||
assert result.data is not None
|
||||
assert result.data.hasDishonesty is True
|
||||
assert result.data.dishonestyCount == 2
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_query_company_refreshes_stale_cache(monkeypatch):
|
||||
"""超过缓存天数的数据应自动刷新。"""
|
||||
from fastapi_modules.fastapi_leaudit.models.qichachaCompanyInfo import QichachaCompanyInfo
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl import QichachaServiceImpl
|
||||
|
||||
stale_record = SimpleNamespace(
|
||||
Id=1,
|
||||
searchKey="广州测试有限公司",
|
||||
creditCode="OLD",
|
||||
companyName="旧公司",
|
||||
enterprise={"Name": "旧公司"},
|
||||
dishonesty={"VerifyResult": 0, "Data": []},
|
||||
created_at=datetime.now(UTC) - timedelta(days=90),
|
||||
updated_at=datetime.now(UTC) - timedelta(days=90),
|
||||
)
|
||||
fake_client = _FakeClient()
|
||||
|
||||
async def fake_find(cls, session, Keyword):
|
||||
return stale_record
|
||||
|
||||
async def fake_upsert(cls, session, **fields):
|
||||
return SimpleNamespace(
|
||||
Id=1,
|
||||
searchKey=fields["SearchKey"],
|
||||
creditCode=fields["CreditCode"],
|
||||
companyName=fields["CompanyName"],
|
||||
enterprise=fields["Enterprise"],
|
||||
dishonesty=fields["Dishonesty"],
|
||||
created_at=datetime.now(UTC),
|
||||
updated_at=datetime.now(UTC),
|
||||
)
|
||||
|
||||
monkeypatch.setattr(
|
||||
"fastapi_modules.fastapi_leaudit.services.impl.qichachaServiceImpl.GetAsyncSession",
|
||||
lambda: _FakeSessionContext(),
|
||||
)
|
||||
monkeypatch.setattr(QichachaCompanyInfo, "FindByKeyword", classmethod(fake_find))
|
||||
monkeypatch.setattr(QichachaCompanyInfo, "Upsert", classmethod(fake_upsert))
|
||||
|
||||
service = QichachaServiceImpl(Client=fake_client, CacheDays=30)
|
||||
result = await service.QueryCompany(Keyword="广州测试有限公司")
|
||||
|
||||
assert fake_client.calls == [("company", "广州测试有限公司")]
|
||||
assert result.data is not None
|
||||
assert result.data.companyName == "广州测试有限公司"
|
||||
@@ -0,0 +1,96 @@
|
||||
from fastapi_modules.fastapi_leaudit.domian.vo.documentVo import DocumentDetailVO
|
||||
from fastapi_modules.fastapi_leaudit.domian.vo.pageQualityVo import PageQualitySummaryVO
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.documentServiceImpl import DocumentServiceImpl
|
||||
|
||||
|
||||
def test_review_document_payload_includes_page_quality_summary():
|
||||
detail = DocumentDetailVO(
|
||||
documentId=71,
|
||||
internalDocumentNo=10071,
|
||||
versionGroupKey="vg-71",
|
||||
versionNo=1,
|
||||
rootVersionId=71,
|
||||
previousVersionId=None,
|
||||
typeId=10,
|
||||
typeCode="case",
|
||||
typeName="行政许可",
|
||||
groupId=None,
|
||||
groupName=None,
|
||||
region="梅州",
|
||||
tenantCode="MEIZHOU",
|
||||
tenantName="梅州",
|
||||
normalizedName="图片模糊测试",
|
||||
fileId=7001,
|
||||
fileName="(图片模糊)第71号.pdf",
|
||||
fileExt="pdf",
|
||||
mimeType="application/pdf",
|
||||
fileSize=1024,
|
||||
ossUrl="/bucket/documents/71.pdf",
|
||||
processingStatus="completed",
|
||||
currentRunId=9001,
|
||||
runStatus="completed",
|
||||
resultStatus="warning",
|
||||
latestErrorCode=None,
|
||||
latestErrorMessage=None,
|
||||
totalScore=88,
|
||||
passedCount=2,
|
||||
failedCount=1,
|
||||
skippedCount=0,
|
||||
documentNumber="71",
|
||||
auditStatus=0,
|
||||
isTestDocument=False,
|
||||
pageQualityRunId=501,
|
||||
pageQualityRunStatus="completed",
|
||||
pageQualitySummaryStatus="review",
|
||||
pageQualityIssueCount=2,
|
||||
pageQualityWarningText="发现疑似模糊页",
|
||||
updatedAt="2026-05-23T10:00:00",
|
||||
hasHistory=False,
|
||||
totalVersions=1,
|
||||
historyVersions=[],
|
||||
remark=None,
|
||||
pageCount=10,
|
||||
pageQualitySummary=PageQualitySummaryVO(
|
||||
runId=501,
|
||||
runStatus="completed",
|
||||
summaryStatus="review",
|
||||
totalPages=10,
|
||||
reviewPageCount=2,
|
||||
rejectPageCount=0,
|
||||
warningText="发现疑似模糊页",
|
||||
pages=[3, 7],
|
||||
finishedAt="2026-05-23T10:01:00",
|
||||
),
|
||||
attachments=[],
|
||||
)
|
||||
|
||||
payload = DocumentServiceImpl._buildReviewPageQualityPayload(
|
||||
detail,
|
||||
[
|
||||
{"pageNum": 7, "qualityStatus": "review", "qualityScore": 0.64, "reasonText": "图片略模糊"},
|
||||
{"pageNum": 3, "qualityStatus": "reject", "qualityScore": 0.25, "reasonText": "图片严重模糊"},
|
||||
],
|
||||
)
|
||||
|
||||
assert payload == {
|
||||
"pageQualityRunId": 501,
|
||||
"pageQualityRunStatus": "completed",
|
||||
"pageQualitySummaryStatus": "review",
|
||||
"pageQualityIssueCount": 2,
|
||||
"pageQualityWarningText": "发现疑似模糊页",
|
||||
"pageQualitySummary": {
|
||||
"runId": 501,
|
||||
"runStatus": "completed",
|
||||
"summaryStatus": "review",
|
||||
"totalPages": 10,
|
||||
"reviewPageCount": 2,
|
||||
"rejectPageCount": 0,
|
||||
"warningText": "发现疑似模糊页",
|
||||
"pages": [3, 7],
|
||||
"finishedAt": "2026-05-23T10:01:00",
|
||||
},
|
||||
"pageQualityResults": [
|
||||
{"pageNum": 3, "qualityStatus": "reject", "qualityScore": 0.25, "reasonText": "图片严重模糊"},
|
||||
{"pageNum": 7, "qualityStatus": "review", "qualityScore": 0.64, "reasonText": "图片略模糊"},
|
||||
],
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
from fastapi_common.fastapi_common_web.domain.responses import StatusCodeEnum
|
||||
from fastapi_common.fastapi_common_web.exception.LeauditException import LeauditException
|
||||
import pytest
|
||||
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.rbacAdminServiceImpl import RbacAdminServiceImpl
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.rbacServiceImpl import RbacServiceImpl
|
||||
@@ -225,3 +226,25 @@ def test_permission_cache_is_shared_and_can_invalidate_user():
|
||||
PermissionServiceImpl.InvalidateUser(12345)
|
||||
assert 12345 not in first._permission_cache
|
||||
assert 12345 not in second._permission_cache
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_rbac_admin_permission_assertion_uses_permission_service(monkeypatch):
|
||||
service = RbacAdminServiceImpl()
|
||||
checked_permissions: list[tuple[int, str]] = []
|
||||
|
||||
async def fake_context(user_id: int):
|
||||
return {"can_manage": True, "is_super_admin": False}
|
||||
|
||||
async def fake_check_permission(self, user_id: int, permission_key: str):
|
||||
checked_permissions.append((user_id, permission_key))
|
||||
return permission_key != "rbac:roles:update"
|
||||
|
||||
monkeypatch.setattr(service, "_getCurrentUserContext", fake_context)
|
||||
monkeypatch.setattr(PermissionServiceImpl, "CheckPermission", fake_check_permission)
|
||||
|
||||
with pytest.raises(LeauditException) as exc_info:
|
||||
await service._assertPermissions(99, ["rbac:roles:update"])
|
||||
|
||||
assert exc_info.value.status == StatusCodeEnum.HTTP_403_FORBIDDEN
|
||||
assert checked_permissions == [(99, "rbac:roles:update")]
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
"""系统使用统计角色权限语义测试。"""
|
||||
|
||||
from fastapi_modules.fastapi_leaudit.services.impl.usageStatsServiceImpl import UsageStatsServiceImpl
|
||||
|
||||
|
||||
def test_usage_stats_service_does_not_require_admin_role_after_controller_permission_check():
|
||||
"""统计服务不再用管理员角色二次拦截,权限由控制器权限点决定。"""
|
||||
service = UsageStatsServiceImpl()
|
||||
|
||||
service._assert_stats_access(
|
||||
{
|
||||
"is_global": False,
|
||||
"can_manage": False,
|
||||
"tenant_scope_value": "梅州",
|
||||
"tenant_code": "MZ",
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user