Spaces:
Sleeping
Sleeping
File size: 5,390 Bytes
1c9c13f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | """μΈμ
μμν νκ· β μ€λ
μ·/볡μ + 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
|