Skip to content

Commit b81d7ff

Browse files
committed
test(lua): cover sort routes and fixtures
Add six tests for the sort endpoint, covering every route: - SUCCESS on sort-by-rank: the hand is a permutation of the input (no cards lost or created) and strictly descending by (rank, suit). - SUCCESS on sort-by-suit: same permutation contract, descending by (suit, rank). - INVALID_STATE when not in SELECTING_HAND (SHOP fixture). - BAD_REQUEST on missing 'by' (validator rejects). - BAD_REQUEST on non-string 'by' (validator type check). - BAD_REQUEST on 'by' not in {"rank","suit"} (manual enum gate). Tests are deterministic: completion is event-based (trigger='condition' polling), so no flaky marks are used. Two fixtures under "sort": an 8-card SELECTING_HAND hand (pre-scrambled via rearrange so the sort actually moves cards), and a SHOP state reached by setting chips, playing a single card, and cashing out. Closes #213.
1 parent de9d5c3 commit b81d7ff

2 files changed

Lines changed: 182 additions & 0 deletions

File tree

tests/fixtures/fixtures.json

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5685,5 +5685,65 @@
56855685
"params": {}
56865686
}
56875687
]
5688+
},
5689+
"sort": {
5690+
"state-SELECTING_HAND--hand.count-8": [
5691+
{
5692+
"method": "menu",
5693+
"params": {}
5694+
},
5695+
{
5696+
"method": "start",
5697+
"params": {
5698+
"deck": "b_red",
5699+
"stake": "stake_white",
5700+
"seed": "TEST123"
5701+
}
5702+
},
5703+
{
5704+
"method": "select",
5705+
"params": {}
5706+
},
5707+
{
5708+
"method": "rearrange",
5709+
"params": {
5710+
"hand": [7, 6, 5, 4, 3, 2, 1, 0]
5711+
}
5712+
}
5713+
],
5714+
"state-SHOP": [
5715+
{
5716+
"method": "menu",
5717+
"params": {}
5718+
},
5719+
{
5720+
"method": "start",
5721+
"params": {
5722+
"deck": "b_red",
5723+
"stake": "stake_white",
5724+
"seed": "TEST123"
5725+
}
5726+
},
5727+
{
5728+
"method": "select",
5729+
"params": {}
5730+
},
5731+
{
5732+
"method": "set",
5733+
"params": {
5734+
"chips": 1000
5735+
}
5736+
},
5737+
{
5738+
"method": "play",
5739+
"params": {
5740+
"cards": [0]
5741+
}
5742+
},
5743+
{
5744+
"method": "cash_out",
5745+
"params": {}
5746+
}
5747+
]
56885748
}
56895749
}

