test_governance.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. """End-to-end tests for the M3-M0 governance vertical slice."""
  2. from __future__ import annotations
  3. import os
  4. os.environ.setdefault("AGENTPAAS_DATABASE_URL", "sqlite:///:memory:")
  5. os.environ.setdefault("AGENTPAAS_TESTING", "1")
  6. import pytest
  7. from agentpaas.db.models import Database, now_utc
  8. from agentpaas.governance import service
  9. from agentpaas.governance.agents import execute_governance_agent
  10. @pytest.fixture
  11. def db():
  12. value = Database("sqlite:///:memory:")
  13. value.execute(
  14. "INSERT INTO tenants (id,name,plan,status,created_at) VALUES (?,?,?,?,?)",
  15. ("tenant-a", "Tenant A", "free", "active", now_utc()),
  16. )
  17. value.execute(
  18. "INSERT INTO tenants (id,name,plan,status,created_at) VALUES (?,?,?,?,?)",
  19. ("tenant-b", "Tenant B", "free", "active", now_utc()),
  20. )
  21. value.commit()
  22. return value
  23. def test_reference_slice_is_complete_valid_and_idempotent(db):
  24. first = service.seed_tenant_isolation(db, "tenant-a")
  25. second = service.seed_tenant_isolation(db, "tenant-a")
  26. assert first["project"]["id"] == second["project"]["id"]
  27. assert second["validation"]["valid"] is True
  28. assert second["validation"]["findings"] == []
  29. assert second["overview"]["layers"]["M3"] == 8
  30. assert second["overview"]["layers"]["M0"] == 3
  31. assert second["overview"]["totals"]["pending_reviews"] == 0
  32. def test_impact_crosses_business_software_and_governance_profiles(db):
  33. seeded = service.seed_tenant_isolation(db, "tenant-a")
  34. project_id = seeded["project"]["id"]
  35. elements = service.list_elements(db, "tenant-a", project_id, query="TenantIdColumn")["items"]
  36. column = next(item for item in elements if item["name"] == "TenantIdColumn")
  37. result = service.impact(db, "tenant-a", column["id"], max_depth=5)
  38. names = {item["name"] for item in result["nodes"]}
  39. assert {"Tenant", "IAMDataOwner", "TenantIsolationPolicy", "TenantIdCompleteness"} <= names
  40. assert any(edge["predicate"] == "REPRESENTS" for edge in result["edges"])
  41. def test_tenant_scope_blocks_cross_tenant_reads(db):
  42. seeded = service.seed_tenant_isolation(db, "tenant-a")
  43. project_id = seeded["project"]["id"]
  44. with pytest.raises(service.GovernanceError) as exc:
  45. service.overview(db, "tenant-b", project_id)
  46. assert exc.value.status == 404
  47. def test_ddl_agent_persists_facts_candidates_evidence_and_trace(db):
  48. project = service.create_project(db, "tenant-a", {
  49. "name": "DDL ingest", "namespace": "https://example.test/ddl",
  50. })
  51. ddl = """
  52. CREATE TABLE dcp_customer (
  53. id BIGINT PRIMARY KEY,
  54. mobile VARCHAR(32) NOT NULL,
  55. created_at TIMESTAMP
  56. );
  57. """
  58. response = execute_governance_agent(db, "tenant-a", project["id"], {
  59. "source_type": "ddl",
  60. "source_name": "customer.sql",
  61. "environment": "production",
  62. "content": ddl,
  63. })
  64. assert response["status"] == "completed"
  65. assert response["persisted"]["elements"] == 9
  66. assert response["persisted"]["assertions"] == 11
  67. assert response["persisted"]["issues"] == 2
  68. assert {entry["term_name"] for entry in response["trace"]} >= {
  69. "governance-source-normalizer", "asset-discovery-agent",
  70. "business-semantics-agent", "governance-controls-agent",
  71. "governance-synthesis-agent",
  72. }
  73. pending = service.list_assertions(
  74. db, "tenant-a", project["id"], review_status="pending"
  75. )
  76. assert len(pending) == 1
  77. assert pending[0]["predicate"] == "REPRESENTS"
  78. assert pending[0]["assertion_type"] == "inferred"
  79. assert pending[0]["evidence_ids"]
  80. reviewed = service.review_assertion(
  81. db, "tenant-a", pending[0]["id"], "accepted", "data-steward", "术语已确认"
  82. )
  83. assert reviewed["review_status"] == "accepted"
  84. assert reviewed["reviewer"] == "data-steward"
  85. runs = db.fetchall(
  86. "SELECT status,trace_json FROM governance_agent_runs WHERE tenant_id=?",
  87. ("tenant-a",),
  88. )
  89. assert len(runs) == 1 and runs[0]["status"] == "completed"
  90. def test_validation_detects_untyped_published_m0_fact(db):
  91. project = service.create_project(db, "tenant-a", {
  92. "name": "Validation", "namespace": "https://example.test/validation",
  93. })
  94. fact = service.upsert_element(db, "tenant-a", project["id"], {
  95. "layer": "M0", "profile": "software", "kind": "PhysicalTable",
  96. "name": "prod.orphan", "status": "published",
  97. })
  98. result = service.validate_project(db, "tenant-a", project["id"], persist=True)
  99. assert result["valid"] is False
  100. assert any(item["code"] == "M0_UNKNOWN_TYPE" and item["target_id"] == fact["id"]
  101. for item in result["findings"])