test_data_element_api.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. from __future__ import annotations
  2. from dataclasses import replace
  3. import pytest
  4. from app.core.data_research.data_elements import DataElementRecord
  5. class FakeElementService:
  6. def __init__(self):
  7. self.record = DataElementRecord(
  8. uid="element-1",
  9. code="ORDER_ID",
  10. status="draft",
  11. current_version=1,
  12. snapshot={"definition": "订单唯一标识"},
  13. created_by="editor-1",
  14. updated_by="editor-1",
  15. )
  16. self.actions = []
  17. def create_draft(self, payload, actor_uid):
  18. self.actions.append(("create", payload, actor_uid))
  19. return self.record
  20. def list(self, *, status=None, business_domain_uid=None):
  21. self.actions.append(("list", status, business_domain_uid))
  22. return [replace(self.record, status="published")]
  23. def transition(self, uid, target_status, *, expected_version, actor_uid):
  24. self.actions.append(("transition", uid, target_status, expected_version, actor_uid))
  25. return replace(
  26. self.record,
  27. uid=uid,
  28. status=target_status,
  29. current_version=expected_version + 1,
  30. updated_by=actor_uid,
  31. )
  32. class FakeDecisionService:
  33. def __init__(self):
  34. self.calls = []
  35. def decide(self, items, *, actor_uid):
  36. self.calls.append((items, actor_uid))
  37. return [
  38. type(
  39. "Decision",
  40. (),
  41. {
  42. "uid": "decision-1",
  43. "candidate_uid": "candidate-1",
  44. "action": "ignore",
  45. "data_element_uid": None,
  46. "evidence_uids": ("evidence-1",),
  47. "actor_uid": actor_uid,
  48. "reason": "重复字段",
  49. },
  50. )()
  51. ]
  52. @pytest.fixture()
  53. def client(monkeypatch):
  54. from flask import request
  55. from app import create_app
  56. from app.api.data_development import routes
  57. from app.core.system import permissions
  58. elements = FakeElementService()
  59. decisions = FakeDecisionService()
  60. def identity():
  61. token = request.headers.get("Authorization", "")
  62. if token == "Bearer editor":
  63. return {"id": "editor-1", "roles": ["editor"]}
  64. if token == "Bearer admin":
  65. return {"id": "admin-1", "roles": ["admin"]}
  66. return None
  67. monkeypatch.setattr(permissions, "authenticate_request", identity)
  68. monkeypatch.setattr(routes, "get_data_element_service", lambda: elements)
  69. monkeypatch.setattr(routes, "get_candidate_decision_service", lambda: decisions)
  70. app = create_app()
  71. app.config.update(TESTING=True)
  72. return app.test_client(), elements, decisions
  73. def test_editor_creates_draft_and_submits_review(client):
  74. http, elements, _decisions = client
  75. headers = {"Authorization": "Bearer editor"}
  76. created = http.post(
  77. "/api/development/v1/data-elements",
  78. headers=headers,
  79. json={"code": "ORDER_ID", "definition": "订单唯一标识"},
  80. )
  81. reviewed = http.post(
  82. "/api/development/v1/data-elements/element-1/transitions",
  83. headers=headers,
  84. json={"target_status": "in_review", "expected_version": 1},
  85. )
  86. assert created.status_code == 201
  87. assert created.get_json()["data"]["status"] == "draft"
  88. assert reviewed.status_code == 200
  89. assert reviewed.get_json()["data"]["status"] == "in_review"
  90. assert elements.actions[-1] == (
  91. "transition",
  92. "element-1",
  93. "in_review",
  94. 1,
  95. "editor-1",
  96. )
  97. def test_viewer_lists_published_elements_for_business_domain(client):
  98. http, elements, _decisions = client
  99. listed = http.get(
  100. "/api/development/v1/data-elements"
  101. "?status=published&business_domain_uid=domain-1",
  102. headers={"Authorization": "Bearer editor"},
  103. )
  104. assert listed.status_code == 200
  105. assert listed.get_json()["data"]["records"][0]["uid"] == "element-1"
  106. assert elements.actions[-1] == ("list", "published", "domain-1")
  107. def test_only_publisher_can_publish(client):
  108. http, _elements, _decisions = client
  109. payload = {"target_status": "published", "expected_version": 2}
  110. denied = http.post(
  111. "/api/development/v1/data-elements/element-1/transitions",
  112. headers={"Authorization": "Bearer editor"},
  113. json=payload,
  114. )
  115. allowed = http.post(
  116. "/api/development/v1/data-elements/element-1/transitions",
  117. headers={"Authorization": "Bearer admin"},
  118. json=payload,
  119. )
  120. assert denied.status_code == 403
  121. assert allowed.status_code == 200
  122. assert allowed.get_json()["data"]["current_version"] == 3
  123. def test_candidate_decisions_are_editor_gated(client):
  124. http, _elements, decisions = client
  125. response = http.post(
  126. "/api/development/v1/candidate-decisions",
  127. headers={"Authorization": "Bearer editor"},
  128. json={"decisions": [{"candidate_uid": "candidate-1", "action": "ignore"}]},
  129. )
  130. assert response.status_code == 200
  131. assert response.get_json()["data"][0]["evidence_uids"] == ["evidence-1"]
  132. assert decisions.calls[0][1] == "editor-1"