fix: game service api block when a game is running
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
import json
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
from urllib.error import URLError
|
||||
|
||||
from texas_holdem.agents import HttpAgent, normalise_http_agent_endpoint
|
||||
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, payload: dict[str, object]) -> None:
|
||||
self.payload = payload
|
||||
|
||||
def read(self) -> bytes:
|
||||
return json.dumps(self.payload).encode("utf-8")
|
||||
|
||||
def __enter__(self) -> "FakeResponse":
|
||||
return self
|
||||
|
||||
def __exit__(self, *args: object) -> None:
|
||||
return None
|
||||
|
||||
|
||||
class AgentTests(unittest.TestCase):
|
||||
def test_normalise_http_agent_endpoint_accepts_action_or_game_paths(self) -> None:
|
||||
self.assertEqual(
|
||||
normalise_http_agent_endpoint("http://127.0.0.1:9101/act"),
|
||||
"http://127.0.0.1:9101",
|
||||
)
|
||||
self.assertEqual(
|
||||
normalise_http_agent_endpoint("http://127.0.0.1:9101/game/"),
|
||||
"http://127.0.0.1:9101",
|
||||
)
|
||||
|
||||
def test_http_agent_post_retries_and_sets_player_header(self) -> None:
|
||||
calls = []
|
||||
|
||||
def fake_urlopen(request, timeout): # type: ignore[no-untyped-def]
|
||||
calls.append((request, timeout))
|
||||
if len(calls) == 1:
|
||||
raise URLError("temporary")
|
||||
return FakeResponse({"ok": True})
|
||||
|
||||
agent = HttpAgent(
|
||||
"http://agent.test/act",
|
||||
player_id="p1",
|
||||
retries=1,
|
||||
retry_backoff_seconds=0,
|
||||
)
|
||||
|
||||
with patch("texas_holdem.agents.urlopen", fake_urlopen):
|
||||
payload = agent._post_json("/game", {"game_id": "g1"}, timeout_seconds=2)
|
||||
|
||||
self.assertEqual(payload, {"ok": True})
|
||||
self.assertEqual(len(calls), 2)
|
||||
self.assertEqual(calls[1][0].headers["X-player-id"], "p1")
|
||||
self.assertEqual(calls[1][1], 2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
+74
-1
@@ -1,8 +1,26 @@
|
||||
import unittest
|
||||
from threading import Event, Thread
|
||||
|
||||
from texas_holdem.agents import PokerAgent
|
||||
from texas_holdem.models import Observation, PlayerAction
|
||||
from texas_holdem.service import GameManager
|
||||
|
||||
|
||||
class BlockingAgent(PokerAgent):
|
||||
def __init__(self, entered: Event, release: Event) -> None:
|
||||
self.entered = entered
|
||||
self.release = release
|
||||
|
||||
def decide(self, observation: Observation) -> PlayerAction:
|
||||
self.entered.set()
|
||||
if not self.release.wait(timeout=5):
|
||||
raise RuntimeError("test timed out waiting to release blocking agent")
|
||||
for action in observation.legal_actions:
|
||||
if action["action"] == "check":
|
||||
return PlayerAction("check")
|
||||
return PlayerAction("call")
|
||||
|
||||
|
||||
class ServiceTests(unittest.TestCase):
|
||||
def test_create_and_run_game(self) -> None:
|
||||
manager = GameManager()
|
||||
@@ -23,7 +41,62 @@ class ServiceTests(unittest.TestCase):
|
||||
hands = manager.run_hands(game.game_id, count=1)
|
||||
|
||||
self.assertEqual(len(hands), 1)
|
||||
self.assertEqual(manager.get_game("demo").to_dict()["hand_number"], 1)
|
||||
self.assertEqual(manager.get_game_state("demo")["hand_number"], 1)
|
||||
|
||||
def test_get_game_state_does_not_block_during_run(self) -> None:
|
||||
manager = GameManager()
|
||||
entered = Event()
|
||||
release = Event()
|
||||
game = manager.create_game(
|
||||
{
|
||||
"game_id": "blocking",
|
||||
"seed": 13,
|
||||
"starting_stack": 200,
|
||||
"small_blind": 5,
|
||||
"big_blind": 10,
|
||||
"players": [
|
||||
{"id": "a", "type": "calling"},
|
||||
{"id": "b", "type": "calling"},
|
||||
],
|
||||
}
|
||||
)
|
||||
manager.run_hands("blocking", count=1)
|
||||
game.agents["a"] = BlockingAgent(entered, release)
|
||||
|
||||
thread = Thread(target=lambda: manager.run_hands("blocking", count=1))
|
||||
thread.start()
|
||||
self.assertTrue(entered.wait(timeout=2))
|
||||
|
||||
state = manager.get_game_state("blocking")
|
||||
|
||||
release.set()
|
||||
thread.join(timeout=2)
|
||||
self.assertFalse(thread.is_alive())
|
||||
self.assertEqual(state["hand_number"], 1)
|
||||
self.assertEqual(len(state["hands"]), 1)
|
||||
|
||||
def test_duplicate_http_agent_endpoint_is_rejected_across_active_games(self) -> None:
|
||||
manager = GameManager()
|
||||
payload = {
|
||||
"starting_stack": 200,
|
||||
"small_blind": 5,
|
||||
"big_blind": 10,
|
||||
"players": [
|
||||
{
|
||||
"id": "ai",
|
||||
"agent": {
|
||||
"type": "http",
|
||||
"endpoint": "http://127.0.0.1:9101/act",
|
||||
},
|
||||
},
|
||||
{"id": "b", "type": "calling"},
|
||||
],
|
||||
}
|
||||
|
||||
manager.create_game({"game_id": "g1", **payload})
|
||||
|
||||
with self.assertRaisesRegex(ValueError, "already belongs to game g1"):
|
||||
manager.create_game({"game_id": "g2", **payload})
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user