test_data_element_lifecycle.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. from __future__ import annotations
  2. from dataclasses import replace
  3. import pytest
  4. class MemoryElementRepository:
  5. def __init__(self):
  6. self.records = {}
  7. def get(self, uid):
  8. return self.records.get(uid)
  9. def get_by_code(self, code):
  10. return next((item for item in self.records.values() if item.code == code), None)
  11. def save(self, record):
  12. self.records[record.uid] = record
  13. return record
  14. def valid_payload(**overrides):
  15. payload = {
  16. "code": "ORDER_ID",
  17. "name_zh": "订单编号",
  18. "name_en": "order_id",
  19. "definition": "订单的唯一业务标识",
  20. "logical_type": "string",
  21. "evidence_uids": ["evidence-1"],
  22. "business_domain_uids": ["domain-1"],
  23. }
  24. payload.update(overrides)
  25. return payload
  26. def test_data_element_requires_definition_and_starts_as_draft():
  27. from app.core.data_research.data_elements import DataElementService
  28. from app.core.data_research.errors import DataElementInvalid
  29. repository = MemoryElementRepository()
  30. service = DataElementService(repository, uid_factory=lambda: "element-1")
  31. with pytest.raises(DataElementInvalid, match="definition"):
  32. service.create_draft(valid_payload(definition=""), actor_uid="editor-1")
  33. element = service.create_draft(valid_payload(), actor_uid="editor-1")
  34. assert element.uid == "element-1"
  35. assert element.status == "draft"
  36. assert element.current_version == 1
  37. assert element.snapshot["evidence_uids"] == ["evidence-1"]
  38. def test_lifecycle_uses_optimistic_version_and_emits_publish_event():
  39. from app.core.data_research.data_elements import DataElementService
  40. from app.core.data_research.errors import DataElementVersionConflict
  41. repository = MemoryElementRepository()
  42. events = []
  43. service = DataElementService(
  44. repository,
  45. uid_factory=lambda: "element-1",
  46. outbox_enqueue=lambda **event: events.append(event),
  47. )
  48. element = service.create_draft(valid_payload(), actor_uid="editor-1")
  49. review = service.transition(
  50. element.uid,
  51. "in_review",
  52. expected_version=1,
  53. actor_uid="editor-1",
  54. )
  55. published = service.transition(
  56. element.uid,
  57. "published",
  58. expected_version=2,
  59. actor_uid="admin-1",
  60. )
  61. assert review.current_version == 2
  62. assert published.current_version == 3
  63. assert events == [
  64. {
  65. "aggregate_type": "data_element",
  66. "aggregate_id": "element-1",
  67. "event_type": "data_element.version_published",
  68. "payload": {"uid": "element-1", "version": 3},
  69. }
  70. ]
  71. with pytest.raises(DataElementVersionConflict):
  72. service.transition(
  73. element.uid,
  74. "deprecated",
  75. expected_version=2,
  76. actor_uid="admin-1",
  77. )
  78. def test_invalid_lifecycle_transition_is_rejected():
  79. from app.core.data_research.data_elements import DataElementService
  80. from app.core.data_research.errors import DataElementTransitionInvalid
  81. repository = MemoryElementRepository()
  82. service = DataElementService(repository, uid_factory=lambda: "element-1")
  83. element = service.create_draft(valid_payload(), actor_uid="editor-1")
  84. with pytest.raises(DataElementTransitionInvalid, match="draft -> published"):
  85. service.transition(
  86. element.uid,
  87. "published",
  88. expected_version=1,
  89. actor_uid="admin-1",
  90. )