321 lines
10 KiB
Python
321 lines
10 KiB
Python
import pytest
|
|
import json
|
|
from sqlmodel import SQLModel, create_engine, Session
|
|
from pirats import cards
|
|
from pirats import crud
|
|
from pirats.models import Game, Player, Obstacle
|
|
|
|
# In-memory database for testing
|
|
@pytest.fixture(name="session")
|
|
def session_fixture():
|
|
engine = create_engine("sqlite://", connect_args={"check_same_thread": False})
|
|
SQLModel.metadata.create_all(engine)
|
|
with Session(engine) as session:
|
|
yield session
|
|
|
|
def test_card_parsing():
|
|
c_10d = cards.parse_card("10D")
|
|
assert c_10d["value"] == "10"
|
|
assert c_10d["suit"] == "D"
|
|
assert c_10d["color"] == "red"
|
|
assert c_10d["numeric_value"] == 10
|
|
assert not c_10d["is_joker"]
|
|
|
|
c_ac = cards.parse_card("AC")
|
|
assert c_ac["value"] == "A"
|
|
assert c_ac["suit"] == "C"
|
|
assert c_ac["color"] == "black"
|
|
assert c_ac["numeric_value"] == 1
|
|
|
|
c_kh = cards.parse_card("KH")
|
|
assert c_kh["value"] == "K"
|
|
assert c_kh["numeric_value"] == 13
|
|
|
|
c_joker = cards.parse_card("Joker1")
|
|
assert c_joker["is_joker"]
|
|
assert c_joker["color"] == "black"
|
|
|
|
def test_obstacle_info():
|
|
obs = cards.get_obstacle_info("7C")
|
|
assert obs["title"] == "Lustful Bean Whale"
|
|
assert "bean" in obs["description"].lower()
|
|
assert obs["initial_value"] == 7
|
|
assert obs["color"] == "black"
|
|
|
|
def test_game_creation(session):
|
|
game = crud.create_game(session)
|
|
assert game.id is not None
|
|
assert game.phase == "lobby"
|
|
assert len(crud.get_game_deck(game)) == 54
|
|
|
|
def test_character_creation_and_swap(session):
|
|
game = crud.create_game(session)
|
|
p1 = crud.add_player(session, game.id, "Carlos", is_creator=True)
|
|
p2 = crud.add_player(session, game.id, "Barry")
|
|
p3 = crud.add_player(session, game.id, "Jaspar")
|
|
|
|
session.refresh(game)
|
|
assert len(game.players) == 3
|
|
|
|
# Fill basic details
|
|
p1.avatar_look = "Bandana"
|
|
p2.avatar_look = "Eyepatch"
|
|
p3.avatar_look = "Pegleg"
|
|
session.add_all([p1, p2, p3])
|
|
session.commit()
|
|
|
|
# Submit techniques
|
|
crud.submit_techniques(session, p1.id, "Carlos Strike", "Hide", "Roll")
|
|
crud.submit_techniques(session, p2.id, "Barry Leap", "Bite", "Scream")
|
|
crud.submit_techniques(session, p3.id, "Jaspar Blast", "Shoot", "Run")
|
|
|
|
session.refresh(game)
|
|
# The swap should have automatically triggered because all 3 submitted
|
|
assert game.phase == "swap_techniques"
|
|
|
|
session.refresh(p1)
|
|
session.refresh(p2)
|
|
session.refresh(p3)
|
|
|
|
techs_p1 = json.loads(p1.swapped_techniques)
|
|
assert len(techs_p1) == 3
|
|
# Check that Carlos didn't get any of his own techniques
|
|
assert "Carlos Strike" not in techs_p1
|
|
|
|
# Now assign to face cards
|
|
crud.assign_face_card_techniques(session, p1.id, techs_p1[0], techs_p1[1], techs_p1[2])
|
|
crud.assign_face_card_techniques(session, p2.id, "T2", "T3", "T4")
|
|
crud.assign_face_card_techniques(session, p3.id, "T5", "T6", "T7")
|
|
|
|
session.refresh(game)
|
|
# All are ready, should transition to scene_setup and assign starting ranks!
|
|
assert game.phase == "scene_setup"
|
|
|
|
session.refresh(p1)
|
|
session.refresh(p2)
|
|
session.refresh(p3)
|
|
|
|
ranks = [p1.rank, p2.rank, p3.rank]
|
|
assert 3 in ranks
|
|
assert 1 in ranks
|
|
assert 2 in ranks
|
|
|
|
roles = [p1.role, p2.role, p3.role]
|
|
assert "deep" in roles
|
|
assert "pirat" in roles
|
|
|
|
def test_scene_start_and_challenges(session):
|
|
game = crud.create_game(session)
|
|
p1 = crud.add_player(session, game.id, "P1", is_creator=True)
|
|
p2 = crud.add_player(session, game.id, "P2")
|
|
|
|
# Bypass char creation for quick scene test
|
|
p1.rank = 3
|
|
p1.role = "deep"
|
|
p1.previous_role = "deep"
|
|
|
|
p2.rank = 1
|
|
p2.role = "pirat"
|
|
p2.previous_role = "pirat"
|
|
|
|
session.add_all([p1, p2])
|
|
session.commit()
|
|
|
|
# Confirm setup (1 Deep, 1 Pi-Rat)
|
|
success, msg = crud.confirm_scene_setup(session, game.id)
|
|
assert success
|
|
|
|
session.refresh(game)
|
|
assert game.phase == "scene"
|
|
assert len(game.obstacles) == 2 # Deeps (1) + 1 = 2 obstacles
|
|
|
|
obs_list = game.obstacles
|
|
obs = obs_list[0]
|
|
|
|
# Give p2 a known hand for testing
|
|
p2.hand_cards = json.dumps(["10D", "2H", "JS", "Joker1"])
|
|
session.add(p2)
|
|
session.commit()
|
|
|
|
# Play card: 10D (value 10) vs Obstacle. Let's make sure obstacle value is less than 10.
|
|
# Set obstacle value to 5
|
|
obs.current_value = 5
|
|
session.add(obs)
|
|
session.commit()
|
|
|
|
# Play 10D (red) on obstacle. Let's check original obstacle color.
|
|
# If original obstacle was red, we expect a card draw.
|
|
orig_is_red = cards.parse_card(obs.original_card)["color"] == "red"
|
|
deck_before = len(crud.get_game_deck(game))
|
|
|
|
ok, msg, res = crud.play_card_on_obstacle(session, p2.id, obs.id, "10D")
|
|
assert ok
|
|
assert res["success"]
|
|
assert "Success" in res["details"]
|
|
|
|
session.refresh(p2)
|
|
session.refresh(obs)
|
|
# Obstacle current value should update to 10
|
|
assert obs.current_value == 10
|
|
|
|
# Check if hand size matches card draw rule
|
|
# 10D was played (-1 card). If colors matched, drew 1 back (+1 card).
|
|
hand = crud.get_player_hand(p2)
|
|
if orig_is_red:
|
|
assert len(hand) == 4
|
|
assert res["drew_card"] is not None
|
|
else:
|
|
assert len(hand) == 3
|
|
assert res["drew_card"] is None
|
|
|
|
def test_secret_technique_auto_success(session):
|
|
game = crud.create_game(session)
|
|
p1 = crud.add_player(session, game.id, "P1")
|
|
p1.role = "pirat"
|
|
p1.rank = 2
|
|
p1.tech_jack = "Pocket Sand"
|
|
p1.hand_cards = json.dumps(["JS"])
|
|
session.add(p1)
|
|
|
|
obs = Obstacle(
|
|
game_id=game.id,
|
|
original_card="10C",
|
|
suit="C",
|
|
title="Knights",
|
|
current_value=10
|
|
)
|
|
session.add(obs)
|
|
session.commit()
|
|
|
|
# Play Jack. J is a face card -> auto success!
|
|
ok, msg, res = crud.play_card_on_obstacle(session, p1.id, obs.id, "JS")
|
|
assert ok
|
|
assert res["success"]
|
|
assert res["is_technique"]
|
|
assert res["tech_name"] == "Pocket Sand"
|
|
|
|
def test_joker_play(session):
|
|
game = crud.create_game(session)
|
|
# Remove Joker1 and 10C from deck to prevent flakiness
|
|
deck = json.loads(game.deck_cards)
|
|
if "Joker1" in deck:
|
|
deck.remove("Joker1")
|
|
if "10C" in deck:
|
|
deck.remove("10C")
|
|
game.deck_cards = json.dumps(deck)
|
|
session.add(game)
|
|
|
|
p1 = crud.add_player(session, game.id, "P1")
|
|
p1.role = "pirat"
|
|
p1.hand_cards = json.dumps(["Joker1"])
|
|
session.add(p1)
|
|
|
|
obs = Obstacle(
|
|
game_id=game.id,
|
|
original_card="10C",
|
|
suit="C",
|
|
title="Knights",
|
|
current_value=10
|
|
)
|
|
session.add(obs)
|
|
session.commit()
|
|
|
|
# Play Joker
|
|
ok, msg, res = crud.play_card_on_obstacle(session, p1.id, obs.id, "Joker1")
|
|
assert ok
|
|
assert res["is_joker"]
|
|
|
|
# Verify obstacle was deleted and replaced
|
|
session.refresh(game)
|
|
assert len(game.obstacles) == 1
|
|
assert game.obstacles[0].original_card != "10C"
|
|
|
|
def test_obstacle_success_count(session):
|
|
game = crud.create_game(session)
|
|
obs = Obstacle(
|
|
game_id=game.id,
|
|
original_card="10C",
|
|
suit="C",
|
|
title="Knights",
|
|
current_value=10,
|
|
played_cards=json.dumps([
|
|
{"card": "9H", "player_id": "p1", "player_name": "P1", "success": True},
|
|
{"card": "8S", "player_id": "p2", "player_name": "P2", "success": False},
|
|
{"card": "AH", "player_id": "p1", "player_name": "P1", "success": True}
|
|
])
|
|
)
|
|
session.add(obs)
|
|
session.commit()
|
|
|
|
assert obs.success_count == 2
|
|
|
|
def test_non_deep_player_treatment(session):
|
|
game = crud.create_game(session)
|
|
p1 = crud.add_player(session, game.id, "Captain Barnaby")
|
|
p2 = crud.add_player(session, game.id, "Crewmate Pip")
|
|
|
|
# p1 is deep, p2 is None (non-deep)
|
|
p1.role = "deep"
|
|
p2.role = None
|
|
session.add_all([p1, p2])
|
|
session.commit()
|
|
|
|
# Check captain status: p2 should be captain because p2 is the only non-deep player
|
|
assert crud.is_player_captain(p2, game.players)
|
|
assert not crud.is_player_captain(p1, game.players)
|
|
|
|
# Check hand size: p2 should participate in hand size calculations and get Captain privileges (+1)
|
|
assert crud.calculate_max_hand_size(p2, game.players) == 4
|
|
|
|
def test_set_role_endpoint():
|
|
from fastapi.testclient import TestClient
|
|
from sqlalchemy.pool import StaticPool
|
|
from sqlmodel import SQLModel, create_engine, Session
|
|
from pirats.main import app
|
|
from pirats import crud
|
|
|
|
# Setup in-memory DB with StaticPool to share connection across threads
|
|
engine = create_engine(
|
|
"sqlite://",
|
|
connect_args={"check_same_thread": False},
|
|
poolclass=StaticPool,
|
|
)
|
|
SQLModel.metadata.create_all(engine)
|
|
session = Session(engine)
|
|
|
|
# Create game and player
|
|
game = crud.create_game(session)
|
|
player = crud.add_player(session, game.id, "TestPirate")
|
|
game.phase = "scene_setup"
|
|
session.add(game)
|
|
session.commit()
|
|
|
|
def get_session_override():
|
|
yield session
|
|
|
|
# Dynamically find the get_session function object used by the route
|
|
get_session_func = None
|
|
for route in app.routes:
|
|
if route.path == "/game/{game_id}/player/{player_id}/set-role":
|
|
for dep in route.dependant.dependencies:
|
|
if dep.name == "db":
|
|
get_session_func = dep.call
|
|
break
|
|
if get_session_func:
|
|
break
|
|
|
|
assert get_session_func is not None, "Could not find get_session dependency in route"
|
|
app.dependency_overrides[get_session_func] = get_session_override
|
|
client = TestClient(app)
|
|
|
|
try:
|
|
response = client.post(f"/game/{game.id}/player/{player.id}/set-role?role=pirat")
|
|
assert response.status_code == 200
|
|
# The response should contain the rendered scene setup template
|
|
assert "Play Pi-Rat" in response.text
|
|
# The database should be updated
|
|
session.refresh(player)
|
|
assert player.role == "pirat"
|
|
finally:
|
|
app.dependency_overrides.clear()
|