test_data_research_v60_v65.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. from __future__ import annotations
  2. from pathlib import Path
  3. ROOT = Path(__file__).resolve().parents[2]
  4. def valid_graph():
  5. return {
  6. "classes": [{"uid": "customer", "name": "Customer"}],
  7. "properties": [
  8. {"uid": "customer-id", "name": "customerId", "class_uid": "customer", "required": True, "cardinality": "1", "data_element_uid": "element-1"}
  9. ],
  10. "relations": [],
  11. "constraints": [],
  12. "domain_links": [
  13. {"domain_uid": "sales", "role": "owner"},
  14. {"domain_uid": "service", "role": "contributor"},
  15. ],
  16. "element_mappings": [{"property_uid": "customer-id", "data_element_uid": "element-1"}],
  17. }
  18. def test_v60_v62_acceptance_multi_source_policy_evidence_and_redaction():
  19. from app.core.data_research.artifacts import redact_excerpt
  20. from app.core.data_research.extractors.registry import default_registry
  21. from app.core.data_research.file_policy import FilePolicy
  22. registry = default_registry()
  23. assert registry.resolve("text/sql", "schema.sql").parser_version.startswith("sql")
  24. assert registry.resolve("text/csv", "elements.csv").parser_version.startswith("csv")
  25. assert registry.resolve("application/pdf", "standard.pdf").parser_version.startswith("pdf")
  26. assert registry.resolve(
  27. "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "standard.docx"
  28. ).parser_version.startswith("docx")
  29. assert set(FilePolicy.MEDIA_TYPES) == {".sql", ".csv", ".xls", ".xlsx", ".docx", ".pdf", ".png", ".jpg", ".jpeg"}
  30. redacted = redact_excerpt("phone=13800138000 token=top-secret")
  31. assert "138****8000" in redacted and "top-secret" not in redacted
  32. def test_v61_v64_acceptance_element_ontology_dynamic_publish_rollback_and_knowledge():
  33. from app.core.data_research.data_elements import DataElementService
  34. from app.core.data_research.ontology.change_sets import ChangeSetService
  35. from app.core.data_research.ontology.knowledge_sync import OntologyKnowledgeSync
  36. from app.core.data_research.ontology.publication import OntologyPublicationService
  37. from app.core.data_research.ontology.repository import MemoryOntologyRepository
  38. from app.core.data_research.ontology.suggestions import OntologySuggestionEngine
  39. class Elements:
  40. def __init__(self): self.records = {}
  41. def get(self, uid): return self.records.get(uid)
  42. def get_by_code(self, code): return next((x for x in self.records.values() if x.code == code), None)
  43. def save(self, record): self.records[record.uid] = record; return record
  44. element_events = []
  45. elements = DataElementService(Elements(), uid_factory=lambda: "element-1", outbox_enqueue=lambda **event: element_events.append(event))
  46. element = elements.create_draft(
  47. {"code": "CUSTOMER_ID", "name_zh": "客户编号", "name_en": "customer_id", "definition": "客户唯一编号", "logical_type": "string", "evidence_uids": ["evidence-1"], "business_domain_uids": ["sales", "service"]},
  48. "editor-1",
  49. )
  50. elements.transition(element.uid, "in_review", expected_version=1, actor_uid="editor-1")
  51. published_element = elements.transition(element.uid, "published", expected_version=2, actor_uid="admin-1")
  52. assert published_element.current_version == 3 and element_events[0]["event_type"] == "data_element.version_published"
  53. ids = iter(("ontology-1", "version-1", "version-2"))
  54. repository = MemoryOntologyRepository(uid_factory=ids.__next__)
  55. ontology = repository.create(code="CUSTOMER", name="客户本体", owner_uid="admin-1", domain_links=valid_graph()["domain_links"])
  56. assert {link.domain_uid for link in ontology.domain_links} == {"sales", "service"}
  57. version = repository.save_draft(ontology.uid, valid_graph(), expected_revision=0, actor_uid="editor-1")
  58. ontology_events = []
  59. publication = OntologyPublicationService(repository, outbox_enqueue=lambda **event: ontology_events.append(event))
  60. published = publication.publish(version.uid, idempotency_key="publish-1", actor_uid="admin-1")
  61. rollback = publication.create_rollback(ontology.uid, target_version_uid=published.uid, expected_revision=1, actor_uid="admin-1")
  62. assert published.status == "published" and rollback.version == 2
  63. suggestions = OntologySuggestionEngine().generate(
  64. domains=[{"uid": "sales", "name": "销售"}],
  65. data_elements=[{"uid": "element-1", "name": "客户编号", "status": "published"}],
  66. foreign_keys=[],
  67. )
  68. changes = ChangeSetService(uid_factory=lambda: "change-1")
  69. change_set = changes.create(ontology.uid, suggestions, actor_uid="editor-1")
  70. reviewed = changes.decide(change_set, [{"suggestion_uid": suggestions[0].uid, "decision": "accept", "reason": "已核验证据"}], actor_uid="reviewer-1")
  71. applied = changes.apply(valid_graph(), reviewed)
  72. assert len(applied["classes"]) == 2
  73. class KnowledgeRepo:
  74. def __init__(self): self.docs = {}; self.chunks = {}
  75. def upsert_document(self, doc):
  76. key = (doc["object_uid"], doc["object_version"]); changed = key not in self.docs; self.docs[key] = doc; return changed
  77. def replace_chunks(self, key, chunks): self.chunks[key] = chunks
  78. synced = OntologyKnowledgeSync(KnowledgeRepo()).sync({"uid": ontology.uid, "name": ontology.name, "version": published.version, "status": "published", "graph_document": valid_graph()})
  79. assert synced["document"]["object_version"] == 1
  80. def test_v65_acceptance_rbac_reconciliation_and_release_parity():
  81. from app.core.system.permissions import permission_for_request
  82. assert permission_for_request("/api/development/v1/ontologies/o/publish", "POST") == ("ontologies:publish",)
  83. assert permission_for_request("/api/development/v1/sources/files", "POST") == ("ingestion:run",)
  84. assert permission_for_request("/api/development/v1/evidence/e", "GET") == ("governance:read",)
  85. for relative in (
  86. "core/data_research/ontology/publication.py",
  87. "core/data_research/operations.py",
  88. "api/data_development/routes.py",
  89. ):
  90. assert (ROOT / "deployment/app" / relative).read_bytes() == (ROOT / "app" / relative).read_bytes()
  91. overview = (ROOT / "docs/architecture/ARCHITECTURE_OVERVIEW.md").read_text(encoding="utf-8")
  92. data_model = (ROOT / "docs/architecture/DATA_MODEL.md").read_text(encoding="utf-8")
  93. roadmap = (ROOT / "docs/architecture/NEXT_ITERATION_ROADMAP.md").read_text(encoding="utf-8")
  94. assert "V60-V65 数据研发" in overview
  95. assert "ontology_versions" in data_model
  96. assert "V65" in roadmap and "已完成" in roadmap