페르소나 소스팩 동기화 정리

This commit is contained in:
Yun Chan 2026-06-28 20:12:35 +09:00
parent 6a81ec596c
commit e8e08935ed
10 changed files with 1126 additions and 283 deletions

View file

@ -2,7 +2,10 @@
from __future__ import annotations
import importlib.util
import json
import unittest
from pathlib import Path
from typing import Any
from unittest.mock import AsyncMock, patch
@ -245,6 +248,44 @@ def _draft_payload(
class PersonaApprovalBoundaryTest(unittest.IsolatedAsyncioTestCase):
def test_persona_seed_runner_reports_repo_manifest_without_db(self) -> None:
script_path = Path(__file__).resolve().parents[3] / "scripts" / "materialize-persona-seeds.py"
spec = importlib.util.spec_from_file_location("materialize_persona_seeds", script_path)
self.assertIsNotNone(spec)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
rows = module._manifest_rows()
self.assertEqual([row["code"] for row in rows], ["P1", "P2", "P3", "P4", "P5", "P6", "P7"])
self.assertEqual({row["version"] for row in rows}, {persona_repository.SEED_VERSION})
self.assertEqual(rows[0]["persona_id"], str(persona_repository.seed_persona_id("P1")))
self.assertIn("source_provenance", rows[0])
async def test_persona_seed_runner_apply_initializes_and_closes_pool(self) -> None:
script_path = Path(__file__).resolve().parents[3] / "scripts" / "materialize-persona-seeds.py"
spec = importlib.util.spec_from_file_location("materialize_persona_seeds_apply", script_path)
self.assertIsNotNone(spec)
assert spec is not None and spec.loader is not None
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
init_pool = AsyncMock()
materialize = AsyncMock(return_value=7)
close_pool = AsyncMock()
with (
patch.object(module, "init_pool", init_pool),
patch.object(module, "materialize_seed_personas", materialize),
patch.object(module, "close_pool", close_pool),
):
exit_code = await module._main_async(module.parse_args(["--apply"]))
self.assertEqual(exit_code, 0)
init_pool.assert_awaited_once()
materialize.assert_awaited_once()
close_pool.assert_awaited_once()
async def test_repository_loads_repo_persona_files_for_p4_to_p7(self) -> None:
cards = persona_repository.load_file_personas()
@ -753,12 +794,16 @@ class PersonaReviewQueueTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(response.draft.code, "P8")
self.assertEqual(response.draft.triggers["sore_spots"], ["버림받음"])
self.assertEqual(response.source_summary, "관계 상실 후 불안이 높아진 사례")
self.assertEqual(response.pii_entities_masked, ["PHONE"])
self.assertIn("PHONE", response.pii_entities_masked)
self.assertEqual(response.source_references[0].source_id, "persona_authoring_test")
self.assertEqual(response.evidence_chunks[0].chunk_id, 44)
self.assertIn("RAG sources=persona_authoring_test", response.draft.source_provenance)
self.assertIn("chunks=44", response.draft.source_provenance)
self.assertIn("prompt=persona-draft-rag@2026-06-28.1#", response.draft.source_provenance)
self.assertEqual(len(captured), 1)
self.assertEqual(captured[0].metadata["prompt_bundle"]["id"], "persona-draft-rag")
self.assertEqual(captured[0].metadata["prompt_bundle"]["version"], "2026-06-28.1")
self.assertRegex(captured[0].metadata["prompt_bundle"]["hash"], r"^[0-9a-f]{12}$")
sent_text = captured[0].messages[-1].content
self.assertNotIn("010-1234-5678", sent_text)
self.assertIn("[PHONE]", sent_text)
@ -799,7 +844,7 @@ class PersonaReviewQueueTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(response.kb_kind, "diagnostic")
self.assertEqual(response.license_class, "B")
self.assertTrue(response.external_llm_ok)
self.assertEqual(response.pii_entities_masked, ["PHONE"])
self.assertIn("PHONE", response.pii_entities_masked)
self.assertEqual(len(captured_index), 1)
index_req = captured_index[0]
self.assertTrue(index_req.source_id.startswith("persona_authoring_"))
@ -808,9 +853,24 @@ class PersonaReviewQueueTest(unittest.IsolatedAsyncioTestCase):
self.assertEqual(index_req.chunks[0]["visible_to"], ["evaluator"])
self.assertEqual(index_req.chunks[0]["sensitivity"], 2)
self.assertEqual(index_req.chunks[0]["meta"]["source_kind"], "client_record")
self.assertTrue(index_req.chunks[0]["meta"]["raw_source_not_indexed"])
self.assertEqual(index_req.chunks[0]["meta"]["raw_source_storage"], "hash_only")
self.assertTrue(index_req.chunks[0]["meta"]["raw_source_id"].endswith("_raw"))
self.assertRegex(index_req.chunks[0]["meta"]["raw_source_content_hash"], r"^[0-9a-f]{64}$")
self.assertNotIn("raw_source_not_stored", index_req.chunks[0]["meta"])
source_query, source_args = conn.execute_calls[0]
self.assertIn("INSERT INTO kb.source", source_query)
self.assertEqual(source_args[2], "diagnostic")
raw_query, raw_args = conn.execute_calls[1]
self.assertIn("INSERT INTO kb.raw_source_artifact", raw_query)
self.assertEqual(raw_args[0], index_req.chunks[0]["meta"]["raw_source_id"])
self.assertEqual(raw_args[1], index_req.source_id)
self.assertEqual(raw_args[2], "00000000-0000-0000-0000-000000000901")
self.assertEqual(raw_args[5], "B")
raw_summary = json.loads(raw_args[6])
self.assertEqual(raw_summary["derived_source_id"], index_req.source_id)
self.assertTrue(raw_summary["raw_text_not_indexed"])
self.assertEqual(raw_summary["storage"], "hash_only")
async def test_learner_cannot_create_persona_draft_route(self) -> None:
with patch.object(