Skip to content

Commit efb3490

Browse files
committed
test: add test for rearrange_jokers endpoint
1 parent 5edfde7 commit efb3490

File tree

1 file changed

+195
-0
lines changed

1 file changed

+195
-0
lines changed
Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
import socket
2+
from typing import Generator
3+
4+
import pytest
5+
6+
from balatrobot.enums import ErrorCode, State
7+
8+
from ..conftest import assert_error_response, send_and_receive_api_message
9+
10+
11+
class TestRearrangeJokers:
12+
"""Tests for the rearrange_jokers API endpoint."""
13+
14+
@pytest.fixture(autouse=True)
15+
def setup_and_teardown(
16+
self, tcp_client: socket.socket
17+
) -> Generator[dict, None, None]:
18+
"""Start a run, reach SELECTING_HAND phase with jokers, yield initial state, then clean up."""
19+
# Begin a run with The Omelette challenge which starts with jokers
20+
send_and_receive_api_message(
21+
tcp_client,
22+
"start_run",
23+
{
24+
"deck": "Red Deck",
25+
"stake": 1,
26+
"challenge": "The Omelette",
27+
"seed": "OOOO155",
28+
},
29+
)
30+
31+
# Select blind to enter SELECTING_HAND state with jokers already available
32+
game_state = send_and_receive_api_message(
33+
tcp_client, "skip_or_select_blind", {"action": "select"}
34+
)
35+
36+
assert game_state["state"] == State.SELECTING_HAND.value
37+
38+
# Skip if we don't have enough jokers to test with
39+
if (
40+
not game_state.get("jokers")
41+
or not game_state["jokers"].get("cards")
42+
or len(game_state["jokers"]["cards"]) < 2
43+
):
44+
pytest.skip("Not enough jokers available for testing rearrange_jokers")
45+
46+
yield game_state
47+
send_and_receive_api_message(tcp_client, "go_to_menu", {})
48+
49+
# ------------------------------------------------------------------
50+
# Success scenario
51+
# ------------------------------------------------------------------
52+
53+
def test_rearrange_jokers_success(
54+
self, tcp_client: socket.socket, setup_and_teardown: dict
55+
) -> None:
56+
"""Reverse the joker order and verify the API response reflects it."""
57+
initial_state = setup_and_teardown
58+
initial_jokers = initial_state["jokers"]["cards"]
59+
jokers_count: int = len(initial_jokers)
60+
61+
# Reverse order indices (API expects zero-based indices)
62+
new_order = list(range(jokers_count - 1, -1, -1))
63+
64+
final_state = send_and_receive_api_message(
65+
tcp_client,
66+
"rearrange_jokers",
67+
{"jokers": new_order},
68+
)
69+
70+
# Ensure we remain in selecting hand state
71+
assert final_state["state"] == State.SELECTING_HAND.value
72+
73+
# Compare sort_id ordering to make sure it's reversed
74+
initial_sort_ids = [joker["sort_id"] for joker in initial_jokers]
75+
final_sort_ids = [joker["sort_id"] for joker in final_state["jokers"]["cards"]]
76+
assert final_sort_ids == list(reversed(initial_sort_ids))
77+
78+
def test_rearrange_jokers_noop(
79+
self, tcp_client: socket.socket, setup_and_teardown: dict
80+
) -> None:
81+
"""Sending indices in current order should leave the jokers unchanged."""
82+
initial_state = setup_and_teardown
83+
initial_jokers = initial_state["jokers"]["cards"]
84+
jokers_count: int = len(initial_jokers)
85+
86+
# Existing order indices (0-based)
87+
current_order = list(range(jokers_count))
88+
89+
final_state = send_and_receive_api_message(
90+
tcp_client,
91+
"rearrange_jokers",
92+
{"jokers": current_order},
93+
)
94+
95+
assert final_state["state"] == State.SELECTING_HAND.value
96+
97+
initial_sort_ids = [joker["sort_id"] for joker in initial_jokers]
98+
final_sort_ids = [joker["sort_id"] for joker in final_state["jokers"]["cards"]]
99+
assert final_sort_ids == initial_sort_ids
100+
101+
# ------------------------------------------------------------------
102+
# Validation / error scenarios
103+
# ------------------------------------------------------------------
104+
105+
def test_rearrange_jokers_invalid_number_of_jokers(
106+
self, tcp_client: socket.socket, setup_and_teardown: dict
107+
) -> None:
108+
"""Providing an index list with the wrong length should error."""
109+
jokers_count = len(setup_and_teardown["jokers"]["cards"])
110+
invalid_order = list(range(jokers_count - 1)) # one short
111+
112+
response = send_and_receive_api_message(
113+
tcp_client,
114+
"rearrange_jokers",
115+
{"jokers": invalid_order},
116+
)
117+
118+
assert_error_response(
119+
response,
120+
"Invalid number of jokers to rearrange",
121+
["jokers_count", "valid_range"],
122+
ErrorCode.PARAMETER_OUT_OF_RANGE.value,
123+
)
124+
125+
def test_rearrange_jokers_out_of_range_index(
126+
self, tcp_client: socket.socket, setup_and_teardown: dict
127+
) -> None:
128+
"""Including an index >= jokers count should error."""
129+
jokers_count = len(setup_and_teardown["jokers"]["cards"])
130+
order = list(range(jokers_count))
131+
order[-1] = jokers_count # out-of-range zero-based index
132+
133+
response = send_and_receive_api_message(
134+
tcp_client,
135+
"rearrange_jokers",
136+
{"jokers": order},
137+
)
138+
139+
assert_error_response(
140+
response,
141+
"Joker index out of range",
142+
["index", "max_index"],
143+
ErrorCode.PARAMETER_OUT_OF_RANGE.value,
144+
)
145+
146+
def test_rearrange_jokers_no_jokers_available(
147+
self, tcp_client: socket.socket
148+
) -> None:
149+
"""Calling rearrange_jokers when no jokers are available should error."""
150+
# Start a run without jokers (regular Red Deck without The Omelette challenge)
151+
send_and_receive_api_message(
152+
tcp_client,
153+
"start_run",
154+
{
155+
"deck": "Red Deck",
156+
"stake": 1,
157+
"seed": "OOOO155",
158+
},
159+
)
160+
send_and_receive_api_message(
161+
tcp_client, "skip_or_select_blind", {"action": "select"}
162+
)
163+
164+
response = send_and_receive_api_message(
165+
tcp_client,
166+
"rearrange_jokers",
167+
{"jokers": []},
168+
)
169+
170+
assert_error_response(
171+
response,
172+
"No jokers available to rearrange",
173+
["jokers_available"],
174+
ErrorCode.MISSING_GAME_OBJECT.value,
175+
)
176+
177+
# Clean up
178+
send_and_receive_api_message(tcp_client, "go_to_menu", {})
179+
180+
def test_rearrange_jokers_missing_required_field(
181+
self, tcp_client: socket.socket, setup_and_teardown: dict
182+
) -> None:
183+
"""Calling rearrange_jokers without the jokers field should error."""
184+
response = send_and_receive_api_message(
185+
tcp_client,
186+
"rearrange_jokers",
187+
{}, # Missing required 'jokers' field
188+
)
189+
190+
assert_error_response(
191+
response,
192+
"Missing required field: jokers",
193+
["field"],
194+
ErrorCode.INVALID_PARAMETER.value,
195+
)

0 commit comments

Comments
 (0)