Fable will fix it! Pt 2

This commit is contained in:
2026-06-11 21:32:00 -07:00
parent a7cab7bcb6
commit a074ff77b1
44 changed files with 1547 additions and 4505 deletions

View File

@@ -3,7 +3,7 @@ import json
from sqlmodel import SQLModel, create_engine, Session
from pirats import cards
from pirats import crud
from pirats.models import Game, Player, Obstacle
from pirats.models import Obstacle
# In-memory database for testing
@pytest.fixture(name="session")
@@ -201,7 +201,6 @@ def test_assistant_does_not_draw(session):
ok, msg = crud.create_challenge(session, game.id, deep.id, p2.id, [obs.id])
assert ok
session.refresh(game)
challenge = game.challenges[0]
# p3 assists: plays a color-matching card but must NOT draw back
ok, msg, res = crud.play_challenge_card(session, p3.id, obs.id, helper_card)
@@ -409,7 +408,6 @@ def test_create_game_endpoint():
from sqlalchemy.pool import StaticPool
from sqlmodel import SQLModel, create_engine, Session, select
from pirats.main import app
from pirats import crud
# Setup in-memory DB
engine = create_engine(
@@ -963,7 +961,7 @@ def test_submit_techniques_rejects_collisions(session):
def test_roll_new_character_smell_name_normalization(session):
game = crud.create_game(session)
p1 = crud.add_player(session, game.id, "P1")
p2 = crud.add_player(session, game.id, "P2")
crud.add_player(session, game.id, "P2")
p1.is_dead = True
session.add(p1)
session.commit()
@@ -1009,3 +1007,115 @@ def test_roll_new_character_avoids_technique_collisions(session):
session.refresh(p1)
recruit_techs = {p1.tech_jack, p1.tech_queen, p1.tech_king}
assert recruit_techs == set(original[3:6])
def test_reshuffle_keeps_open_pvp_temp_card_out_of_deck(session):
game, deep, (p2, p3) = make_scene_game(session, num_pirats=2)
p2.hand_cards = json.dumps(["7C"])
session.add(p2)
session.commit()
ok, msg = crud.create_pvp_challenge(session, game.id, p2.id, p3.id, "7C")
assert ok, msg
# A mid-duel reshuffle must not pull the temporary Obstacle back into the deck
session.refresh(game)
crud.reshuffle_discard_pile(session, game)
assert "7C" not in crud.get_game_deck(game)
# Once the duel resolves, the card is discarded and reshuffles normally
duel = [c for c in game.challenges if c.challenge_type == "pvp"][0]
p3.hand_cards = json.dumps(["9C"])
session.add(p3)
session.commit()
ok, _, _ = crud.play_pvp_defense(session, duel.id, p3.id, "9C")
assert ok
crud.reshuffle_discard_pile(session, game)
assert "7C" in crud.get_game_deck(game)
def test_late_joiner_is_dealt_a_hand(session):
game, deep, (p2,) = make_scene_game(session)
assert game.phase == "scene"
newbie = crud.add_player(session, game.id, "Stowaway")
assert newbie.role == "pirat"
session.refresh(game)
expected = crud.calculate_max_hand_size(newbie, game.players, game.captain_player_id)
assert expected > 0
assert len(crud.get_player_hand(newbie)) == expected
def test_technique_suggestions_endpoint():
from fastapi.testclient import TestClient
from pirats.main import app
client = TestClient(app, base_url="http://testserver")
response = client.get("/api/suggestions/techniques")
assert response.status_code == 200
techs = response.json()["techniques"]
assert techs == cards.TECHNIQUE_SUGGESTIONS
assert len(techs) >= 100
def test_admin_key_returned_at_create_but_hidden_from_state():
from fastapi.testclient import TestClient
from sqlalchemy.pool import StaticPool
from sqlmodel import SQLModel, create_engine, Session
from pirats.main import app
engine = create_engine(
"sqlite://",
connect_args={"check_same_thread": False},
poolclass=StaticPool,
)
SQLModel.metadata.create_all(engine)
session = Session(engine)
def get_session_override():
yield session
from pirats.database import get_session
app.dependency_overrides[get_session] = get_session_override
client = TestClient(app, base_url="http://testserver")
try:
created = client.post("/api/game", data={"crew_name": "Leak Test"}).json()
assert created["admin_key"]
game_id = created["id"]
player_id = client.post(f"/api/game/{game_id}/join", data={"name": "Carlos"}).json()["id"]
state = client.get(f"/api/game/{game_id}/player/{player_id}/state").json()
assert "admin_key" not in state["game"]
finally:
app.dependency_overrides.clear()
def test_event_log_pagination(session):
from pirats.models import GameEvent
game = crud.create_game(session)
for i in range(120):
# Explicit timestamps keep ordering deterministic
session.add(GameEvent(game_id=game.id, timestamp=1000.0 + i, message=f"event {i}", kind="info"))
session.commit()
# Newest page
events, have_more = crud.get_events_page(session, game.id, limit=50)
assert have_more
assert len(events) == 50
assert events[0].message == "event 70"
assert events[-1].message == "event 119"
# Page back from the oldest loaded event
older, have_more = crud.get_events_page(session, game.id, before=events[0].timestamp, limit=50)
assert have_more
assert older[0].message == "event 20"
assert older[-1].message == "event 69"
# Final page is short and reports no more history
oldest, have_more = crud.get_events_page(session, game.id, before=older[0].timestamp, limit=50)
assert not have_more
assert len(oldest) == 20
assert oldest[0].message == "event 0"
assert oldest[-1].message == "event 19"
# Exact page boundary still reports no remaining history
exact, have_more = crud.get_events_page(session, game.id, before=1020.0, limit=20)
assert not have_more
assert [e.message for e in exact] == [f"event {i}" for i in range(20)]