"""Session review public share regression tests.""" from __future__ import annotations import unittest from starlette.requests import Request from . import session_persistence from .deps import Principal, Role from .routes import sessions from .routes import share as share_routes from .services import persona as persona_service, state_machine from .store import InProcSession, TurnRecord, store def _principal() -> Principal: return Principal( user_id="00000000-0000-0000-0000-000000000201", role=Role.LEARNER, cohort_ids=[], email="share-test@hs.ac.kr", display_name="Share Test", consent_at=1.0, ) def _request(path: str = "/sessions/share-test-session/share") -> Request: return Request( { "type": "http", "method": "POST", "path": path, "scheme": "https", "server": ("api.test", 443), "headers": [(b"host", b"api.test")], } ) def _ended_session(principal: Principal) -> InProcSession: card = persona_service.P1 sess = InProcSession( session_id="share-test-session", case_id="share-test-case", learner_id=principal.user_id, persona_code=card.code, theory_mode="humanistic", persona=card, state=state_machine.SessionState( resistance=card.base_resistance(), ideation_stage=card.ideation_baseline(), ), ended=True, ended_at=1_800_000_100, ) sess.turns.extend( [ TurnRecord( turn_seq=1, speaker="counselor", stage="라포", text="원문 학습자 민감 발화", text_masked="원문 학습자 민감 발화", created_at=1_800_000_000, ), TurnRecord( turn_seq=2, speaker="client", stage="라포", text="내담자 응답", text_masked="내담자 응답", created_at=1_800_000_030, ), ] ) store.put(sess) return sess class SessionShareTest(unittest.IsolatedAsyncioTestCase): async def asyncSetUp(self) -> None: store._sessions.clear() session_persistence._SESSION_SHARE_CACHE.clear() session_persistence._SESSION_SHARE_TOKEN_INDEX.clear() async def asyncTearDown(self) -> None: store._sessions.clear() session_persistence._SESSION_SHARE_CACHE.clear() session_persistence._SESSION_SHARE_TOKEN_INDEX.clear() async def test_create_share_returns_public_summary_without_transcript(self) -> None: principal = _principal() _ended_session(principal) response = await sessions.create_session_share( "share-test-session", _request(), principal, ) self.assertTrue(response.shareUrl.startswith("https://api.test/share/session/")) token = response.shareUrl.rsplit("/", 1)[-1] summary = await share_routes._load_share_or_404(token) self.assertIn("Vignette 회기 리뷰", summary.title) self.assertIn(summary.reachedPhase, {"라포", "탐색", "개입", "정리"}) self.assertNotIn("원문 학습자 민감 발화", summary.model_dump_json()) self.assertIn("저장된 실제 축어록 2개", summary.summary) async def test_public_share_normalizes_legacy_reached_phase(self) -> None: coded = share_routes._safe_payload({"reachedPhase": "rapport"}) blank = share_routes._safe_payload({"reachedPhase": ""}) invalid = share_routes._safe_payload({"reachedPhase": "unknown-stage"}) missing = share_routes._safe_payload({}) self.assertEqual(coded.reachedPhase, "라포") self.assertIsNone(blank.reachedPhase) self.assertIsNone(invalid.reachedPhase) self.assertIsNone(missing.reachedPhase) async def test_public_share_lookup_tolerates_legacy_blank_reached_phase(self) -> None: token = "legacyShareTokenValue000000000000" token_hash = session_persistence.share_token_hash(token) session_persistence._SESSION_SHARE_CACHE["legacy-share-session"] = { "session_id": "legacy-share-session", "token_hash": token_hash, "payload": { "title": "Legacy share", "description": "Legacy summary", "reachedPhase": "", }, "created_at": 1_800_000_000.0, "updated_at": 1_800_000_000.0, "revoked_at": None, } session_persistence._SESSION_SHARE_TOKEN_INDEX[token_hash] = "legacy-share-session" summary = await share_routes._load_share_or_404(token) self.assertEqual(summary.title, "Legacy share") self.assertIsNone(summary.reachedPhase) async def test_revoke_share_blocks_public_lookup(self) -> None: principal = _principal() _ended_session(principal) response = await sessions.create_session_share( "share-test-session", _request(), principal, ) token = response.shareUrl.rsplit("/", 1)[-1] revoked = await sessions.revoke_session_share("share-test-session", principal) self.assertTrue(revoked.revoked) with self.assertRaises(share_routes.HTTPException) as caught: await share_routes._load_share_or_404(token) self.assertEqual(caught.exception.status_code, 404)