페르소나 소스팩 동기화 정리
This commit is contained in:
parent
6a81ec596c
commit
e8e08935ed
10 changed files with 1126 additions and 283 deletions
|
|
@ -3,8 +3,24 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import unittest
|
||||
from typing import Any
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
from .services import live_coach
|
||||
from .services import live_coach, rag, source_pack_sync
|
||||
|
||||
|
||||
class _SourcePackConn:
|
||||
def __init__(self, latest: dict[tuple[str, str], dict[str, Any]] | None = None) -> None:
|
||||
self.latest = latest or {}
|
||||
self.execute_calls: list[tuple[str, tuple[Any, ...]]] = []
|
||||
self.fetchrow_calls: list[tuple[str, tuple[Any, ...]]] = []
|
||||
|
||||
async def execute(self, query: str, *args: Any) -> None:
|
||||
self.execute_calls.append((query, args))
|
||||
|
||||
async def fetchrow(self, query: str, *args: Any) -> dict[str, Any] | None:
|
||||
self.fetchrow_calls.append((query, args))
|
||||
return self.latest.get((str(args[0]), str(args[1])))
|
||||
|
||||
|
||||
class LiveCoachSourcePackTest(unittest.TestCase):
|
||||
|
|
@ -39,5 +55,99 @@ class LiveCoachSourcePackTest(unittest.TestCase):
|
|||
self.assertTrue(all(chunk["sensitivity"] == 2 for chunk in dsm["chunks"]))
|
||||
|
||||
|
||||
class RagIndexPolicyTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_index_document_rejects_raw_source_chunks_before_db(self) -> None:
|
||||
class NoDbConn:
|
||||
async def fetchrow(self, *_args: Any, **_kwargs: Any) -> None:
|
||||
raise AssertionError("raw source validation must run before DB access")
|
||||
|
||||
async def execute(self, *_args: Any, **_kwargs: Any) -> None:
|
||||
raise AssertionError("raw source validation must run before DB access")
|
||||
|
||||
req = rag.IndexRequest(
|
||||
source_id="raw-source",
|
||||
doc_uri="raw-source.txt",
|
||||
chunks=[
|
||||
{
|
||||
"seq": 0,
|
||||
"chunk_text": "원문 축어록",
|
||||
"visible_to": [],
|
||||
"sensitivity": 3,
|
||||
"meta": {"raw_source_artifact": True},
|
||||
}
|
||||
],
|
||||
)
|
||||
|
||||
with self.assertRaises(rag.IndexPolicyViolation) as caught:
|
||||
await rag.index_document(NoDbConn(), req) # type: ignore[arg-type]
|
||||
|
||||
self.assertIn("raw source artifacts must not be indexed", str(caught.exception))
|
||||
|
||||
|
||||
class RepoSourcePackSyncTest(unittest.IsolatedAsyncioTestCase):
|
||||
def _patch_manifest(self) -> Any:
|
||||
row = {
|
||||
"source_id": "source-a",
|
||||
"title": "Source A",
|
||||
"kb_kind": "theory",
|
||||
"license_class": "A",
|
||||
"origin_path": "data/source-a.json",
|
||||
"citation": "source-a",
|
||||
"external_llm_ok": True,
|
||||
}
|
||||
payload = {
|
||||
"source_id": "source-a",
|
||||
"doc_uri": "repo/source-a.json",
|
||||
"version": 1,
|
||||
"content_hash": "new-hash",
|
||||
"chunks": [{"seq": 0, "chunk_text": "body", "visible_to": ["evaluator"], "sensitivity": 2}],
|
||||
}
|
||||
return patch.object(source_pack_sync, "build_repo_source_pack_manifest", return_value=([row], [payload]))
|
||||
|
||||
async def test_dry_run_reports_next_version_without_writes(self) -> None:
|
||||
conn = _SourcePackConn({("source-a", "repo/source-a.json"): {"doc_id": 10, "version": 2, "content_hash": "old-hash"}})
|
||||
|
||||
with self._patch_manifest(), patch.object(source_pack_sync.rag, "index_document", AsyncMock()) as index_document:
|
||||
result = await source_pack_sync.sync_repo_source_packs(conn, apply=False)
|
||||
|
||||
self.assertFalse(result.applied)
|
||||
self.assertEqual(result.sources_upserted, 0)
|
||||
self.assertEqual(conn.execute_calls, [])
|
||||
index_document.assert_not_awaited()
|
||||
self.assertEqual(result.items[0].previous_version, 2)
|
||||
self.assertEqual(result.items[0].new_version, 3)
|
||||
self.assertFalse(result.items[0].skipped_unchanged)
|
||||
|
||||
async def test_apply_bumps_changed_document_version(self) -> None:
|
||||
conn = _SourcePackConn({("source-a", "repo/source-a.json"): {"doc_id": 10, "version": 2, "content_hash": "old-hash"}})
|
||||
seen_versions: list[int] = []
|
||||
|
||||
async def fake_index_document(_: Any, request: rag.IndexRequest) -> rag.IndexResult:
|
||||
seen_versions.append(request.version)
|
||||
return rag.IndexResult(doc_id=11, chunks_indexed=1, skipped_unchanged=False, embedded=False, degraded=True)
|
||||
|
||||
with self._patch_manifest(), patch.object(source_pack_sync.rag, "index_document", fake_index_document):
|
||||
result = await source_pack_sync.sync_repo_source_packs(conn, apply=True)
|
||||
|
||||
self.assertTrue(result.applied)
|
||||
self.assertEqual(result.sources_upserted, 1)
|
||||
self.assertEqual(len(conn.execute_calls), 1)
|
||||
self.assertEqual(seen_versions, [3])
|
||||
self.assertEqual(result.items[0].doc_id, 11)
|
||||
self.assertEqual(result.items[0].chunks_indexed, 1)
|
||||
self.assertTrue(result.degraded)
|
||||
|
||||
async def test_apply_skips_same_content_hash(self) -> None:
|
||||
conn = _SourcePackConn({("source-a", "repo/source-a.json"): {"doc_id": 10, "version": 2, "content_hash": "new-hash"}})
|
||||
|
||||
with self._patch_manifest(), patch.object(source_pack_sync.rag, "index_document", AsyncMock()) as index_document:
|
||||
result = await source_pack_sync.sync_repo_source_packs(conn, apply=True)
|
||||
|
||||
index_document.assert_not_awaited()
|
||||
self.assertEqual(result.skipped_unchanged, 1)
|
||||
self.assertEqual(result.items[0].doc_id, 10)
|
||||
self.assertEqual(result.items[0].new_version, 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue