interactive-chat / tests /test_persistence.py
Junhoee's picture
Upload 162 files
1c9c13f verified
Raw
History Blame Contribute Delete
5.39 kB
"""μ„Έμ…˜ μ˜μ†ν™” νšŒκ·€ β€” μŠ€λƒ…μƒ·/볡원 + SQLite μ €μž₯μ†Œ + λΌμš°ν„° μž¬μ‹œμž‘ 볡원.
핡심 νšŒκ·€: 볡원은 _enter()λ₯Ό κ±°μΉ˜μ§€ μ•ŠλŠ”λ‹€ β€” 쇠약도/신뒰좕이 재적용되면 결함.
"""
import pytest
from fastapi import HTTPException
from engine import router
from engine.repositories.content import ContentRepository
from engine.repositories.play_session import PlaySessionRepository
from engine.services.story import StoryEngine
@pytest.fixture(scope="module")
def repo():
return ContentRepository("content")
def play_until(repo, stop_id: str | None, policy: str = "trust") -> StoryEngine:
"""stop_id μΉ΄λ“œ 도달(λ˜λŠ” None이면 μ—”λ”©)κΉŒμ§€ μ§„ν–‰."""
engine = StoryEngine(repo)
step = engine.start()
while step.ending is None and engine.current_id != stop_id:
if step.choices:
idx = next((i for i, c in enumerate(step.choices) if c.axis == policy),
next((i for i, c in enumerate(step.choices)
if c.axis in (None, "neutral")), 0))
_, step = engine.choose(idx)
else:
step = engine.advance()
return engine
# ── μŠ€λƒ…μƒ·/볡원 (μ—”μ§„) ──────────────────────────────────────
def test_snapshot_restore_roundtrip(repo):
engine = play_until(repo, "E04-01")
snap = engine.snapshot()
restored = StoryEngine.restore(repo, snap)
assert restored.snapshot() == snap # 볡원이 μƒνƒœλ₯Ό μ‘°κΈˆλ„ λ°”κΎΈμ§€ μ•ŠλŠ”λ‹€
assert restored.current_id == engine.current_id
assert restored.visited == engine.visited
step = restored.current_step()
assert step.card.id == engine.current_id
assert step.ending is None
def test_restore_does_not_reapply_enter_side_effects(repo):
"""핡심 νšŒκ·€ β€” 볡원 μ‹œ 쇠약도·신뒰좕이 재적용되면 μ•ˆ λœλ‹€ (_enter 우회 검증)."""
engine = play_until(repo, None) # μ—”λ”©κΉŒμ§€ = 쇠약도·좕 이벀트 λ‹€μˆ˜ λˆ„μ 
assert engine.state.frailty > 0 and engine.state.trust_score > 0
snap = engine.snapshot()
restored = StoryEngine.restore(repo, snap)
assert restored.state.frailty == engine.state.frailty
assert restored.state.trust_score == engine.state.trust_score
assert restored.state.doubt_score == engine.state.doubt_score
assert restored.state.axis_log == engine.state.axis_log
# μ—¬λŸ¬ 번 볡원해도 동일 (재적용이 있으면 μ—¬κΈ°μ„œ μ–΄κΈ‹λ‚œλ‹€)
again = StoryEngine.restore(repo, restored.snapshot())
assert again.snapshot() == snap
def test_ending_session_restores_to_ending(repo):
engine = play_until(repo, None, policy="trust")
assert engine.ending is not None
restored = StoryEngine.restore(repo, engine.snapshot())
step = restored.current_step()
assert step.ending is not None and step.ending.id == engine.ending.id
# ── SQLite μ €μž₯μ†Œ ───────────────────────────────────────────
def test_play_session_repo_save_load(tmp_path, repo):
db = PlaySessionRepository(tmp_path / "sessions.db")
engine = play_until(repo, "E02-01")
db.save("sid1", engine.snapshot(), repo.content_version)
loaded = db.load("sid1", repo.content_version)
assert loaded == engine.snapshot()
assert db.load("μ—†λŠ”sid", repo.content_version) is None
def test_content_version_mismatch_invalidates(tmp_path, repo):
db = PlaySessionRepository(tmp_path / "sessions.db")
db.save("sid1", {"story": {}, "hidden": {}}, "κ΅¬λ²„μ „ν•΄μ‹œ")
assert db.load("sid1", repo.content_version) is None # μ½˜ν…μΈ  κ°±μ‹  β†’ λ¬΄νš¨ν™”
def test_purge_removes_stale_sessions(tmp_path):
db = PlaySessionRepository(tmp_path / "sessions.db")
db.save("sid1", {"story": {}, "hidden": {}}, "v")
assert db.purge(days=30) == 0 # 방금 μ €μž₯ β€” λ‚¨λŠ”λ‹€
assert db.purge(days=0) == 1 # μ¦‰μ‹œ 만료 μ·¨κΈ‰ β€” μ§€μ›Œμ§„λ‹€
# ── λΌμš°ν„° (μž¬μ‹œμž‘ λͺ¨μ‚¬) ────────────────────────────────────
@pytest.mark.asyncio
async def test_router_restores_after_restart(tmp_path, monkeypatch):
monkeypatch.setattr(router, "_playdb", PlaySessionRepository(tmp_path / "s.db"))
monkeypatch.setattr(router, "_sessions", {})
data = await router.create_session()
sid = data["session_id"]
step = data["step"]
while not step["choices"] and not step["ending"]: # 첫 μ„ νƒμ§€κΉŒμ§€ 읽기
step = (await router.advance(sid))["step"]
step = (await router.choose(sid, router.ChooseBody(index=0)))["step"]
current_card = step["card"]["id"]
router._sessions.clear() # μ„œλ²„ μž¬μ‹œμž‘ λͺ¨μ‚¬ (인메λͺ¨λ¦¬ μΊμ‹œ μ†Œμ‹€)
got = await router.get_session(sid)
assert got["step"]["card"]["id"] == current_card
assert router._sessions[sid].pocket_sid is None # 포켓은 λΉ„μ˜μ† β€” λ‹«νžŒ 채 볡원
@pytest.mark.asyncio
async def test_router_unknown_sid_404(tmp_path, monkeypatch):
monkeypatch.setattr(router, "_playdb", PlaySessionRepository(tmp_path / "s.db"))
monkeypatch.setattr(router, "_sessions", {})
with pytest.raises(HTTPException) as exc:
await router.get_session("μ—†λŠ”μ„Έμ…˜")
assert exc.value.status_code == 404