import unittest from . import turn_runtime from .services import guardrail, live_coach from .services import state_machine class LiveCoachPromptPrivacyTest(unittest.TestCase): def tearDown(self) -> None: guardrail.set_ko_pii_recognizer(None) def test_messages_remask_recent_turns_and_evaluation_payload(self) -> None: class FakeKoRecognizer: def analyze(self, text: str): spans = [] for entity_type, value in ( ("NAME", "보라별"), ("ORG", "미래학교상담연구랩"), ): start = text.find(value) if start >= 0: spans.append(guardrail.PiiEntitySpan(entity_type, start, start + len(value))) return spans guardrail.set_ko_pii_recognizer(FakeKoRecognizer()) item = live_coach.LiveCoachInput( session_id="privacy-live-coach", turn_seq=3, stage="exploration", effective_openness=0.42, theory_mode="humanistic", persona_code="P1", persona_name="서연", learner_text="별명은 보라별이고 기관은 미래학교상담연구랩입니다.", client_reply="전화는 010-1234-5678입니다.", recent_turns=[ { "speaker": "counselor", "text": "보라별이 미래학교상담연구랩에서 힘들다고 했죠.", }, { "speaker": "client", "text": "010-1234-5678로 연락하지 말아주세요.", }, ], evaluation={ "appropriateness": "warn", "appropriateness_note": "보라별 연락처 010-1234-5678을 되물었다.", "nested": {"rationale": "기관 미래학교상담연구랩 언급"}, }, ) messages = live_coach._messages(item, []) user_prompt = messages[1].content for raw in ("보라별", "미래학교상담연구랩", "010-1234-5678"): self.assertNotIn(raw, user_prompt) for masked in ("[NAME]", "[ORG]", "[PHONE]"): self.assertIn(masked, user_prompt) class LiveCoachQuotaRechargeTest(unittest.TestCase): def test_recharge_requires_positive_score_and_client_change(self) -> None: before = state_machine.SessionState( stage=state_machine.Stage.RAPPORT, turn_seq=2, effective_openness=0.15, ) after = state_machine.SessionState( stage=state_machine.Stage.RAPPORT, turn_seq=3, effective_openness=0.19, ) should_recharge, reason = turn_runtime.should_recharge_live_coach_credit( {"appropriateness": "pos", "rapport_signal": 0.45}, before, after, ) self.assertTrue(should_recharge) self.assertIn("개방도", reason) should_recharge, _ = turn_runtime.should_recharge_live_coach_credit( {"appropriateness": "pos", "rapport_signal": 0.45}, before, state_machine.SessionState( stage=state_machine.Stage.RAPPORT, turn_seq=3, effective_openness=0.16, ), ) self.assertFalse(should_recharge) should_recharge, _ = turn_runtime.should_recharge_live_coach_credit( {"appropriateness": "neutral", "rapport_signal": 0.8}, before, after, ) self.assertFalse(should_recharge) if __name__ == "__main__": unittest.main()