test_data_rule_control_plane.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. from __future__ import annotations
  2. import copy
  3. import os
  4. import pytest
  5. from sqlalchemy import create_engine, text
  6. from sqlalchemy.orm import Session
  7. from app.core.common.identifiers import new_governance_uid
  8. from tests.core.data_rules.test_contracts import (
  9. valid_dataflow_spec,
  10. valid_rule_spec,
  11. valid_standard_spec,
  12. )
  13. pytestmark = pytest.mark.integration
  14. @pytest.fixture()
  15. def database_url():
  16. value = os.environ.get("TEST_DATABASE_URL")
  17. if not value:
  18. pytest.skip("TEST_DATABASE_URL is not configured")
  19. return value
  20. def test_postgres_rule_standard_and_production_line_release_is_atomic(
  21. database_url,
  22. ):
  23. from app.core.data_rules.release import ProductionLineReleaseService
  24. from app.core.data_rules.repository import DataRuleRepository
  25. engine = create_engine(database_url)
  26. with engine.connect() as connection:
  27. transaction = connection.begin()
  28. try:
  29. actor = connection.execute(
  30. text(
  31. "SELECT id::text FROM public.users "
  32. "WHERE status = 'active' ORDER BY created_at LIMIT 1"
  33. )
  34. ).scalar_one()
  35. session = Session(bind=connection)
  36. repository = DataRuleRepository(session)
  37. quality_spec = valid_rule_spec()
  38. quality_spec["steps"] = [copy.deepcopy(quality_spec["steps"][1])]
  39. quality_created = repository.create_rule_version(
  40. rule_spec=quality_spec,
  41. source_text="手机号必须为11位数字",
  42. category="standard_clause",
  43. created_by=actor,
  44. )
  45. quality = repository.publish_rule_version(
  46. version_id=quality_created["id"],
  47. published_by=actor,
  48. )
  49. transform_spec = valid_rule_spec()
  50. transform_created = repository.create_rule_version(
  51. rule_spec=transform_spec,
  52. source_text="清洗姓名并校验手机号",
  53. category="flow_scoped",
  54. created_by=actor,
  55. )
  56. transform = repository.publish_rule_version(
  57. version_id=transform_created["id"],
  58. published_by=actor,
  59. )
  60. standard_spec = valid_standard_spec(quality["id"])
  61. standard_created = repository.create_standard_version(
  62. standard_spec=standard_spec,
  63. source_text="客户手机号遵循统一格式",
  64. created_by=actor,
  65. )
  66. standard = repository.publish_standard_version(
  67. version_id=standard_created["id"],
  68. published_by=actor,
  69. )
  70. flow = valid_dataflow_spec(standard["id"], transform["id"])
  71. released = ProductionLineReleaseService(repository).release(
  72. dataflow_uid=flow["dataflow_uid"],
  73. dataflow_spec=flow,
  74. source_text="清洗客户数据并执行客户数据标准",
  75. input_schema_hashes={"bd:customer_raw:v2": "a" * 64},
  76. output_schema_hash="b" * 64,
  77. created_by=actor,
  78. )
  79. assert released["status"] == "released"
  80. assert released["package"]["standard_version_ids"] == [
  81. standard["id"]
  82. ]
  83. assert released["package"]["rule_version_ids"] == sorted(
  84. [quality["id"], transform["id"]]
  85. )
  86. counts = connection.execute(
  87. text(
  88. "SELECT "
  89. "(SELECT COUNT(*) FROM public.dataflow_component_bindings "
  90. " WHERE dataflow_version_id = CAST(:id AS uuid)) AS bindings, "
  91. "(SELECT COUNT(*) FROM public.rule_execution_plans p "
  92. " JOIN public.dataflow_component_bindings b "
  93. " ON b.id = p.component_binding_id "
  94. " WHERE b.dataflow_version_id = CAST(:id AS uuid) "
  95. " AND p.status = 'published') AS plans"
  96. ),
  97. {"id": released["id"]},
  98. ).one()
  99. assert counts.bindings == 2
  100. assert counts.plans == 2
  101. finally:
  102. transaction.rollback()
  103. engine.dispose()