test_governance_metrics_api.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. from __future__ import annotations
  2. class FakeGovernanceMetricsService:
  3. def __init__(self):
  4. self.calls = []
  5. def summary(self, access):
  6. self.calls.append(("summary", access))
  7. return {
  8. "scope": {
  9. "global_access": access.global_access,
  10. "business_domain_uids": list(access.business_domain_uids),
  11. },
  12. "metrics": [
  13. {
  14. "code": "asset_completeness",
  15. "name": "台账完整率",
  16. "definition": "固定口径",
  17. "numerator": 2,
  18. "denominator": 3,
  19. "rate": 0.666667,
  20. "status": "available",
  21. "states": ["complete", "incomplete"],
  22. }
  23. ],
  24. }
  25. def details(self, access, **filters):
  26. self.calls.append(("details", access, filters))
  27. return {
  28. "metric": {
  29. "code": filters["metric"],
  30. "name": "台账完整率",
  31. "definition": "固定口径",
  32. },
  33. "state": filters["state"],
  34. "records": [{"asset_uid": "asset-1", "asset_name": "循环泵"}],
  35. "page": int(filters["page"]),
  36. "page_size": int(filters["page_size"]),
  37. "total": 1,
  38. }
  39. def _client(monkeypatch, *, role="viewer"):
  40. from app import create_app
  41. from app.api.data_development import routes
  42. from app.core.data_research.governance_metrics import GovernanceMetricAccess
  43. service = FakeGovernanceMetricsService()
  44. monkeypatch.setattr(
  45. "app.core.system.permissions.authenticate_request",
  46. lambda: {
  47. "id": "00000000-0000-7000-8000-000000000111",
  48. "roles": [role],
  49. },
  50. )
  51. monkeypatch.setattr(
  52. routes,
  53. "get_governance_metrics_service",
  54. lambda: service,
  55. raising=False,
  56. )
  57. monkeypatch.setattr(
  58. routes,
  59. "_governance_metric_access",
  60. lambda: GovernanceMetricAccess(
  61. global_access=role == "admin",
  62. business_domain_uids=(
  63. () if role == "admin" else ("domain-a",)
  64. ),
  65. ),
  66. raising=False,
  67. )
  68. app = create_app()
  69. app.config.update(TESTING=True)
  70. return app.test_client(), service
  71. def test_viewer_reads_summary_with_server_derived_scope(monkeypatch):
  72. http, service = _client(monkeypatch)
  73. response = http.get(
  74. "/api/development/v1/governance-metrics/summary"
  75. )
  76. assert response.status_code == 200
  77. data = response.get_json()["data"]
  78. assert data["scope"] == {
  79. "global_access": False,
  80. "business_domain_uids": ["domain-a"],
  81. }
  82. assert data["metrics"][0]["rate"] == 0.666667
  83. assert service.calls[0][0] == "summary"
  84. def test_viewer_traces_metric_to_bounded_details(monkeypatch):
  85. http, service = _client(monkeypatch)
  86. response = http.get(
  87. "/api/development/v1/governance-metrics/details"
  88. "?metric=asset_completeness&state=incomplete&page=2&page_size=25"
  89. )
  90. assert response.status_code == 200
  91. data = response.get_json()["data"]
  92. assert data["records"] == [
  93. {"asset_uid": "asset-1", "asset_name": "循环泵"}
  94. ]
  95. assert service.calls[-1][2] == {
  96. "metric": "asset_completeness",
  97. "state": "incomplete",
  98. "page": "2",
  99. "page_size": "25",
  100. }
  101. def test_invalid_metric_returns_safe_400_contract(monkeypatch):
  102. from app.core.data_research.errors import GovernanceMetricInvalid
  103. http, service = _client(monkeypatch)
  104. def invalid(*_args, **_kwargs):
  105. raise GovernanceMetricInvalid("unsupported metric")
  106. service.details = invalid
  107. response = http.get(
  108. "/api/development/v1/governance-metrics/details"
  109. "?metric=ranking&state=top"
  110. )
  111. assert response.status_code == 400
  112. assert response.get_json()["error"]["code"] == (
  113. "GOVERNANCE_METRIC_INVALID"
  114. )
  115. def test_unexpected_repository_failure_uses_existing_safe_error(monkeypatch):
  116. http, service = _client(monkeypatch)
  117. def unavailable(*_args, **_kwargs):
  118. raise RuntimeError("database secret")
  119. service.summary = unavailable
  120. response = http.get(
  121. "/api/development/v1/governance-metrics/summary"
  122. )
  123. assert response.status_code == 500
  124. payload = response.get_json()
  125. assert payload["message"] == "数据采集任务处理失败"
  126. assert "database secret" not in repr(payload)
  127. def test_admin_scope_is_global_and_anonymous_is_denied(monkeypatch):
  128. admin_http, service = _client(monkeypatch, role="admin")
  129. admin_response = admin_http.get(
  130. "/api/development/v1/governance-metrics/summary"
  131. )
  132. assert admin_response.status_code == 200
  133. assert service.calls[0][1].global_access is True
  134. monkeypatch.setattr(
  135. "app.core.system.permissions.authenticate_request",
  136. lambda: None,
  137. )
  138. from app import create_app
  139. anonymous_response = create_app().test_client().get(
  140. "/api/development/v1/governance-metrics/summary"
  141. )
  142. assert anonymous_response.status_code == 401