Coverage for backend/tests/test_ws.py: 98%
110 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-02 01:42 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-02 01:42 +0000
1import pytest
2from fastapi.testclient import TestClient
4from backend.dependencies import LobbyManager, lobby_manager
5from backend.hello import app
7lm = LobbyManager(["a", "b", "c"])
10def lobby_manager_override() -> LobbyManager:
11 """Override lobby manager."""
12 return lm
15app.dependency_overrides[lobby_manager] = lobby_manager_override
18@pytest.fixture
19def lobby_client() -> TestClient:
20 """Create a TestClient."""
21 client = TestClient(app)
22 return client
25@pytest.mark.slow
26def test_websocket(lobby_client: TestClient) -> None:
27 """Test that websocket connection does not hang."""
28 code = lobby_client.post("lobby").json()["code"]
29 player_1_id = lobby_client.post("/lobby/join", json={"code": code}).json()["id"]
30 player_2_id = lobby_client.post("/lobby/join", json={"code": code}).json()["id"]
32 with lobby_client.websocket_connect("/ws") as websocket_1, lobby_client.websocket_connect("/ws") as websocket_2:
33 websocket_1.send_json(dict(data=dict(type="initializer", code=code, id=player_1_id)))
34 websocket_2.send_json(dict(data=dict(type="initializer", code=code, id=player_2_id)))
36 # since player_1 joins first, this should be a player count message with count=1
37 player_count = websocket_1.receive_json()
38 assert player_count["data"]["count"] == 1
39 # the next message for player_1 will be the player count update for player_2 joining
40 player_count = websocket_1.receive_json()
41 assert player_count["data"]["count"] == 2
43 # whereas player_2 should only get the player count for player_2 joining
44 player_count = websocket_2.receive_json()
45 assert player_count["data"]["count"] == 2
47 # player_1 starts the game...
48 websocket_1.send_json(dict(data=dict(type="lobby_lifecycle", lifecycle_type="game_start")))
50 # ...so player_2 receives the start broadcast
51 data = websocket_2.receive_json()["data"]
52 assert data["lifecycle_type"] == "game_start"
54 # next message is the role assignment
55 player_1_role = websocket_1.receive_json()["data"]
56 _ = websocket_2.receive_json()["data"]
58 if player_1_role["role"] == "manager":
59 manager = websocket_1
60 cook = websocket_2
61 else:
62 manager = websocket_2
63 cook = websocket_1
65 # next message to the manager should be the order
66 data = manager.receive_json()["data"]
67 order = data["order"]
68 burger = order["burger"]["ingredients"]
69 assert burger[0] == "Bottom Bun"
70 assert burger[-1] == "Top Bun"
72 # cook should send an order component...
73 cook.send_json(
74 dict(
75 data=dict(
76 type="game_state",
77 game_state_update_type="order_component",
78 component_type="burger",
79 component=dict(ingredients=burger),
80 )
81 )
82 )
84 # ...which should be sent to the manager
85 data = manager.receive_json()["data"]
86 assert data["component"]["ingredients"] == burger
88 # manager submits order
89 manager.send_json(
90 dict(
91 data=dict(
92 type="game_state",
93 game_state_update_type="order_submission",
94 order=dict(burger=dict(ingredients=burger), side=None, drink=None),
95 )
96 )
97 )
99 # and gets a score
100 data = manager.receive_json()["data"]
102 # a perfect burger gets 500
103 assert data["score"] == 500
106@pytest.mark.slow
107def test_websocket_full_order(lobby_client: TestClient) -> None:
108 """Test that websocket handles all 4 players and full order flow."""
109 code = lobby_client.post("lobby").json()["code"]
111 player_ids = [lobby_client.post("/lobby/join", json={"code": code}).json()["id"] for _ in range(4)]
112 websockets = [lobby_client.websocket_connect("/ws") for _ in range(4)]
114 with websockets[0] as ws1, websockets[1] as ws2, websockets[2] as ws3, websockets[3] as ws4:
115 sockets = [ws1, ws2, ws3, ws4]
117 # Send initializer messages
118 for ws, pid in zip(sockets, player_ids, strict=False):
119 ws.send_json({"data": {"type": "initializer", "code": code, "id": pid}})
121 # Each websocket receives player count updates
122 for ws in sockets:
123 while True:
124 msg = ws.receive_json()
125 if msg["data"].get("count") == 4:
126 break
128 # Start game from first player
129 ws1.send_json({"data": {"type": "lobby_lifecycle", "lifecycle_type": "game_start"}})
131 # Wait for game start broadcast and role assignments
132 roles = {}
133 for ws, _ in zip(sockets, player_ids, strict=False):
134 data = ws.receive_json()["data"]
135 if data.get("lifecycle_type") == "game_start":
136 data = ws.receive_json()["data"]
137 roles[data["role"]] = ws
139 assert {"manager", "burger", "side", "drink"} == set(roles)
141 manager = roles["manager"]
142 burger_cook = roles["burger"]
143 side_cook = roles["side"]
144 drink_cook = roles["drink"]
146 # Manager receives an order
147 data = manager.receive_json()["data"]
148 order = data["order"]
149 burger = order["burger"]["ingredients"]
150 side = order["side"]["table_state"]
151 drink = order["drink"]
153 # Burger cook sends component
154 burger_cook.send_json(
155 {
156 "data": {
157 "type": "game_state",
158 "game_state_update_type": "order_component",
159 "component_type": "burger",
160 "component": {"ingredients": burger},
161 }
162 }
163 )
164 data = manager.receive_json()["data"]
165 assert data["component"]["ingredients"] == burger
167 # Side cook sends component
168 side_cook.send_json(
169 {
170 "data": {
171 "type": "game_state",
172 "game_state_update_type": "order_component",
173 "component_type": "side",
174 "component": {"table_state": side},
175 }
176 }
177 )
178 data = manager.receive_json()["data"]
179 assert data["component"]["table_state"] == side
181 # Drink cook sends component
182 drink_cook.send_json(
183 {
184 "data": {
185 "type": "game_state",
186 "game_state_update_type": "order_component",
187 "component_type": "drink",
188 "component": {"color": drink["color"], "fill": drink["fill"], "size": drink["size"]},
189 }
190 }
191 )
192 data = manager.receive_json()["data"]
193 assert data["component"]["color"] == drink["color"]
195 # Manager submits complete order
196 manager.send_json(
197 {
198 "data": {
199 "type": "game_state",
200 "game_state_update_type": "order_submission",
201 "order": {
202 "burger": {"ingredients": burger},
203 "side": {"table_state": side},
204 "drink": {"color": drink["color"], "fill": drink["fill"], "size": drink["size"]},
205 },
206 }
207 }
208 )
210 # Manager receives score
211 score_msg = manager.receive_json()["data"]
212 assert "score" in score_msg
213 assert score_msg["score"] == 1200
216@pytest.mark.slow
217def test_websocket_spam_chat(lobby_client: TestClient) -> None:
218 """Test that a bunch of chat messages are received and broadcast without pausing."""
219 code = lobby_client.post("lobby").json()["code"]
220 player_1_id = lobby_client.post("/lobby/join", json={"code": code}).json()["id"]
221 player_2_id = lobby_client.post("/lobby/join", json={"code": code}).json()["id"]
223 with lobby_client.websocket_connect("/ws") as websocket_1, lobby_client.websocket_connect("/ws") as websocket_2:
224 websocket_1.send_json(dict(data=dict(type="initializer", code=code, id=player_1_id)))
225 websocket_2.send_json(dict(data=dict(type="initializer", code=code, id=player_2_id)))
227 # since player_1 joins first, this should be a player count message with count=1
228 player_count = websocket_1.receive_json()
229 assert player_count["data"]["count"] == 1
230 # the next message for player_1 will be the player count update for player_2 joining
231 player_count = websocket_1.receive_json()
232 assert player_count["data"]["count"] == 2
234 # whereas player_2 should only get the player count for player_2 joining
235 player_count = websocket_2.receive_json()
236 assert player_count["data"]["count"] == 2
238 for _ in range(10):
239 websocket_1.send_json(dict(data=dict(type="chat", typing=True, id=player_1_id)))
241 for _ in range(10):
242 data = websocket_2.receive_json()["data"]
243 assert data["type"] == "chat"