test_product_governance_api.py 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. from __future__ import annotations
  2. USER_UID = "01900000-0000-7000-8000-000000008801"
  3. PRODUCT_UID = "01900000-0000-7000-8000-000000008802"
  4. APPLICATION_UID = "01900000-0000-7000-8000-000000008803"
  5. CONTRACT_UID = "01900000-0000-7000-8000-000000008804"
  6. class FakeProductGovernanceService:
  7. def __init__(self):
  8. self.calls = []
  9. def list_products(self, **filters):
  10. self.calls.append(("list_products", filters))
  11. return [{"uid": PRODUCT_UID, "name": "设备健康产品", "status": "draft"}]
  12. def register_product(self, payload, actor_uid):
  13. self.calls.append(("register_product", payload, actor_uid))
  14. return {"uid": PRODUCT_UID, "current_version": 1, **payload}
  15. def transition_product(self, uid, payload, expected_version, actor_uid):
  16. self.calls.append(("transition_product", uid, payload, expected_version, actor_uid))
  17. return {"uid": uid, "status": "in_review", "current_version": 2}
  18. def create_application(self, payload, actor_uid):
  19. self.calls.append(("create_application", payload, actor_uid))
  20. return {"uid": APPLICATION_UID, "current_version": 1, **payload}
  21. def submit_application(self, uid, payload, expected_version, actor_uid):
  22. self.calls.append(("submit_application", uid, payload, expected_version, actor_uid))
  23. return {"uid": uid, "status": "pending_approval", "current_version": 2}
  24. def create_contract(self, product_uid, payload, actor_uid):
  25. self.calls.append(("create_contract", product_uid, payload, actor_uid))
  26. return {"uid": CONTRACT_UID, "product_uid": product_uid, "current_version": 1}
  27. def generate_certificate(self, product_uid, payload, actor_uid):
  28. self.calls.append(("generate_certificate", product_uid, payload, actor_uid))
  29. return {"uid": "certificate-1", "product_uid": product_uid, "status": "qualified"}
  30. def dashboard(self):
  31. return {"product_count": 1, "qualified_count": 1}
  32. def _headers(role, **extra):
  33. return {"Authorization": f"Bearer {role}", **extra}
  34. def _client(monkeypatch):
  35. from app import create_app
  36. from app.api.data_service import product_governance_routes
  37. service = FakeProductGovernanceService()
  38. monkeypatch.setattr(product_governance_routes, "_service", lambda: service)
  39. monkeypatch.setattr(
  40. "app.core.system.auth.load_identity_from_token",
  41. lambda token, secret: (
  42. {"id": USER_UID, "username": token, "roles": [token]}
  43. if token in {"viewer", "editor", "admin"}
  44. else None
  45. ),
  46. )
  47. app = create_app()
  48. app.config.update(TESTING=True)
  49. return app.test_client(), service
  50. def test_viewer_can_read_governed_products_and_dashboard(monkeypatch):
  51. client, service = _client(monkeypatch)
  52. products = client.get(
  53. "/api/dataservice/governance/products?status=draft",
  54. headers=_headers("viewer"),
  55. )
  56. assert products.status_code == 200
  57. assert products.get_json()["data"][0]["uid"] == PRODUCT_UID
  58. assert service.calls[-1] == ("list_products", {"status": "draft"})
  59. dashboard = client.get(
  60. "/api/dataservice/governance/dashboard", headers=_headers("viewer")
  61. )
  62. assert dashboard.status_code == 200
  63. assert dashboard.get_json()["data"]["qualified_count"] == 1
  64. def test_editor_creates_and_submits_application_but_cannot_register_product(monkeypatch):
  65. client, service = _client(monkeypatch)
  66. payload = {
  67. "title": "设备健康数据申请",
  68. "source_type": "api",
  69. "source_ref": {"endpoint": "/health"},
  70. }
  71. created = client.post(
  72. "/api/dataservice/governance/applications",
  73. json=payload,
  74. headers=_headers("editor"),
  75. )
  76. assert created.status_code == 201
  77. assert created.headers["ETag"] == '"1"'
  78. missing_version = client.post(
  79. f"/api/dataservice/governance/applications/{APPLICATION_UID}/submit",
  80. json={"workflow_uid": "workflow-1"},
  81. headers=_headers("editor"),
  82. )
  83. assert missing_version.status_code == 428
  84. submitted = client.post(
  85. f"/api/dataservice/governance/applications/{APPLICATION_UID}/submit",
  86. json={"workflow_uid": "workflow-1"},
  87. headers=_headers("editor", **{"If-Match": '"1"'}),
  88. )
  89. assert submitted.status_code == 200
  90. assert submitted.headers["ETag"] == '"2"'
  91. assert service.calls[-1][3] == 1
  92. forbidden = client.post(
  93. "/api/dataservice/governance/products",
  94. json={"name": "不得登记"},
  95. headers=_headers("editor"),
  96. )
  97. assert forbidden.status_code == 403
  98. owner_transition = client.post(
  99. f"/api/dataservice/governance/products/{PRODUCT_UID}/transition",
  100. json={"action": "submit_review", "reason": "责任人提交"},
  101. headers=_headers("editor", **{"If-Match": '"1"'}),
  102. )
  103. assert owner_transition.status_code == 200
  104. def test_admin_manages_lifecycle_contract_and_certificate(monkeypatch):
  105. client, service = _client(monkeypatch)
  106. product = client.post(
  107. "/api/dataservice/governance/products",
  108. json={"name": "设备健康产品"},
  109. headers=_headers("admin"),
  110. )
  111. assert product.status_code == 201
  112. assert product.headers["ETag"] == '"1"'
  113. transitioned = client.post(
  114. f"/api/dataservice/governance/products/{PRODUCT_UID}/transition",
  115. json={"action": "submit_review", "reason": "治理评审"},
  116. headers=_headers("admin", **{"If-Match": '"1"'}),
  117. )
  118. assert transitioned.status_code == 200
  119. assert transitioned.headers["ETag"] == '"2"'
  120. contract = client.post(
  121. f"/api/dataservice/governance/products/{PRODUCT_UID}/contracts",
  122. json={"definition": {"schema": {"fields": []}}},
  123. headers=_headers("admin"),
  124. )
  125. assert contract.status_code == 201
  126. certificate = client.post(
  127. f"/api/dataservice/governance/products/{PRODUCT_UID}/certificates",
  128. json={"approval_task_uid": "task-1", "evidence_refs": {}},
  129. headers=_headers("admin"),
  130. )
  131. assert certificate.status_code == 201
  132. assert certificate.get_json()["data"]["status"] == "qualified"