test_query_audit.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. from __future__ import annotations
  2. import hashlib
  3. def _evidence():
  4. from app.core.knowledge.retrieval.contracts import KnowledgeEvidence
  5. return KnowledgeEvidence(
  6. chunk_id="device:asset-1:v2",
  7. content="设备名称:循环泵\n责任人:张工",
  8. score=0.91,
  9. retriever="device+lexical",
  10. object_uid="asset-1",
  11. object_type="DeviceAsset",
  12. object_version=2,
  13. business_domain_uid="domain-a",
  14. point_keys=("DeviceAsset/asset-1/summary",),
  15. point_revisions=(2,),
  16. generation=2,
  17. source_updated_at="2026-07-29T08:00:00+00:00",
  18. )
  19. def test_query_audit_normalizes_hash_and_persists_only_minimized_metadata():
  20. from app.core.knowledge.access import KnowledgeAccessContext
  21. from app.core.knowledge.query_audit import build_query_audit
  22. context = KnowledgeAccessContext(
  23. subject_id="11111111-1111-4111-8111-111111111111",
  24. roles=frozenset({"viewer"}),
  25. permissions=frozenset({"governance:read"}),
  26. business_domain_uids=frozenset({"domain-b", "domain-a"}),
  27. correlation_id="22222222-2222-4222-8222-222222222222",
  28. )
  29. record = build_query_audit(
  30. query=" 循环泵 的责任人 ",
  31. context=context,
  32. mode="semantic",
  33. evidence=(_evidence(),),
  34. cited_points=(("DeviceAsset/asset-1/summary", 2),),
  35. degraded_components=("vector",),
  36. latency_ms=17,
  37. )
  38. assert record.query_hash == hashlib.sha256(
  39. "循环泵 的责任人".encode()
  40. ).hexdigest()
  41. assert record.roles == ("viewer",)
  42. assert record.business_domain_uids == ("domain-a", "domain-b")
  43. assert record.retriever_counts == {"device": 1, "lexical": 1}
  44. assert record.cited_points == (
  45. {
  46. "point_key": "DeviceAsset/asset-1/summary",
  47. "point_revision": 2,
  48. },
  49. )
  50. assert record.degraded_components == ("vector",)
  51. assert record.latency_ms == 17
  52. serialized = repr(record)
  53. for forbidden in (
  54. "循环泵",
  55. "张工",
  56. "设备名称",
  57. "password",
  58. "source_config",
  59. ):
  60. assert forbidden not in serialized
  61. def test_query_audit_repository_records_and_lists_safe_fields_only():
  62. from app.core.knowledge.query_audit import (
  63. KnowledgeQueryAuditRecord,
  64. SqlKnowledgeQueryAuditRepository,
  65. )
  66. class Session:
  67. def __init__(self):
  68. self.statements = []
  69. self.committed = False
  70. self.rolled_back = False
  71. def execute(self, statement, parameters=None):
  72. self.statements.append((str(statement), parameters))
  73. def commit(self):
  74. self.committed = True
  75. def rollback(self):
  76. self.rolled_back = True
  77. session = Session()
  78. repository = SqlKnowledgeQueryAuditRepository(session)
  79. record = KnowledgeQueryAuditRecord(
  80. uid="33333333-3333-4333-8333-333333333333",
  81. query_hash="a" * 64,
  82. user_id="11111111-1111-4111-8111-111111111111",
  83. roles=("viewer",),
  84. business_domain_uids=("domain-a",),
  85. mode="semantic",
  86. retriever_counts={"device": 1},
  87. cited_points=(),
  88. degraded_components=("vector",),
  89. correlation_id="22222222-2222-4222-8222-222222222222",
  90. latency_ms=9,
  91. )
  92. repository.record(record)
  93. statement, parameters = session.statements[0]
  94. assert "knowledge_query_audits" in statement
  95. assert parameters["query_hash"] == "a" * 64
  96. assert "query" not in parameters
  97. assert "answer" not in parameters
  98. assert "content" not in parameters
  99. assert session.committed is True