Files
Echo-Tavern/tests/test_room_manager_flow.py
T
2026-03-08 09:16:44 +08:00

183 lines
5.8 KiB
Python

from __future__ import annotations
import json
import threading
import pytest
from game.action_resolver import ActionResolver
from game.room_manager import RoomManager
from game.state_manager import StateManager
from game.story_engine import StoryEngine
from services.deepseek_client import DeepSeekClient
from services.persistence import SQLitePersistence
def _build_manager(db_path: str) -> RoomManager:
persistence = SQLitePersistence(db_path)
story_engine = StoryEngine()
state_manager = StateManager(persistence, story_engine)
action_resolver = ActionResolver()
deepseek_client = DeepSeekClient(api_key="")
return RoomManager(
persistence,
state_manager,
story_engine,
action_resolver,
deepseek_client,
max_players=8,
max_action_length=280,
)
def test_start_game_writes_initial_narration(test_db_path):
manager = _build_manager(test_db_path)
created = manager.create_room("Host")
room_id = created["room_id"]
host_id = created["player_id"]
manager.start_game(room_id, host_id)
snapshot = manager.get_snapshot(room_id)
ai_rows = [row for row in snapshot["messages"] if row["kind"] == "ai"]
assert ai_rows
assert "雾港旅店" in ai_rows[-1]["content"]
def test_single_player_action_auto_resolves_flag(test_db_path):
manager = _build_manager(test_db_path)
created = manager.create_room("Host")
room_id = created["room_id"]
host_id = created["player_id"]
manager.start_game(room_id, host_id)
action_result = manager.submit_action(room_id, host_id, "检查登记台")
assert action_result["should_auto_resolve"] is True
def test_multi_player_action_waits_until_all_online_submitted(test_db_path):
manager = _build_manager(test_db_path)
created = manager.create_room("Host")
room_id = created["room_id"]
host_id = created["player_id"]
join_info, _ = manager.join_room(room_id, "Mate")
mate_id = join_info["player_id"]
manager.set_ready(room_id, mate_id, True)
manager.start_game(room_id, host_id)
first = manager.submit_action(room_id, host_id, "先查看油画")
assert first["should_auto_resolve"] is False
second = manager.submit_action(room_id, mate_id, "检查登记台")
assert second["should_auto_resolve"] is True
def test_same_player_cannot_submit_twice_before_round_resolve(test_db_path):
manager = _build_manager(test_db_path)
created = manager.create_room("Host")
room_id = created["room_id"]
host_id = created["player_id"]
join_info, _ = manager.join_room(room_id, "Mate")
mate_id = join_info["player_id"]
manager.set_ready(room_id, mate_id, True)
manager.start_game(room_id, host_id)
manager.submit_action(room_id, host_id, "先查看前台账本")
with pytest.raises(ValueError, match="action_already_submitted"):
manager.submit_action(room_id, host_id, "再翻一遍前台账本")
def test_ai_failure_does_not_resolve_round_or_clear_pending_actions(test_db_path):
manager = _build_manager(test_db_path)
created = manager.create_room("Host")
room_id = created["room_id"]
host_id = created["player_id"]
manager.start_game(room_id, host_id)
manager.deepseek_client.chat_json = lambda *args, **kwargs: {
"ok": False,
"error": "request_timeout",
"detail": "Read timed out",
}
manager.submit_action(room_id, host_id, "检查登记台")
with pytest.raises(ValueError, match="ai_call_failed:request_timeout"):
manager.resolve_round(room_id, host_id)
snapshot = manager.get_snapshot(room_id)
assert snapshot["pending_actions"]
assert snapshot["game_state"]["round_no"] == 0
def test_concurrent_resolve_round_only_one_can_run(test_db_path):
manager = _build_manager(test_db_path)
created = manager.create_room("Host")
room_id = created["room_id"]
host_id = created["player_id"]
manager.start_game(room_id, host_id)
manager.submit_action(room_id, host_id, "检查登记台")
started = threading.Event()
release = threading.Event()
def slow_ai(*args, **kwargs):
started.set()
release.wait(timeout=2)
return {
"ok": True,
"content": json.dumps(
{
"narration": "你翻开了登记台账本,发现被撕掉的一页。",
"npc_dialogues": [],
"state_updates": {
"danger_delta": 0,
"add_clues": [],
"set_flags": {},
"npc_changes": {},
"inventory_add": {},
"inventory_remove": {},
},
"private_player_updates": [],
"choices": ["继续调查"],
"should_end_scene": False,
"should_end_game": False,
"ending_summary": None,
},
ensure_ascii=False,
),
}
manager.deepseek_client.chat_json = slow_ai
outcomes: list[tuple[str, str]] = []
def runner():
try:
manager.resolve_round(room_id, host_id)
outcomes.append(("ok", ""))
except ValueError as exc:
outcomes.append(("err", str(exc)))
t1 = threading.Thread(target=runner)
t1.start()
assert started.wait(timeout=1.5)
t2 = threading.Thread(target=runner)
t2.start()
t2.join(timeout=2)
release.set()
t1.join(timeout=2)
ok_count = sum(1 for kind, _ in outcomes if kind == "ok")
err_codes = [code for kind, code in outcomes if kind == "err"]
assert ok_count == 1
assert any(code == "round_resolving" for code in err_codes)
snapshot = manager.get_snapshot(room_id)
assert snapshot["game_state"]["round_no"] == 1