test_ontology_api.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. from __future__ import annotations
  2. from dataclasses import replace
  3. import pytest
  4. from app.core.data_research.ontology.models import DomainLink, GraphDocument, Ontology, OntologyVersion
  5. from app.core.data_research.ontology.validation import ValidationIssue
  6. class FakeOntologyService:
  7. def __init__(self):
  8. self.ontology = Ontology(
  9. uid="ontology-1",
  10. code="CUSTOMER",
  11. name="客户本体",
  12. owner_uid="owner-1",
  13. domain_links=(DomainLink("domain-1", "owner"), DomainLink("domain-2", "contributor")),
  14. )
  15. self.version = OntologyVersion(
  16. uid="version-1",
  17. ontology_uid="ontology-1",
  18. version=1,
  19. graph_document=GraphDocument.from_dict({"domain_links": [{"domain_uid": "domain-1", "role": "owner"}]}),
  20. content_hash="a" * 64,
  21. )
  22. self.calls = []
  23. def list(self):
  24. return [self.ontology]
  25. def create(self, payload, actor_uid):
  26. self.calls.append(("create", payload, actor_uid))
  27. return self.ontology
  28. def save_draft(self, uid, graph, expected_revision, actor_uid):
  29. self.calls.append(("draft", uid, expected_revision, actor_uid))
  30. return self.version
  31. def validate(self, uid):
  32. self.calls.append(("validate", uid))
  33. return [ValidationIssue("ONTOLOGY_OWNER_REQUIRED", "owner required", "domain_links")]
  34. def publish(self, uid, idempotency_key, actor_uid):
  35. self.calls.append(("publish", uid, idempotency_key, actor_uid))
  36. return replace(self.version, status="published")
  37. def diff(self, uid, left, right):
  38. return {"classes": {"added": [], "removed": [], "changed": []}}
  39. def rollback(self, uid, target_version_uid, expected_revision, actor_uid):
  40. self.calls.append(("rollback", uid, target_version_uid, expected_revision, actor_uid))
  41. return replace(self.version, uid="version-2", version=2, parent_version_uid="version-1")
  42. @pytest.fixture()
  43. def client(monkeypatch):
  44. from flask import request
  45. from app import create_app
  46. from app.api.data_development import routes
  47. from app.core.system import permissions
  48. service = FakeOntologyService()
  49. def identity():
  50. token = request.headers.get("Authorization", "")
  51. role = token.removeprefix("Bearer ")
  52. return {"id": f"{role}-1", "roles": [role]} if role in {"viewer", "editor", "admin"} else None
  53. monkeypatch.setattr(permissions, "authenticate_request", identity)
  54. monkeypatch.setattr(routes, "get_ontology_service", lambda: service)
  55. app = create_app()
  56. app.config.update(TESTING=True)
  57. return app.test_client(), service
  58. def test_viewer_is_read_only_and_multi_domain_roles_are_returned(client):
  59. http, _service = client
  60. headers = {"Authorization": "Bearer viewer"}
  61. listed = http.get("/api/development/v1/ontologies", headers=headers)
  62. denied = http.post(
  63. "/api/development/v1/ontologies",
  64. headers=headers,
  65. json={"code": "CUSTOMER", "name": "客户本体"},
  66. )
  67. assert listed.status_code == 200
  68. assert listed.get_json()["data"][0]["domain_links"][1]["role"] == "contributor"
  69. assert denied.status_code == 403
  70. def test_editor_saves_etag_draft_but_cannot_publish(client):
  71. http, service = client
  72. headers = {"Authorization": "Bearer editor", "If-Match": '"0"'}
  73. saved = http.patch(
  74. "/api/development/v1/ontologies/ontology-1/graph",
  75. headers=headers,
  76. json={"classes": [], "domain_links": []},
  77. )
  78. denied = http.post(
  79. "/api/development/v1/ontologies/ontology-1/publish",
  80. headers={"Authorization": "Bearer editor", "Idempotency-Key": "publish-1"},
  81. )
  82. assert saved.status_code == 200
  83. assert service.calls[-1][2] == 0
  84. assert denied.status_code == 403
  85. def test_admin_validates_publishes_diffs_and_rolls_back(client):
  86. http, service = client
  87. headers = {"Authorization": "Bearer admin"}
  88. validation = http.post("/api/development/v1/ontologies/ontology-1/validate", headers=headers)
  89. published = http.post(
  90. "/api/development/v1/ontologies/ontology-1/publish",
  91. headers={**headers, "Idempotency-Key": "publish-1"},
  92. )
  93. diffed = http.get(
  94. "/api/development/v1/ontologies/ontology-1/diff?left=version-0&right=version-1",
  95. headers=headers,
  96. )
  97. rolled_back = http.post(
  98. "/api/development/v1/ontologies/ontology-1/rollback",
  99. headers=headers,
  100. json={"target_version_uid": "version-1", "expected_revision": 1},
  101. )
  102. assert validation.status_code == 422
  103. assert validation.get_json()["data"][0]["code"] == "ONTOLOGY_OWNER_REQUIRED"
  104. assert published.get_json()["data"]["status"] == "published"
  105. assert diffed.status_code == 200
  106. assert rolled_back.get_json()["data"]["parent_version_uid"] == "version-1"