test_sync.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. from __future__ import annotations
  2. from dataclasses import dataclass, field
  3. @dataclass
  4. class FakeRepository:
  5. active: object | None = None
  6. embeddings: dict[str, tuple[float, ...]] = field(default_factory=dict)
  7. publications: list[object] = field(default_factory=list)
  8. def load_active_snapshot(self, source_type, source_uid):
  9. return self.active
  10. def load_embedding(self, embedding_hash):
  11. return self.embeddings.get(embedding_hash)
  12. def activate(self, publication):
  13. self.active = publication.snapshot
  14. self.embeddings.update(publication.embeddings)
  15. self.publications.append(publication)
  16. class RecordingEmbedder:
  17. profile_key = "qwen:1024:test"
  18. def __init__(self, fail=False):
  19. self.calls = []
  20. self.fail = fail
  21. def embed(self, texts):
  22. self.calls.extend(texts)
  23. if self.fail:
  24. raise RuntimeError("embedding unavailable")
  25. return [[float(index), 1.0] for index, _text in enumerate(texts)]
  26. def _snapshot(version: int, owner: str):
  27. from app.core.knowledge.point_builder import build_knowledge_snapshot
  28. return build_knowledge_snapshot(
  29. "DataFlow",
  30. {
  31. "uid": "01900000-0000-7000-8000-000000001001",
  32. "version": version,
  33. "name": "sync",
  34. "purpose": "sync customer data",
  35. "owner": owner,
  36. "business_domain_uid": "01900000-0000-7000-8000-000000001002",
  37. },
  38. )
  39. def test_sync_reuses_unchanged_embeddings_and_activates_once():
  40. from app.core.knowledge.sync import KnowledgeSyncService
  41. repository = FakeRepository()
  42. first_embedder = RecordingEmbedder()
  43. service = KnowledgeSyncService(repository=repository, embedder=first_embedder)
  44. first_result = service.sync(_snapshot(1, "team-a"))
  45. assert first_result.status == "canonical_active"
  46. assert len(first_embedder.calls) == 3
  47. second_embedder = RecordingEmbedder()
  48. service = KnowledgeSyncService(repository=repository, embedder=second_embedder)
  49. second_result = service.sync(_snapshot(2, "team-b"))
  50. assert second_result.status == "canonical_active"
  51. assert second_result.diff_counts == {
  52. "added": 0,
  53. "modified": 1,
  54. "deleted": 0,
  55. "unchanged": 2,
  56. }
  57. assert second_embedder.calls == ["team-b"]
  58. assert repository.active.source_revision == 2
  59. assert len(repository.publications) == 2
  60. def test_sync_failure_keeps_old_active_snapshot():
  61. import pytest
  62. from app.core.knowledge.sync import KnowledgeSyncService
  63. repository = FakeRepository()
  64. KnowledgeSyncService(repository=repository, embedder=RecordingEmbedder()).sync(
  65. _snapshot(1, "team-a")
  66. )
  67. with pytest.raises(RuntimeError, match="embedding unavailable"):
  68. KnowledgeSyncService(
  69. repository=repository, embedder=RecordingEmbedder(fail=True)
  70. ).sync(_snapshot(2, "team-b"))
  71. assert repository.active.source_revision == 1
  72. assert len(repository.publications) == 1
  73. def test_sync_same_revision_and_snapshot_is_idempotent():
  74. from app.core.knowledge.sync import KnowledgeSyncService
  75. repository = FakeRepository()
  76. embedder = RecordingEmbedder()
  77. service = KnowledgeSyncService(repository=repository, embedder=embedder)
  78. snapshot = _snapshot(1, "team-a")
  79. service.sync(snapshot)
  80. result = service.sync(snapshot)
  81. assert result.status == "canonical_active"
  82. assert result.embedded_chunk_count == 0
  83. assert result.reused_embedding_count == 3
  84. assert len(repository.publications) == 1