uno / tests /test_rules.py
cacodex's picture
Fix auto draw and room deep links
9f14abf verified
Raw
History Blame Contribute Delete
7.08 kB
import asyncio
import pytest
from app import Card, GameError, Player, Room, create_deck_for_tests, rooms, run_auto_draw
def make_room(player_count=3):
players = [
Player(id=f"p{i}", name=f"P{i}", token=f"t{i}", connected=True)
for i in range(player_count)
]
return Room(code="TEST", host_player_id="p0", total_players=player_count, players=players)
def test_deck_has_108_cards():
deck = create_deck_for_tests()
assert len(deck) == 108
assert sum(1 for c in deck if c.color == "wild" and c.value == "wild") == 4
assert sum(1 for c in deck if c.color == "wild" and c.value == "wild_draw4") == 4
def test_start_game_with_15_players_is_legal():
room = make_room(15)
event = room.start_game("p0")
assert event["type"] == "game_started"
assert room.phase == "playing"
assert all(len(p.hand) == 7 for p in room.players)
assert len(room.discard_pile) == 1
assert room.discard_pile[-1].color != "wild"
assert len(room.deck) == 2
def test_reshuffle_keeps_top_discard():
room = make_room()
top = Card("top", "red", "5")
old = Card("old", "blue", "7")
room.discard_pile = [old, top]
room.deck = []
room.reshuffle_deck()
assert room.discard_pile == [top]
assert room.deck == [old]
def test_normal_play_rules_and_draw_stacking():
room = make_room()
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "3")]
assert room.is_valid_play(Card("same_color", "red", "9"))
assert room.is_valid_play(Card("same_value", "blue", "3"))
assert room.is_valid_play(Card("wild", "wild", "wild"))
assert not room.is_valid_play(Card("bad", "green", "8"))
room.pending_draw = 2
room.discard_pile = [Card("draw_top", "red", "draw2")]
assert room.is_valid_play(Card("stack2", "blue", "draw2"))
assert room.is_valid_play(Card("stack4", "wild", "wild_draw4"))
assert not room.is_valid_play(Card("number", "red", "3"))
def test_strict_jump_in_requires_color_and_value():
room = make_room()
room.phase = "playing"
room.discard_pile = [Card("top", "red", "7")]
assert room.is_strict_jump_in(Card("same", "red", "7"))
assert not room.is_strict_jump_in(Card("value_only", "blue", "7"))
assert not room.is_strict_jump_in(Card("color_only", "red", "8"))
room.discard_pile = [Card("wild_top", "wild", "wild_draw4")]
assert room.is_strict_jump_in(Card("wild_same", "wild", "wild_draw4"))
assert not room.is_strict_jump_in(Card("wild_other", "wild", "wild"))
def test_jump_in_interrupts_and_continues_from_jumper():
room = make_room(4)
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
room.players[2].hand = [Card("jump", "red", "5")]
room.play_card("p2", "jump", expected_top_card_id="top", expected_version=0)
assert room.current_player_id == "p3"
assert room.discard_pile[-1].id == "jump"
def test_jump_in_with_reverse_uses_jumper_as_turn_source():
room = make_room(4)
room.phase = "playing"
room.current_player_id = "p0"
room.direction = 1
room.current_color = "green"
room.discard_pile = [Card("top", "green", "reverse")]
room.players[2].hand = [Card("jump", "green", "reverse")]
room.play_card("p2", "jump", expected_top_card_id="top", expected_version=0)
assert room.direction == -1
assert room.current_player_id == "p1"
def test_second_simultaneous_jump_in_revalidates_top_card():
room = make_room(4)
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
room.players[2].hand = [Card("jump1", "red", "5")]
room.players[3].hand = [Card("jump2", "red", "5")]
room.play_card("p2", "jump1", expected_top_card_id="top", expected_version=0)
with pytest.raises(GameError):
room.version += 1
room.play_card("p3", "jump2", expected_top_card_id="top", expected_version=0)
def test_bot_jump_in_revalidation_shape():
room = make_room(4)
room.players[2].is_bot = True
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
bot_card = Card("bot_jump", "red", "5")
room.players[2].hand = [bot_card]
assert any(room.is_strict_jump_in(card) for card in room.players[2].hand)
room.discard_pile.append(Card("changed", "blue", "9"))
assert not any(room.is_strict_jump_in(card) for card in room.players[2].hand)
def test_auto_draw_no_playable_human_draws_and_advances():
room = make_room()
room.code = "AUTO1"
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
room.players[0].hand = [Card("bad", "green", "8")]
room.players[1].hand = [Card("p1_playable", "red", "1")]
room.deck = [Card("drawn", "blue", "9")]
rooms[room.code] = room
try:
asyncio.run(run_auto_draw(room.code, "p0", room.version, "top", "red", 0, delay=0))
assert [card.id for card in room.players[0].hand] == ["bad", "drawn"]
assert room.current_player_id == "p1"
assert room.version == 1
finally:
rooms.pop(room.code, None)
def test_auto_draw_takes_pending_penalty_and_clears_it():
room = make_room()
room.code = "AUTO2"
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.pending_draw = 2
room.discard_pile = [Card("top", "red", "draw2")]
room.players[0].hand = [Card("bad", "green", "8")]
room.players[1].hand = [Card("p1_playable", "red", "1")]
room.deck = [Card("drawn1", "blue", "9"), Card("drawn2", "yellow", "1")]
rooms[room.code] = room
try:
asyncio.run(run_auto_draw(room.code, "p0", room.version, "top", "red", 2, delay=0))
assert len(room.players[0].hand) == 3
assert room.pending_draw == 0
assert room.current_player_id == "p1"
finally:
rooms.pop(room.code, None)
def test_auto_draw_revalidates_before_drawing():
room = make_room()
room.code = "AUTO3"
room.phase = "playing"
room.current_player_id = "p0"
room.current_color = "red"
room.discard_pile = [Card("top", "red", "5")]
room.players[0].hand = [Card("playable", "red", "8")]
room.deck = [Card("drawn", "blue", "9")]
rooms[room.code] = room
try:
asyncio.run(run_auto_draw(room.code, "p0", room.version, "top", "red", 0, delay=0))
assert [card.id for card in room.players[0].hand] == ["playable"]
assert room.current_player_id == "p0"
room.players[0].hand = [Card("bad", "green", "8")]
room.version += 1
asyncio.run(run_auto_draw(room.code, "p0", 0, "top", "red", 0, delay=0))
assert [card.id for card in room.players[0].hand] == ["bad"]
assert room.current_player_id == "p0"
finally:
rooms.pop(room.code, None)