개선관리 요구사항과 Google 로그인을 완료

This commit is contained in:
Yun Chan 2026-08-28 16:07:09 +09:00
parent cc0a15b7c6
commit 2a39636163
112 changed files with 10166 additions and 527 deletions

View file

@ -211,6 +211,48 @@ class DeliberatePracticeStoreAsyncTests(unittest.IsolatedAsyncioTestCase):
episode=self.pack.cases[0].episodes[0],
)
async def test_attempt_rejects_disabled_prescription_source_before_replay(
self,
) -> None:
principal = _principal(Role.LEARNER)
case = self.pack.cases[0]
episode_payload = case.episodes[0].model_dump(mode="python")
for attempt in episode_payload["attempts"]:
for ref in attempt["evidence_refs"]:
ref["ref_id"] = str(uuid4())
for ref in attempt["criterion"]["evidence_refs"]:
ref["ref_id"] = str(uuid4())
episode = type(case.episodes[0]).model_validate(episode_payload)
source_session_id = uuid4()
conn = AsyncMock()
conn.fetchrow.return_value = {
"prescription_record_id": uuid4(),
"session_id": source_session_id,
"prescription_payload": {},
"created_at": datetime.now(timezone.utc),
"source_learner_feedback_enabled": False,
}
@asynccontextmanager
async def fake_acquire(**_kwargs):
yield conn
with patch.object(deliberate_practice_store.db, "acquire", fake_acquire):
with self.assertRaises(
deliberate_practice_store.DeliberatePracticeFeedbackDisabledError
):
await deliberate_practice_store.append_learner_attempt_submission(
principal=principal,
submission_id=uuid4(),
prescription_id=episode.prescription_id,
episode=episode,
)
self.assertEqual(conn.fetchrow.await_count, 1)
source_query = conn.fetchrow.await_args.args[0]
self.assertIn("source_session.learner_feedback_enabled", source_query)
self.assertNotIn("app.practice_episode_submission", source_query)
async def test_runtime_observer_derives_attempt_from_later_ready_session(
self,
) -> None:
@ -388,6 +430,71 @@ class DeliberatePracticeStoreAsyncTests(unittest.IsolatedAsyncioTestCase):
self.assertIsNone(result["competency_graph"])
self.assertFalse(result["clinical_claim_allowed"])
async def test_learner_read_rejects_any_disabled_source_snapshot(self) -> None:
principal = _principal(Role.LEARNER)
conn = AsyncMock()
conn.fetch.side_effect = [
[
{
"prescription_record_id": uuid4(),
"session_id": uuid4(),
"source_learner_feedback_enabled": False,
}
],
[],
]
conn.fetchrow.return_value = None
@asynccontextmanager
async def fake_acquire(**_kwargs):
yield conn
with patch.object(deliberate_practice_store.db, "acquire", fake_acquire):
with self.assertRaises(
deliberate_practice_store.DeliberatePracticeFeedbackDisabledError
):
await deliberate_practice_store.read_deliberate_practice(
principal=principal
)
prescription_query = conn.fetch.await_args_list[0].args[0]
episode_query = conn.fetch.await_args_list[1].args[0]
self.assertIn("source_session.learner_feedback_enabled", prescription_query)
self.assertIn("source_session.learner_feedback_enabled", episode_query)
async def test_teacher_read_keeps_disabled_source_snapshot_visible(self) -> None:
principal = _principal(Role.TEACHER)
learner_id = uuid4()
conn = AsyncMock()
conn.fetchval.return_value = True
conn.fetch.side_effect = [
[
{
"prescription_record_id": uuid4(),
"session_id": uuid4(),
"source_learner_feedback_enabled": False,
}
],
[],
]
conn.fetchrow.return_value = None
@asynccontextmanager
async def fake_acquire(**_kwargs):
yield conn
with patch.object(deliberate_practice_store.db, "acquire", fake_acquire):
result = await deliberate_practice_store.read_deliberate_practice(
principal=principal,
learner_id=learner_id,
)
self.assertEqual(len(result["prescriptions"]), 1)
self.assertNotIn(
"source_learner_feedback_enabled",
result["prescriptions"][0],
)
async def test_learner_read_cannot_target_another_learner(self) -> None:
with self.assertRaises(
deliberate_practice_store.DeliberatePracticeNotFoundError
@ -439,6 +546,32 @@ class DeliberatePracticeStoreAsyncTests(unittest.IsolatedAsyncioTestCase):
)
self.assertEqual(captured.exception.status_code, 409)
async def test_attempt_route_maps_disabled_source_snapshot_to_403(self) -> None:
case = self.pack.cases[0]
body = deliberate_practices.PracticeAttemptSubmissionRequest(
submission_id=uuid4(),
episode=case.episodes[0],
)
with patch.object(
deliberate_practices.deliberate_practice_store,
"append_learner_attempt_submission",
AsyncMock(
side_effect=deliberate_practice_store.DeliberatePracticeFeedbackDisabledError(
"source session feedback disabled"
)
),
) as append:
with self.assertRaises(HTTPException) as captured:
await deliberate_practices.create_practice_attempt(
prescription_id=case.episodes[0].prescription_id,
body=body,
principal=_principal(Role.LEARNER),
)
self.assertEqual(captured.exception.status_code, 403)
self.assertEqual(captured.exception.detail, "learner_feedback_disabled")
append.assert_awaited_once()
async def test_runtime_practice_route_forwards_completed_session_identity(
self,
) -> None: