Spaces:
Sleeping
Sleeping
| """μΈμ μμν νκ· β μ€λ μ·/볡μ + 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 | |
| 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 # μ¦μ λ§λ£ μ·¨κΈ β μ§μμ§λ€ | |
| # ββ λΌμ°ν° (μ¬μμ λͺ¨μ¬) ββββββββββββββββββββββββββββββββββββ | |
| 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 # ν¬μΌμ λΉμμ β λ«ν μ± λ³΅μ | |
| 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 | |