tests/lua/endpoints/test_sort.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
"""Tests for src/lua/endpoints/sort.lua"""
2+
3+
import httpx
4+
5+
from tests.lua.conftest import (
6+
api,
7+
assert_error_response,
8+
assert_gamestate_response,
9+
load_fixture,
10+
)
11+
12+
# ---------------------------------------------------------------------------
13+
# Sort comparator helpers
14+
# ---------------------------------------------------------------------------
15+
16+
RANK = {
17+
"2": 0,
18+
"3": 1,
19+
"4": 2,
20+
"5": 3,
21+
"6": 4,
22+
"7": 5,
23+
"8": 6,
24+
"9": 7,
25+
"T": 8,
26+
"J": 9,
27+
"Q": 10,
28+
"K": 11,
29+
"A": 12,
30+
}
31+
SUIT = {"D": 0, "C": 1, "H": 2, "S": 3} # Diamonds < Clubs < Hearts < Spades
32+
33+
34+
def _key_rank(card: dict) -> tuple[int, int]:
35+
"""Sort-by-rank key: (rank, suit) — primary rank desc, tiebreak suit desc."""
36+
return (RANK[card["value"]["rank"]], SUIT[card["value"]["suit"]])
37+
38+
39+
def _key_suit(card: dict) -> tuple[int, int]:
40+
"""Sort-by-suit key: (suit, rank) — primary suit desc, tiebreak rank desc."""
41+
return (SUIT[card["value"]["suit"]], RANK[card["value"]["rank"]])
42+
43+
44+
def _is_descending_by(cards: list[dict], key) -> bool:
45+
"""True if `cards` is strictly descending by `key`."""
46+
ks = [key(c) for c in cards]
47+
return all(ks[i] > ks[i + 1] for i in range(len(ks) - 1))
48+
49+
50+
class TestSortEndpoint:
51+
"""Test sort endpoint functionality."""
52+
53+
def test_sort_by_rank(self, client: httpx.Client) -> None:
54+
"""Sort by rank reorders hand descending A>K>...>2, suit Spades>...>Diamonds."""
55+
before = load_fixture(client, "sort", "state-SELECTING_HAND--hand.count-8")
56+
assert before["state"] == "SELECTING_HAND"
57+
assert before["hand"]["count"] == 8
58+
before_ids = {card["id"] for card in before["hand"]["cards"]}
59+
60+
response = api(client, "sort", {"by": "rank"})
61+
after = assert_gamestate_response(response, state="SELECTING_HAND")
62+
63+
after_cards = after["hand"]["cards"]
64+
after_ids = {card["id"] for card in after_cards}
65+
# Permutation contract: no cards lost or created
66+
assert after_ids == before_ids
67+
# Sort contract: descending by (rank, suit)
68+
assert _is_descending_by(after_cards, _key_rank)
69+
70+
def test_sort_by_suit(self, client: httpx.Client) -> None:
71+
"""Sort by suit groups Spades>Hearts>Clubs>Diamonds, rank desc within suit."""
72+
before = load_fixture(client, "sort", "state-SELECTING_HAND--hand.count-8")
73+
assert before["state"] == "SELECTING_HAND"
74+
assert before["hand"]["count"] == 8
75+
before_ids = {card["id"] for card in before["hand"]["cards"]}
76+
77+
response = api(client, "sort", {"by": "suit"})
78+
after = assert_gamestate_response(response, state="SELECTING_HAND")
79+
80+
after_cards = after["hand"]["cards"]
81+
after_ids = {card["id"] for card in after_cards}
82+
# Permutation contract: no cards lost or created
83+
assert after_ids == before_ids
84+
# Sort contract: descending by (suit, rank)
85+
assert _is_descending_by(after_cards, _key_suit)
86+
87+
def test_sort_wrong_state(self, client: httpx.Client) -> None:
88+
"""sort requires SELECTING_HAND state."""
89+
gamestate = load_fixture(client, "sort", "state-SHOP")
90+
assert gamestate["state"] == "SHOP"
91+
assert_error_response(
92+
api(client, "sort", {"by": "rank"}),
93+
"INVALID_STATE",
94+
"requires one of these states: SELECTING_HAND",
95+
)
96+
97+
def test_sort_missing_by(self, client: httpx.Client) -> None:
98+
"""Missing 'by' param is rejected by the schema validator."""
99+
load_fixture(client, "sort", "state-SELECTING_HAND--hand.count-8")
100+
assert_error_response(
101+
api(client, "sort", {}),
102+
"BAD_REQUEST",
103+
"Missing required field 'by'",
104+
)
105+
106+
def test_sort_by_wrong_type(self, client: httpx.Client) -> None:
107+
"""Non-string 'by' param is rejected by the schema validator."""
108+
load_fixture(client, "sort", "state-SELECTING_HAND--hand.count-8")
109+
assert_error_response(
110+
api(client, "sort", {"by": 5}),
111+
"BAD_REQUEST",
112+
"Field 'by' must be of type string",
113+
)
114+
115+
def test_sort_by_invalid_enum(self, client: httpx.Client) -> None:
116+
"""'by' must be exactly 'rank' or 'suit' (manual enum check)."""
117+
load_fixture(client, "sort", "state-SELECTING_HAND--hand.count-8")
118+
assert_error_response(
119+
api(client, "sort", {"by": "value"}),
120+
"BAD_REQUEST",
121+
'must be "rank" or "suit"',
122+
)

0 commit comments

Comments
 (0)