test_data_element_api.py 4.4 KB

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