"""sr_schedule 공용추출 회귀 테스트 (B1). 문제 SR 동작이 추출 전과 동일함을 보장 — advance() 가 구 session_finalize 분기 로직과 바이트 동등(전진 1·3·7·14 / 졸업 / 오답 리셋 / skipped 불변 / 상수값)인지 검증. """ from __future__ import annotations import sys from datetime import datetime, timedelta, timezone from pathlib import Path ROOT = Path(__file__).resolve().parent.parent sys.path.insert(0, str(ROOT / "app")) from services.study import sr_schedule as sr # noqa: E402 NOW = datetime(2026, 6, 7, 12, 0, 0, tzinfo=timezone.utc) def _old_logic(review_stage, outcome, now): """추출 전 session_finalize.py:188-201 의 산술을 그대로 재현 (동등성 기준).""" if outcome == "correct": new_stage = (review_stage or 0) + 1 if new_stage >= 4: return new_stage, None return new_stage, now + timedelta(days={1: 3, 2: 7, 3: 14}[new_stage]) elif outcome in ("wrong", "unsure"): return 0, now + timedelta(days=1) return None # skipped def test_constants(): assert sr.REVIEW_INTERVAL_DAYS == {1: 3, 2: 7, 3: 14} assert sr.REVIEW_STAGE_MASTERED == 4 assert sr.DEFAULT_FIRST_DUE_DAYS == 1 def test_advance_correct_progression(): assert sr.advance(None, "correct", NOW) == (1, NOW + timedelta(days=3)) assert sr.advance(0, "correct", NOW) == (1, NOW + timedelta(days=3)) assert sr.advance(1, "correct", NOW) == (2, NOW + timedelta(days=7)) assert sr.advance(2, "correct", NOW) == (3, NOW + timedelta(days=14)) def test_advance_graduation(): # stage 3 → correct → stage 4 = 졸업(due_at=None) assert sr.advance(3, "correct", NOW) == (4, None) assert sr.advance(4, "correct", NOW) == (5, None) def test_advance_reset(): assert sr.advance(0, "wrong", NOW) == (0, NOW + timedelta(days=1)) assert sr.advance(2, "wrong", NOW) == (0, NOW + timedelta(days=1)) assert sr.advance(2, "unsure", NOW) == (0, NOW + timedelta(days=1)) def test_advance_skipped_no_change(): assert sr.advance(1, "skipped", NOW) is None assert sr.advance(3, "skipped", NOW) is None def test_first_due(): assert sr.first_due(NOW) == (0, NOW + timedelta(days=1)) def test_equivalence_with_old_logic(): # 전 stage × 전 outcome 조합에서 추출 함수 == 구 로직. for stage in (None, 0, 1, 2, 3, 4): for outcome in ("correct", "wrong", "unsure", "skipped"): assert sr.advance(stage, outcome, NOW) == _old_logic(stage, outcome, NOW), \ f"mismatch stage={stage} outcome={outcome}" def test_reexport_preserved(): # 기존 import 경로 (session_finalize / study_question_progress) 가 상수를 재-export. from services.study import session_finalize as sf assert sf.REVIEW_INTERVAL_DAYS == sr.REVIEW_INTERVAL_DAYS assert sf.REVIEW_STAGE_MASTERED == sr.REVIEW_STAGE_MASTERED assert sf.DEFAULT_FIRST_DUE_DAYS == sr.DEFAULT_FIRST_DUE_DAYS _TESTS = [v for k, v in dict(globals()).items() if k.startswith("test_")] if __name__ == "__main__": # session_finalize import 는 무거운 의존(ai 등) 가능 — reexport 테스트만 조건부. ran = 0 for t in _TESTS: if t.__name__ == "test_reexport_preserved": continue t() ran += 1 print(f"OK ({ran} pure tests; reexport는 pytest에서)")