test_governance_audit_repository.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. from __future__ import annotations
  2. from datetime import UTC, datetime, timedelta
  3. class FakeRows:
  4. def __init__(self, rows=None):
  5. self.rows = list(rows or [])
  6. def mappings(self):
  7. return self
  8. def all(self):
  9. return self.rows
  10. def one(self):
  11. return self.rows[0]
  12. def one_or_none(self):
  13. return self.rows[0] if self.rows else None
  14. class RecordingSession:
  15. def __init__(self, responses=None):
  16. self.responses = list(responses or [])
  17. self.calls = []
  18. def execute(self, statement, params=None):
  19. self.calls.append((str(statement), dict(params or {})))
  20. rows = self.responses.pop(0) if self.responses else []
  21. return FakeRows(rows)
  22. def flush(self):
  23. return None
  24. def test_repository_queries_six_authoritative_sources_with_safe_projections():
  25. from app.core.system.governance_audit import AUDIT_CATEGORIES
  26. from app.core.system.governance_audit_repository import (
  27. SqlAlchemyGovernanceAuditRepository,
  28. )
  29. session = RecordingSession()
  30. repository = SqlAlchemyGovernanceAuditRepository(session)
  31. now = datetime(2026, 7, 30, 8, 0, tzinfo=UTC)
  32. assert repository.fetch_events(
  33. categories=AUDIT_CATEGORIES,
  34. period_start=now - timedelta(days=1),
  35. period_end=now,
  36. ) == []
  37. sql = "\n".join(statement for statement, _ in session.calls).lower()
  38. for table in (
  39. "auth_audit_events",
  40. "ingestion_jobs",
  41. "device_entity_match_reviews",
  42. "device_entity_merge_rollbacks",
  43. "ontology_publish_runs",
  44. "device_semantic_code_reviews",
  45. "device_quality_profile_versions",
  46. "device_quality_issue_timeline",
  47. "knowledge_query_audits",
  48. ):
  49. assert table in sql
  50. for forbidden in (
  51. "select ip_address",
  52. "select user_agent",
  53. "last_error",
  54. "source.config",
  55. "encrypted_payload",
  56. "snapshot",
  57. "timeline.note",
  58. "timeline.payload",
  59. "query text",
  60. ):
  61. assert forbidden not in sql
  62. assert all(
  63. params["period_start"] == now - timedelta(days=1)
  64. and params["period_end"] == now
  65. for _, params in session.calls
  66. )
  67. def test_repository_normalizes_rows_without_exposing_raw_payloads():
  68. from app.core.system.governance_audit_repository import (
  69. SqlAlchemyGovernanceAuditRepository,
  70. )
  71. now = datetime(2026, 7, 30, 8, 0, tzinfo=UTC)
  72. session = RecordingSession(
  73. [
  74. [
  75. {
  76. "event_uid": "authentication:7",
  77. "category": "authentication",
  78. "action": "login",
  79. "status": "success",
  80. "actor_uid": "admin",
  81. "resource_type": "user",
  82. "resource_uid": "user-1",
  83. "occurred_at": now,
  84. "safe_detail": {"username": "admin"},
  85. }
  86. ]
  87. ]
  88. )
  89. repository = SqlAlchemyGovernanceAuditRepository(session)
  90. events = repository.fetch_events(
  91. categories=["authentication"],
  92. period_start=now - timedelta(days=1),
  93. period_end=now + timedelta(days=1),
  94. )
  95. assert events == [
  96. {
  97. "event_uid": "authentication:7",
  98. "category": "authentication",
  99. "action": "login",
  100. "status": "success",
  101. "actor_uid": "admin",
  102. "resource_type": "user",
  103. "resource_uid": "user-1",
  104. "occurred_at": now,
  105. "safe_detail": {"username": "admin"},
  106. }
  107. ]
  108. def test_security_snapshot_checks_nested_secret_keys():
  109. from app.core.system.governance_audit_repository import (
  110. SqlAlchemyGovernanceAuditRepository,
  111. )
  112. session = RecordingSession(
  113. [
  114. [{"credential_count": 0, "encrypted_credential_count": 0}],
  115. [{"plaintext_source_config_count": 0}],
  116. ]
  117. )
  118. SqlAlchemyGovernanceAuditRepository(session).security_snapshot()
  119. source_sql = session.calls[1][0].lower()
  120. assert '$.** ? (@.type() == "object").keyvalue()' in source_sql
  121. assert "like_regex" in source_sql
  122. assert "password" in source_sql
  123. assert "authorization" in source_sql
  124. def test_repository_persists_and_reads_append_only_seals():
  125. from app.core.system.governance_audit_repository import (
  126. SqlAlchemyGovernanceAuditRepository,
  127. )
  128. now = datetime(2026, 7, 30, 8, 0, tzinfo=UTC)
  129. stored = {
  130. "uid": "seal-1",
  131. "period_start": now,
  132. "period_end": now + timedelta(hours=1),
  133. "categories": ["authentication"],
  134. "event_count": 1,
  135. "root_hash": "a" * 64,
  136. "signature": "b" * 64,
  137. "key_version": "v1",
  138. "sealed_by": "admin-1",
  139. "created_at": now,
  140. }
  141. session = RecordingSession([[stored], [stored], [stored]])
  142. repository = SqlAlchemyGovernanceAuditRepository(session)
  143. saved = repository.save_seal(
  144. {
  145. **stored,
  146. "period_start": stored["period_start"].isoformat(),
  147. "period_end": stored["period_end"].isoformat(),
  148. }
  149. )
  150. loaded = repository.get_seal("seal-1")
  151. listed = repository.list_seals(limit=10)
  152. assert saved["uid"] == loaded["uid"] == listed[0]["uid"]
  153. sql = "\n".join(statement for statement, _ in session.calls).upper()
  154. assert "INSERT INTO PUBLIC.GOVERNANCE_AUDIT_SEALS" in sql
  155. assert "UPDATE PUBLIC.GOVERNANCE_AUDIT_SEALS" not in sql
  156. assert "DELETE FROM PUBLIC.GOVERNANCE_AUDIT_SEALS" not in sql