From 388afa585a0f507cc8166d3ef94241808587ec9b Mon Sep 17 00:00:00 2001 From: Erik Vroon Date: Tue, 10 Sep 2024 20:32:59 +0200 Subject: [PATCH] Make primary key typing stricter (#904) --- backend/bracket/cronjobs/scheduling.py | 3 +- backend/bracket/logic/planning/matches.py | 8 +- backend/bracket/logic/planning/rounds.py | 14 ++-- backend/bracket/logic/scheduling/builder.py | 16 ++-- .../bracket/logic/scheduling/elimination.py | 9 +-- .../scheduling/handle_stage_activation.py | 2 +- .../bracket/logic/scheduling/ladder_teams.py | 8 +- .../bracket/logic/scheduling/round_robin.py | 3 +- .../logic/scheduling/upcoming_matches.py | 3 +- backend/bracket/logic/subscriptions.py | 3 +- backend/bracket/logic/tournaments.py | 3 +- backend/bracket/models/db/club.py | 7 +- backend/bracket/models/db/court.py | 7 +- backend/bracket/models/db/match.py | 9 ++- backend/bracket/models/db/player.py | 7 +- backend/bracket/models/db/player_x_team.py | 5 +- backend/bracket/models/db/round.py | 15 ++-- backend/bracket/models/db/stage.py | 7 +- backend/bracket/models/db/stage_item.py | 5 +- .../bracket/models/db/stage_item_inputs.py | 2 +- backend/bracket/models/db/team.py | 24 ++---- backend/bracket/models/db/tournament.py | 7 +- backend/bracket/models/db/user.py | 9 ++- backend/bracket/models/db/user_x_club.py | 7 +- backend/bracket/routes/auth.py | 8 +- backend/bracket/routes/clubs.py | 7 +- backend/bracket/routes/matches.py | 2 +- backend/bracket/routes/rounds.py | 7 +- backend/bracket/routes/teams.py | 18 +++-- backend/bracket/routes/tournaments.py | 7 +- backend/bracket/routes/users.py | 20 ++--- backend/bracket/sql/clubs.py | 2 +- backend/bracket/sql/rounds.py | 4 +- backend/bracket/sql/users.py | 8 +- backend/bracket/utils/db_init.py | 55 +++++++------ backend/bracket/utils/dummy_records.py | 76 +++++++++--------- backend/cli.py | 6 +- .../api/activate_next_stage_test.py | 21 +++-- .../api/auto_scheduling_matches_test.py | 43 +++++++--- .../tests/integration_tests/api/clubs_test.py | 11 ++- .../integration_tests/api/matches_test.py | 9 +-- .../integration_tests/api/rankings_test.py | 3 +- .../api/rescheduling_matches_test.py | 7 +- .../api/scheduling_matches_test.py | 15 ++-- .../integration_tests/api/stages_test.py | 7 +- .../integration_tests/api/tournaments_test.py | 4 +- .../cronjobs/demo_user_deletion_test.py | 3 +- backend/tests/integration_tests/mocks.py | 8 +- backend/tests/integration_tests/sql.py | 80 ++++++++++--------- .../unit_tests/ranking_calculation_test.py | 44 ++++++++-- backend/tests/unit_tests/swiss_test.py | 22 +++-- 51 files changed, 367 insertions(+), 313 deletions(-) diff --git a/backend/bracket/cronjobs/scheduling.py b/backend/bracket/cronjobs/scheduling.py index db173a34..7986294e 100644 --- a/backend/bracket/cronjobs/scheduling.py +++ b/backend/bracket/cronjobs/scheduling.py @@ -7,7 +7,6 @@ from bracket.models.db.account import UserAccountType from bracket.sql.users import delete_user_and_owned_clubs, get_expired_demo_users from bracket.utils.asyncio import AsyncioTasksManager from bracket.utils.logging import logger -from bracket.utils.types import assert_some CronjobT = Callable[[], Awaitable[None]] @@ -21,7 +20,7 @@ async def delete_demo_accounts() -> None: for demo_user in demo_users: assert demo_user.account_type is UserAccountType.DEMO - user_id = assert_some(demo_user.id) + user_id = demo_user.id await delete_user_and_owned_clubs(user_id) diff --git a/backend/bracket/logic/planning/matches.py b/backend/bracket/logic/planning/matches.py index 38a255fc..b1ecf2b0 100644 --- a/backend/bracket/logic/planning/matches.py +++ b/backend/bracket/logic/planning/matches.py @@ -40,7 +40,7 @@ async def schedule_all_unscheduled_matches(tournament_id: TournamentId) -> None: for match in round_.matches: if match.start_time is None and match.position_in_schedule is None: await sql_reschedule_match_and_determine_duration_and_margin( - assert_some(match.id), + match.id, court.id, start_time, position_in_schedule, @@ -63,7 +63,7 @@ async def schedule_all_unscheduled_matches(tournament_id: TournamentId) -> None: if match.start_time is None and match.position_in_schedule is None: await sql_reschedule_match_and_determine_duration_and_margin( - assert_some(match.id), + match.id, courts[-1].id, start_time, position_in_schedule, @@ -92,7 +92,7 @@ async def reorder_matches_for_court( last_start_time = tournament.start_time for i, match_pos in enumerate(matches_this_court): await sql_reschedule_match_and_determine_duration_and_margin( - assert_some(match_pos.match.id), + match_pos.match.id, court_id, last_start_time, position_in_schedule=i, @@ -151,7 +151,7 @@ async def update_start_times_of_matches(tournament_id: TournamentId) -> None: scheduled_matches = get_scheduled_matches(stages) for court in courts: - await reorder_matches_for_court(tournament, scheduled_matches, assert_some(court.id)) + await reorder_matches_for_court(tournament, scheduled_matches, court.id) def get_scheduled_matches(stages: list[StageWithStageItems]) -> list[MatchPosition]: diff --git a/backend/bracket/logic/planning/rounds.py b/backend/bracket/logic/planning/rounds.py index 7ed46c30..9a99abdd 100644 --- a/backend/bracket/logic/planning/rounds.py +++ b/backend/bracket/logic/planning/rounds.py @@ -22,13 +22,9 @@ def get_active_and_next_rounds( active_round = next((round_ for round_ in stage_item.rounds if round_.is_active), None) def is_round_in_future(round_: RoundWithMatches) -> bool: - return ( - (assert_some(round_.id) > assert_some(active_round.id)) - if active_round is not None - else True - ) + return (round_.id > active_round.id) if active_round is not None else True - rounds_chronologically_sorted = sorted(stage_item.rounds, key=lambda r: assert_some(r.id)) + rounds_chronologically_sorted = sorted(stage_item.rounds, key=lambda r: r.id) next_round = next( (round_ for round_ in rounds_chronologically_sorted if is_round_in_future(round_)), None, @@ -53,7 +49,7 @@ async def schedule_all_matches_for_swiss_round( assert len(active_round.matches) <= len(courts) for i, match in enumerate(active_round.matches): - court_id = assert_some(courts[i].id) + court_id = courts[i].id last_match = ( next((m for m in matches_per_court[court_id][::-1] if m.match.id != match.id), None) if court_id in matches_per_court @@ -83,7 +79,7 @@ async def schedule_all_matches_for_swiss_round( ) rescheduling_operations.append( sql_reschedule_match_and_determine_duration_and_margin( - assert_some(last_match.match.id), + last_match.match.id, court_id, assert_some(last_match.match.start_time), assert_some(last_match.match.position_in_schedule), @@ -104,7 +100,7 @@ async def schedule_all_matches_for_swiss_round( rescheduling_operations.append( sql_reschedule_match_and_determine_duration_and_margin( - assert_some(match.id), court_id, start_time, pos_in_schedule, match, tournament + match.id, court_id, start_time, pos_in_schedule, match, tournament ) ) diff --git a/backend/bracket/logic/scheduling/builder.py b/backend/bracket/logic/scheduling/builder.py index 67dc402c..b6cb7616 100644 --- a/backend/bracket/logic/scheduling/builder.py +++ b/backend/bracket/logic/scheduling/builder.py @@ -8,7 +8,7 @@ from bracket.logic.scheduling.round_robin import ( build_round_robin_stage_item, get_number_of_rounds_to_create_round_robin, ) -from bracket.models.db.round import RoundToInsert +from bracket.models.db.round import RoundInsertable from bracket.models.db.stage_item import StageItem, StageType from bracket.models.db.stage_item_inputs import ( StageItemInputOptionFinal, @@ -19,7 +19,7 @@ from bracket.models.db.util import StageWithStageItems from bracket.sql.rounds import get_next_round_name, sql_create_round from bracket.sql.stage_items import get_stage_item from bracket.utils.id_types import StageId, TournamentId -from bracket.utils.types import assert_some +from tests.integration_tests.mocks import MOCK_NOW async def create_rounds_for_new_stage_item( @@ -38,16 +38,18 @@ async def create_rounds_for_new_stage_item( for _ in range(rounds_count): await sql_create_round( - RoundToInsert( - stage_item_id=assert_some(stage_item.id), - name=await get_next_round_name(tournament_id, assert_some(stage_item.id)), + RoundInsertable( + created=MOCK_NOW, + is_draft=False, + stage_item_id=stage_item.id, + name=await get_next_round_name(tournament_id, stage_item.id), ), ) async def build_matches_for_stage_item(stage_item: StageItem, tournament_id: TournamentId) -> None: await create_rounds_for_new_stage_item(tournament_id, stage_item) - stage_item_with_rounds = await get_stage_item(tournament_id, assert_some(stage_item.id)) + stage_item_with_rounds = await get_stage_item(tournament_id, stage_item.id) if stage_item_with_rounds is None: raise ValueError( @@ -73,7 +75,7 @@ def determine_available_inputs( teams: list[FullTeamWithPlayers], stages: list[StageWithStageItems], ) -> list[StageItemInputOptionTentative | StageItemInputOptionFinal]: - results_team_ids = [assert_some(team.id) for team in teams] + results_team_ids = [team.id for team in teams] results_tentative = [] for stage in stages: diff --git a/backend/bracket/logic/scheduling/elimination.py b/backend/bracket/logic/scheduling/elimination.py index 18951d18..50cff6ee 100644 --- a/backend/bracket/logic/scheduling/elimination.py +++ b/backend/bracket/logic/scheduling/elimination.py @@ -5,7 +5,6 @@ from bracket.sql.matches import sql_create_match from bracket.sql.rounds import get_rounds_for_stage_item from bracket.sql.tournaments import sql_get_tournament from bracket.utils.id_types import TournamentId -from bracket.utils.types import assert_some def determine_matches_first_round( @@ -18,7 +17,7 @@ def determine_matches_first_round( second_input = stage_item.inputs[i + 1] suggestions.append( MatchCreateBody( - round_id=assert_some(round_.id), + round_id=round_.id, court_id=None, team1_id=first_input.team_id, team1_winner_from_stage_item_id=first_input.winner_from_stage_item_id, @@ -51,7 +50,7 @@ def determine_matches_subsequent_round( suggestions.append( MatchCreateBody( - round_id=assert_some(round_.id), + round_id=round_.id, court_id=None, team1_id=None, team1_winner_from_stage_item_id=None, @@ -59,8 +58,8 @@ def determine_matches_subsequent_round( team2_id=None, team2_winner_from_stage_item_id=None, team2_winner_position=None, - team1_winner_from_match_id=assert_some(first_match.id), - team2_winner_from_match_id=assert_some(second_match.id), + team1_winner_from_match_id=first_match.id, + team2_winner_from_match_id=second_match.id, duration_minutes=tournament.duration_minutes, margin_minutes=tournament.margin_minutes, custom_duration_minutes=None, diff --git a/backend/bracket/logic/scheduling/handle_stage_activation.py b/backend/bracket/logic/scheduling/handle_stage_activation.py index 88cfd96c..dda7132c 100644 --- a/backend/bracket/logic/scheduling/handle_stage_activation.py +++ b/backend/bracket/logic/scheduling/handle_stage_activation.py @@ -62,7 +62,7 @@ async def set_team_ids_for_match( stage_item_x_team_rankings, ) - await sql_update_team_ids_for_match(assert_some(match.id), team1_id, team2_id) + await sql_update_team_ids_for_match(match.id, team1_id, team2_id) async def get_team_rankings_lookup_for_stage( diff --git a/backend/bracket/logic/scheduling/ladder_teams.py b/backend/bracket/logic/scheduling/ladder_teams.py index 49ab4631..d024ccc1 100644 --- a/backend/bracket/logic/scheduling/ladder_teams.py +++ b/backend/bracket/logic/scheduling/ladder_teams.py @@ -47,7 +47,7 @@ def get_number_of_teams_played_per_team( if isinstance(match, MatchWithDetailsDefinitive): for team in match.teams: if team.active and team.id not in excluded_team_ids: - result[assert_some(team.id)] += 1 + result[team.id] += 1 return result @@ -80,7 +80,7 @@ def get_possible_upcoming_matches_for_swiss( ) for team in teams_to_schedule: if team.id not in times_played_per_team: - times_played_per_team[assert_some(team.id)] = 0 + times_played_per_team[team.id] = 0 min_times_played = min(times_played_per_team.values()) if len(times_played_per_team) > 0 else 0 @@ -100,8 +100,8 @@ def get_possible_upcoming_matches_for_swiss( continue times_played_min = min( - times_played_per_team[assert_some(team1.id)], - times_played_per_team[assert_some(team2.id)], + times_played_per_team[team1.id], + times_played_per_team[team2.id], ) suggested_match = check_team_combination_adheres_to_filter( team1, team2, filter_, is_recommended=times_played_min <= min_times_played diff --git a/backend/bracket/logic/scheduling/round_robin.py b/backend/bracket/logic/scheduling/round_robin.py index f1560b90..2371257b 100644 --- a/backend/bracket/logic/scheduling/round_robin.py +++ b/backend/bracket/logic/scheduling/round_robin.py @@ -5,7 +5,6 @@ from bracket.models.db.util import StageItemWithRounds from bracket.sql.matches import sql_create_match from bracket.sql.tournaments import sql_get_tournament from bracket.utils.id_types import TournamentId -from bracket.utils.types import assert_some def get_round_robin_combinations(team_count: int) -> list[list[tuple[int, int]]]: @@ -48,7 +47,7 @@ async def build_round_robin_stage_item( team_1, team_2 = stage_item.inputs[team_1_id], stage_item.inputs[team_2_id] match = MatchCreateBody( - round_id=assert_some(round_.id), + round_id=round_.id, team1_id=team_1.team_id, team1_winner_from_stage_item_id=team_1.winner_from_stage_item_id, team1_winner_position=team_1.winner_position, diff --git a/backend/bracket/logic/scheduling/upcoming_matches.py b/backend/bracket/logic/scheduling/upcoming_matches.py index d51dbb63..91ccc488 100644 --- a/backend/bracket/logic/scheduling/upcoming_matches.py +++ b/backend/bracket/logic/scheduling/upcoming_matches.py @@ -9,7 +9,6 @@ from bracket.sql.rounds import get_rounds_for_stage_item from bracket.sql.stages import get_full_tournament_details from bracket.sql.teams import get_teams_with_members from bracket.utils.id_types import StageItemId, TournamentId -from bracket.utils.types import assert_some async def get_draft_round_in_stage_item( @@ -40,7 +39,7 @@ async def get_upcoming_matches_for_swiss_round( if not round_.is_draft: raise HTTPException(400, "There is no draft round, so no matches can be scheduled.") - rounds = await get_rounds_for_stage_item(tournament_id, assert_some(stage_item.id)) + rounds = await get_rounds_for_stage_item(tournament_id, stage_item.id) teams = await get_teams_with_members(tournament_id, only_active_teams=True) return get_possible_upcoming_matches_for_swiss(match_filter, rounds, teams) diff --git a/backend/bracket/logic/subscriptions.py b/backend/bracket/logic/subscriptions.py index bdbeb1c3..188fb83e 100644 --- a/backend/bracket/logic/subscriptions.py +++ b/backend/bracket/logic/subscriptions.py @@ -14,7 +14,6 @@ from bracket.sql.clubs import create_club from bracket.sql.rankings import sql_create_ranking from bracket.sql.tournaments import sql_create_tournament from bracket.utils.id_types import UserId -from bracket.utils.types import assert_some if TYPE_CHECKING: from bracket.models.db.user import UserBase @@ -80,7 +79,7 @@ async def setup_demo_account(user_id: UserId) -> None: tournament = TournamentBody( name="Demo Tournament", - club_id=assert_some(club_inserted.id), + club_id=club_inserted.id, start_time=datetime_utc.future(hours=1), dashboard_public=False, players_can_be_in_multiple_teams=False, diff --git a/backend/bracket/logic/tournaments.py b/backend/bracket/logic/tournaments.py index c035d9a5..26ed1f8b 100644 --- a/backend/bracket/logic/tournaments.py +++ b/backend/bracket/logic/tournaments.py @@ -9,7 +9,6 @@ from bracket.sql.stages import get_full_tournament_details, sql_delete_stage from bracket.sql.teams import sql_delete_teams_of_tournament from bracket.sql.tournaments import sql_delete_tournament, sql_get_tournament from bracket.utils.id_types import TournamentId -from bracket.utils.types import assert_some async def get_tournament_logo_path(tournament_id: TournamentId) -> str | None: @@ -36,7 +35,7 @@ async def sql_delete_tournament_completely(tournament_id: TournamentId) -> None: for stage_item in stage.stage_items: await sql_delete_stage_item(stage_item.id) - await sql_delete_stage(tournament_id, assert_some(stage.id)) + await sql_delete_stage(tournament_id, stage.id) for ranking in await get_all_rankings_in_tournament(tournament_id): await sql_delete_ranking(tournament_id, ranking.id) diff --git a/backend/bracket/models/db/club.py b/backend/bracket/models/db/club.py index cfde65da..0b7dbea8 100644 --- a/backend/bracket/models/db/club.py +++ b/backend/bracket/models/db/club.py @@ -4,12 +4,15 @@ from bracket.models.db.shared import BaseModelORM from bracket.utils.id_types import ClubId -class Club(BaseModelORM): - id: ClubId | None = None +class ClubInsertable(BaseModelORM): name: str created: datetime_utc +class Club(ClubInsertable): + id: ClubId + + class ClubCreateBody(BaseModelORM): name: str diff --git a/backend/bracket/models/db/court.py b/backend/bracket/models/db/court.py index 1cf4df4f..8c5de6d4 100644 --- a/backend/bracket/models/db/court.py +++ b/backend/bracket/models/db/court.py @@ -4,13 +4,16 @@ from bracket.models.db.shared import BaseModelORM from bracket.utils.id_types import CourtId, TournamentId -class Court(BaseModelORM): - id: CourtId | None = None +class CourtInsertable(BaseModelORM): name: str created: datetime_utc tournament_id: TournamentId +class Court(CourtInsertable): + id: CourtId + + class CourtBody(BaseModelORM): name: str diff --git a/backend/bracket/models/db/match.py b/backend/bracket/models/db/match.py index fd1adeca..4235494e 100644 --- a/backend/bracket/models/db/match.py +++ b/backend/bracket/models/db/match.py @@ -10,8 +10,7 @@ from bracket.utils.id_types import CourtId, MatchId, PlayerId, RoundId, StageIte from bracket.utils.types import assert_some -class MatchBase(BaseModelORM): - id: MatchId | None = None +class MatchBaseInsertable(BaseModelORM): created: datetime_utc start_time: datetime_utc | None = None duration_minutes: int @@ -32,7 +31,7 @@ class MatchBase(BaseModelORM): ) -class Match(MatchBase): +class MatchInsertable(MatchBaseInsertable): team1_id: TeamId | None = None team2_id: TeamId | None = None team1_winner_position: int | None = None @@ -49,6 +48,10 @@ class Match(MatchBase): return 1 if self.team1_score > self.team2_score else 0 +class Match(MatchInsertable): + id: MatchId + + class MatchWithDetails(Match): court: Court | None = None diff --git a/backend/bracket/models/db/player.py b/backend/bracket/models/db/player.py index cbf3189e..216d1047 100644 --- a/backend/bracket/models/db/player.py +++ b/backend/bracket/models/db/player.py @@ -7,8 +7,7 @@ from bracket.models.db.shared import BaseModelORM from bracket.utils.id_types import PlayerId, TournamentId -class Player(BaseModelORM): - id: PlayerId | None = None +class PlayerInsertable(BaseModelORM): active: bool name: str created: datetime_utc @@ -19,6 +18,10 @@ class Player(BaseModelORM): draws: int = 0 losses: int = 0 + +class Player(PlayerInsertable): + id: PlayerId + def __hash__(self) -> int: return self.id if self.id is not None else int(self.created.timestamp()) diff --git a/backend/bracket/models/db/player_x_team.py b/backend/bracket/models/db/player_x_team.py index 2b1cddf0..67d85546 100644 --- a/backend/bracket/models/db/player_x_team.py +++ b/backend/bracket/models/db/player_x_team.py @@ -1,8 +1,7 @@ from bracket.models.db.shared import BaseModelORM -from bracket.utils.id_types import PlayerId, PlayerXTeamId, TeamId +from bracket.utils.id_types import PlayerId, TeamId -class PlayerXTeam(BaseModelORM): - id: PlayerXTeamId | None = None +class PlayerXTeamInsertable(BaseModelORM): player_id: PlayerId team_id: TeamId diff --git a/backend/bracket/models/db/round.py b/backend/bracket/models/db/round.py index f1d076aa..42ecd135 100644 --- a/backend/bracket/models/db/round.py +++ b/backend/bracket/models/db/round.py @@ -4,15 +4,18 @@ from bracket.models.db.shared import BaseModelORM from bracket.utils.id_types import RoundId, StageItemId -class Round(BaseModelORM): - id: RoundId | None = None - stage_item_id: StageItemId +class RoundInsertable(BaseModelORM): created: datetime_utc + stage_item_id: StageItemId is_draft: bool is_active: bool = False name: str +class Round(RoundInsertable): + id: RoundId + + class RoundUpdateBody(BaseModelORM): name: str is_draft: bool @@ -22,9 +25,3 @@ class RoundUpdateBody(BaseModelORM): class RoundCreateBody(BaseModelORM): name: str | None = None stage_item_id: StageItemId - - -class RoundToInsert(RoundUpdateBody): - stage_item_id: StageItemId - is_draft: bool = False - is_active: bool = False diff --git a/backend/bracket/models/db/stage.py b/backend/bracket/models/db/stage.py index b4cc8a56..e9cb54a2 100644 --- a/backend/bracket/models/db/stage.py +++ b/backend/bracket/models/db/stage.py @@ -6,14 +6,17 @@ from bracket.models.db.shared import BaseModelORM from bracket.utils.id_types import StageId, TournamentId -class Stage(BaseModelORM): - id: StageId | None = None +class StageInsertable(BaseModelORM): tournament_id: TournamentId name: str created: datetime_utc is_active: bool +class Stage(StageInsertable): + id: StageId + + class StageUpdateBody(BaseModelORM): name: str diff --git a/backend/bracket/models/db/stage_item.py b/backend/bracket/models/db/stage_item.py index a4f2fe77..70e19094 100644 --- a/backend/bracket/models/db/stage_item.py +++ b/backend/bracket/models/db/stage_item.py @@ -20,8 +20,7 @@ class StageType(EnumAutoStr): return self in [StageType.SWISS] -class StageItemToInsert(BaseModelORM): - id: StageItemId | None = None +class StageItemInsertable(BaseModelORM): stage_id: StageId name: str created: datetime_utc @@ -30,7 +29,7 @@ class StageItemToInsert(BaseModelORM): ranking_id: RankingId | None = None -class StageItem(StageItemToInsert): +class StageItem(StageItemInsertable): id: StageItemId diff --git a/backend/bracket/models/db/stage_item_inputs.py b/backend/bracket/models/db/stage_item_inputs.py index d208349e..a49518c0 100644 --- a/backend/bracket/models/db/stage_item_inputs.py +++ b/backend/bracket/models/db/stage_item_inputs.py @@ -7,7 +7,7 @@ from bracket.utils.id_types import MatchId, StageItemId, StageItemInputId, TeamI class StageItemInputBase(BaseModelORM): - id: StageItemInputId | None = None + id: StageItemInputId slot: int tournament_id: TournamentId stage_item_id: StageItemId | None = None diff --git a/backend/bracket/models/db/team.py b/backend/bracket/models/db/team.py index f43d1424..62560956 100644 --- a/backend/bracket/models/db/team.py +++ b/backend/bracket/models/db/team.py @@ -12,11 +12,9 @@ from bracket.logic.ranking.statistics import START_ELO from bracket.models.db.player import Player from bracket.models.db.shared import BaseModelORM from bracket.utils.id_types import PlayerId, TeamId, TournamentId -from bracket.utils.types import assert_some -class Team(BaseModelORM): - id: TeamId | None = None +class TeamInsertable(BaseModelORM): created: datetime_utc name: str tournament_id: TournamentId @@ -29,8 +27,12 @@ class Team(BaseModelORM): logo_path: str | None = None +class Team(TeamInsertable): + id: TeamId + + class TeamWithPlayers(BaseModel): - id: TeamId | None = None + id: TeamId players: list[Player] elo_score: Decimal = START_ELO swiss_score: Decimal = Decimal("0.0") @@ -42,7 +44,7 @@ class TeamWithPlayers(BaseModel): @property def player_ids(self) -> list[PlayerId]: - return [assert_some(player.id) for player in self.players] + return [player.id for player in self.players] @field_validator("players", mode="before") def handle_players(values: list[Player]) -> list[Player]: # type: ignore[misc] @@ -88,15 +90,3 @@ class TeamBody(BaseModelORM): class TeamMultiBody(BaseModelORM): names: str = Field(..., min_length=1) active: bool - - -class TeamToInsert(BaseModelORM): - created: datetime_utc - name: str - tournament_id: TournamentId - active: bool - elo_score: Decimal = Decimal("0.0") - swiss_score: Decimal = Decimal("0.0") - wins: int = 0 - draws: int = 0 - losses: int = 0 diff --git a/backend/bracket/models/db/tournament.py b/backend/bracket/models/db/tournament.py index 7b3194ef..cd162554 100644 --- a/backend/bracket/models/db/tournament.py +++ b/backend/bracket/models/db/tournament.py @@ -6,8 +6,7 @@ from bracket.utils.id_types import ClubId, TournamentId from bracket.utils.pydantic import EmptyStrToNone -class Tournament(BaseModelORM): - id: TournamentId | None = None +class TournamentInsertable(BaseModelORM): club_id: ClubId name: str created: datetime_utc @@ -21,6 +20,10 @@ class Tournament(BaseModelORM): auto_assign_courts: bool +class Tournament(TournamentInsertable): + id: TournamentId + + class TournamentUpdateBody(BaseModelORM): start_time: datetime_utc name: str diff --git a/backend/bracket/models/db/user.py b/backend/bracket/models/db/user.py index 856fb663..e47b0763 100644 --- a/backend/bracket/models/db/user.py +++ b/backend/bracket/models/db/user.py @@ -14,7 +14,6 @@ if TYPE_CHECKING: class UserBase(BaseModelORM): - id: UserId | None = None email: str name: str created: datetime_utc @@ -27,12 +26,16 @@ class UserBase(BaseModelORM): return subscription_lookup[self.account_type] -class User(UserBase): +class UserInsertable(UserBase): password_hash: str | None = None +class User(UserInsertable): + id: UserId + + class UserPublic(UserBase): - pass + id: UserId class UserToUpdate(BaseModel): diff --git a/backend/bracket/models/db/user_x_club.py b/backend/bracket/models/db/user_x_club.py index 2c8a81b9..d40cc214 100644 --- a/backend/bracket/models/db/user_x_club.py +++ b/backend/bracket/models/db/user_x_club.py @@ -10,8 +10,11 @@ class UserXClubRelation(EnumAutoStr): COLLABORATOR = auto() -class UserXClub(BaseModelORM): - id: UserXClubId | None = None +class UserXClubInsertable(BaseModelORM): user_id: UserId club_id: ClubId relation: UserXClubRelation + + +class UserXClub(UserXClubInsertable): + id: UserXClubId diff --git a/backend/bracket/routes/auth.py b/backend/bracket/routes/auth.py index 17e8c3c7..cbb0ebe6 100644 --- a/backend/bracket/routes/auth.py +++ b/backend/bracket/routes/auth.py @@ -103,7 +103,7 @@ async def user_authenticated_for_tournament( ) -> UserPublic: user = await check_jwt_and_get_user(token) - if not user or not await get_user_access_to_tournament(tournament_id, assert_some(user.id)): + if not user or not await get_user_access_to_tournament(tournament_id, user.id): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", @@ -118,7 +118,7 @@ async def user_authenticated_for_club( ) -> UserPublic: user = await check_jwt_and_get_user(token) - if not user or not await get_user_access_to_club(club_id, assert_some(user.id)): + if not user or not await get_user_access_to_club(club_id, user.id): raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials", @@ -134,9 +134,7 @@ async def user_authenticated_or_public_dashboard( try: token: str = assert_some(await oauth2_scheme(request)) user = await check_jwt_and_get_user(token) - if user is not None and await get_user_access_to_tournament( - tournament_id, assert_some(user.id) - ): + if user is not None and await get_user_access_to_tournament(tournament_id, user.id): return user except HTTPException: pass diff --git a/backend/bracket/routes/clubs.py b/backend/bracket/routes/clubs.py index 2295f5ec..3713c70f 100644 --- a/backend/bracket/routes/clubs.py +++ b/backend/bracket/routes/clubs.py @@ -8,23 +8,22 @@ from bracket.routes.models import ClubResponse, ClubsResponse, SuccessResponse from bracket.sql.clubs import create_club, get_clubs_for_user_id, sql_delete_club, sql_update_club from bracket.utils.errors import ForeignKey, check_foreign_key_violation from bracket.utils.id_types import ClubId -from bracket.utils.types import assert_some router = APIRouter() @router.get("/clubs", response_model=ClubsResponse) async def get_clubs(user: UserPublic = Depends(user_authenticated)) -> ClubsResponse: - return ClubsResponse(data=await get_clubs_for_user_id(assert_some(user.id))) + return ClubsResponse(data=await get_clubs_for_user_id(user.id)) @router.post("/clubs", response_model=ClubResponse) async def create_new_club( club: ClubCreateBody, user: UserPublic = Depends(user_authenticated) ) -> ClubResponse: - existing_clubs = await get_clubs_for_user_id(assert_some(user.id)) + existing_clubs = await get_clubs_for_user_id(user.id) check_requirement(existing_clubs, user, "max_clubs") - return ClubResponse(data=await create_club(club, assert_some(user.id))) + return ClubResponse(data=await create_club(club, user.id)) @router.delete("/clubs/{club_id}", response_model=SuccessResponse) diff --git a/backend/bracket/routes/matches.py b/backend/bracket/routes/matches.py index fd730930..9066232b 100644 --- a/backend/bracket/routes/matches.py +++ b/backend/bracket/routes/matches.py @@ -75,7 +75,7 @@ async def delete_match( ) -> SuccessResponse: round_ = await get_round_by_id(tournament_id, match.round_id) - await sql_delete_match(assert_some(match.id)) + await sql_delete_match(match.id) await recalculate_ranking_for_stage_item_id(tournament_id, assert_some(round_).stage_item_id) return SuccessResponse() diff --git a/backend/bracket/routes/rounds.py b/backend/bracket/routes/rounds.py index c89b3038..0649692b 100644 --- a/backend/bracket/routes/rounds.py +++ b/backend/bracket/routes/rounds.py @@ -9,7 +9,7 @@ from bracket.logic.subscriptions import check_requirement from bracket.models.db.round import ( Round, RoundCreateBody, - RoundToInsert, + RoundInsertable, RoundUpdateBody, ) from bracket.models.db.user import UserPublic @@ -26,6 +26,7 @@ from bracket.sql.stage_items import get_stage_item from bracket.sql.stages import get_full_tournament_details from bracket.sql.validation import check_foreign_keys_belong_to_tournament from bracket.utils.id_types import RoundId, TournamentId +from tests.integration_tests.mocks import MOCK_NOW router = APIRouter() @@ -84,7 +85,9 @@ async def create_round( ) round_id = await sql_create_round( - RoundToInsert( + RoundInsertable( + created=MOCK_NOW, + is_draft=False, stage_item_id=round_body.stage_item_id, name=await get_next_round_name(tournament_id, round_body.stage_item_id), ), diff --git a/backend/bracket/routes/teams.py b/backend/bracket/routes/teams.py index 309b9c38..ae411320 100644 --- a/backend/bracket/routes/teams.py +++ b/backend/bracket/routes/teams.py @@ -9,7 +9,13 @@ from heliclockter import datetime_utc from bracket.database import database from bracket.logic.subscriptions import check_requirement from bracket.logic.teams import get_team_logo_path -from bracket.models.db.team import FullTeamWithPlayers, Team, TeamBody, TeamMultiBody, TeamToInsert +from bracket.models.db.team import ( + FullTeamWithPlayers, + Team, + TeamBody, + TeamInsertable, + TeamMultiBody, +) from bracket.models.db.user import UserPublic from bracket.routes.auth import ( user_authenticated_for_tournament, @@ -91,7 +97,7 @@ async def update_team_by_id( ), values=team_body.model_dump(exclude={"player_ids"}), ) - await update_team_members(assert_some(team.id), tournament_id, team_body.player_ids) + await update_team_members(team.id, tournament_id, team_body.player_ids) return SingleTeamResponse( data=assert_some( @@ -113,7 +119,7 @@ async def update_team_logo( _: UserPublic = Depends(user_authenticated_for_tournament), team: Team = Depends(team_dependency), ) -> SingleTeamResponse: - team_id = assert_some(team.id) + team_id = team.id old_logo_path = await get_team_logo_path(tournament_id, team_id) filename: str | None = None new_logo_path: str | None = None @@ -157,7 +163,7 @@ async def delete_team( ForeignKey.matches_team2_id_fkey, } ): - await sql_delete_team(tournament_id, assert_some(team.id)) + await sql_delete_team(tournament_id, team.id) return SuccessResponse() @@ -175,7 +181,7 @@ async def create_team( last_record_id = await database.execute( query=teams.insert(), - values=TeamToInsert( + values=TeamInsertable( **team_to_insert.model_dump(exclude={"player_ids"}), created=datetime_utc.now(), tournament_id=tournament_id, @@ -201,7 +207,7 @@ async def create_multiple_teams( for team_name in team_names: await database.execute( query=teams.insert(), - values=TeamToInsert( + values=TeamInsertable( name=team_name, active=team_body.active, created=datetime_utc.now(), diff --git a/backend/bracket/routes/tournaments.py b/backend/bracket/routes/tournaments.py index 51b985fd..eae1bcf9 100644 --- a/backend/bracket/routes/tournaments.py +++ b/backend/bracket/routes/tournaments.py @@ -42,7 +42,6 @@ from bracket.utils.errors import ( ) from bracket.utils.id_types import TournamentId from bracket.utils.logging import logger -from bracket.utils.types import assert_some router = APIRouter() unauthorized_exception = HTTPException( @@ -84,7 +83,7 @@ async def get_tournaments( return TournamentsResponse(data=[tournament]) case _, _ if isinstance(user, UserPublic): - user_club_ids = await get_which_clubs_has_user_access_to(assert_some(user.id)) + user_club_ids = await get_which_clubs_has_user_access_to(user.id) return TournamentsResponse( data=await sql_get_tournaments(tuple(user_club_ids), endpoint_name) ) @@ -131,9 +130,7 @@ async def create_tournament( existing_tournaments = await sql_get_tournaments((tournament_to_insert.club_id,)) check_requirement(existing_tournaments, user, "max_tournaments") - has_access_to_club = await get_user_access_to_club( - tournament_to_insert.club_id, assert_some(user.id) - ) + has_access_to_club = await get_user_access_to_club(tournament_to_insert.club_id, user.id) if not has_access_to_club: raise HTTPException( status_code=status.HTTP_401_UNAUTHORIZED, diff --git a/backend/bracket/routes/users.py b/backend/bracket/routes/users.py index a2741cbd..3e569138 100644 --- a/backend/bracket/routes/users.py +++ b/backend/bracket/routes/users.py @@ -9,7 +9,7 @@ from bracket.logic.subscriptions import setup_demo_account from bracket.models.db.account import UserAccountType from bracket.models.db.user import ( DemoUserToRegister, - User, + UserInsertable, UserPasswordToUpdate, UserPublic, UserToRegister, @@ -60,7 +60,7 @@ async def update_user_details( if user_public.id != user_id: raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Can't change details of this user") - await update_user(assert_some(user_public.id), user_to_update) + await update_user(user_public.id, user_to_update) user_updated = await get_user_by_id(user_id) return UserPublicResponse(data=assert_some(user_updated)) @@ -72,7 +72,7 @@ async def put_user_password( user_public: UserPublic = Depends(user_authenticated), ) -> SuccessResponse: assert user_public.id == user_id - await update_user_password(assert_some(user_public.id), hash_password(user_to_update.password)) + await update_user_password(user_public.id, hash_password(user_to_update.password)) return SuccessResponse() @@ -84,7 +84,7 @@ async def register_user(user_to_register: UserToRegister) -> TokenResponse: if not await verify_captcha_token(user_to_register.captcha_token): raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Failed to validate captcha") - user = User( + user = UserInsertable( email=user_to_register.email, password_hash=hash_password(user_to_register.password), name=user_to_register.name, @@ -100,9 +100,7 @@ async def register_user(user_to_register: UserToRegister) -> TokenResponse: data={"user": user_created.email}, expires_delta=access_token_expires ) return TokenResponse( - data=Token( - access_token=access_token, token_type="bearer", user_id=assert_some(user_created.id) - ) + data=Token(access_token=access_token, token_type="bearer", user_id=user_created.id) ) @@ -117,7 +115,7 @@ async def register_demo_user(user_to_register: DemoUserToRegister) -> TokenRespo raise HTTPException(status.HTTP_401_UNAUTHORIZED, "Failed to validate captcha") username = f"demo-{uuid4()}" - user = User( + user = UserInsertable( email=f"{username}@example.org", password_hash=hash_password(str(uuid4())), name=username, @@ -132,9 +130,7 @@ async def register_demo_user(user_to_register: DemoUserToRegister) -> TokenRespo access_token = create_access_token( data={"user": user_created.email}, expires_delta=access_token_expires ) - await setup_demo_account(assert_some(user_created.id)) + await setup_demo_account(user_created.id) return TokenResponse( - data=Token( - access_token=access_token, token_type="bearer", user_id=assert_some(user_created.id) - ) + data=Token(access_token=access_token, token_type="bearer", user_id=user_created.id) ) diff --git a/backend/bracket/sql/clubs.py b/backend/bracket/sql/clubs.py index 1f39f7a8..42a54d8d 100644 --- a/backend/bracket/sql/clubs.py +++ b/backend/bracket/sql/clubs.py @@ -28,7 +28,7 @@ async def create_club(club: ClubCreateBody, user_id: UserId) -> Club: club_created = Club.model_validate(dict(result._mapping)) - await sql_give_user_access_to_club(user_id, assert_some(club_created.id)) + await sql_give_user_access_to_club(user_id, club_created.id) return club_created diff --git a/backend/bracket/sql/rounds.py b/backend/bracket/sql/rounds.py index 1af5dff2..d7905259 100644 --- a/backend/bracket/sql/rounds.py +++ b/backend/bracket/sql/rounds.py @@ -1,12 +1,12 @@ from bracket.database import database -from bracket.models.db.round import RoundToInsert +from bracket.models.db.round import RoundInsertable from bracket.models.db.util import RoundWithMatches from bracket.sql.stage_items import get_stage_item from bracket.sql.stages import get_full_tournament_details from bracket.utils.id_types import RoundId, StageItemId, TournamentId -async def sql_create_round(round_: RoundToInsert) -> RoundId: +async def sql_create_round(round_: RoundInsertable) -> RoundId: query = """ INSERT INTO rounds (created, is_draft, is_active, name, stage_item_id) VALUES (NOW(), :is_draft, :is_active, :name, :stage_item_id) diff --git a/backend/bracket/sql/users.py b/backend/bracket/sql/users.py index 7f5f0a50..0c3cf33d 100644 --- a/backend/bracket/sql/users.py +++ b/backend/bracket/sql/users.py @@ -1,7 +1,7 @@ from bracket.database import database from bracket.logic.tournaments import sql_delete_tournament_completely from bracket.models.db.account import UserAccountType -from bracket.models.db.user import User, UserInDB, UserPublic, UserToUpdate +from bracket.models.db.user import User, UserInDB, UserInsertable, UserPublic, UserToUpdate from bracket.schema import users from bracket.sql.clubs import get_clubs_for_user_id, sql_delete_club from bracket.sql.tournaments import sql_get_tournaments @@ -87,7 +87,7 @@ async def get_expired_demo_users() -> list[UserPublic]: return [UserPublic.model_validate(demo_user) for demo_user in result] -async def create_user(user: User) -> User: +async def create_user(user: UserInsertable) -> User: query = """ INSERT INTO users (email, name, password_hash, created, account_type) VALUES (:email, :name, :password_hash, :created, :account_type) @@ -130,10 +130,10 @@ async def get_user(email: str) -> UserInDB | None: async def delete_user_and_owned_clubs(user_id: UserId) -> None: for club in await get_clubs_for_user_id(user_id): - club_id = assert_some(club.id) + club_id = club.id for tournament in await sql_get_tournaments((club_id,), None): - tournament_id = assert_some(tournament.id) + tournament_id = tournament.id await sql_delete_tournament_completely(tournament_id) await sql_delete_club(club_id) diff --git a/backend/bracket/utils/db_init.py b/backend/bracket/utils/db_init.py index 6870e6dc..ffda679a 100644 --- a/backend/bracket/utils/db_init.py +++ b/backend/bracket/utils/db_init.py @@ -10,23 +10,23 @@ from bracket.logic.ranking.elo import ( ) from bracket.logic.scheduling.builder import build_matches_for_stage_item from bracket.models.db.account import UserAccountType -from bracket.models.db.club import Club -from bracket.models.db.court import Court +from bracket.models.db.club import ClubInsertable +from bracket.models.db.court import CourtInsertable from bracket.models.db.match import Match, MatchBody -from bracket.models.db.player import Player -from bracket.models.db.player_x_team import PlayerXTeam +from bracket.models.db.player import PlayerInsertable +from bracket.models.db.player_x_team import PlayerXTeamInsertable from bracket.models.db.ranking import RankingInsertable -from bracket.models.db.round import Round -from bracket.models.db.stage import Stage -from bracket.models.db.stage_item import StageItem, StageItemCreateBody +from bracket.models.db.round import RoundInsertable +from bracket.models.db.stage import StageInsertable +from bracket.models.db.stage_item import StageItemCreateBody, StageItemInsertable from bracket.models.db.stage_item_inputs import ( StageItemInputCreateBodyFinal, StageItemInputCreateBodyTentative, ) -from bracket.models.db.team import Team -from bracket.models.db.tournament import Tournament -from bracket.models.db.user import User -from bracket.models.db.user_x_club import UserXClub, UserXClubRelation +from bracket.models.db.team import TeamInsertable +from bracket.models.db.tournament import TournamentInsertable +from bracket.models.db.user import UserInsertable +from bracket.models.db.user_x_club import UserXClubInsertable, UserXClubRelation from bracket.schema import ( clubs, courts, @@ -102,7 +102,7 @@ async def create_admin_user() -> UserId: assert config.admin_password user = await create_user( - User( + UserInsertable( name="Admin", email=config.admin_email, password_hash=hash_password(config.admin_password), @@ -110,7 +110,7 @@ async def create_admin_user() -> UserId: account_type=UserAccountType.REGULAR, ) ) - return assert_some(user.id) + return user.id async def init_db_when_empty() -> UserId | None: @@ -144,18 +144,18 @@ async def sql_create_dev_db() -> UserId: alembic_stamp_head() table_lookup: dict[type, Table] = { - User: users, - Club: clubs, - Stage: stages, - Team: teams, - UserXClub: users_x_clubs, - PlayerXTeam: players_x_teams, - Player: players, - Round: rounds, + UserInsertable: users, + ClubInsertable: clubs, + StageInsertable: stages, + TeamInsertable: teams, + UserXClubInsertable: users_x_clubs, + PlayerXTeamInsertable: players_x_teams, + PlayerInsertable: players, + RoundInsertable: rounds, Match: matches, - Tournament: tournaments, - Court: courts, - StageItem: stage_items, + TournamentInsertable: tournaments, + CourtInsertable: courts, + StageItemInsertable: stage_items, RankingInsertable: rankings, } @@ -174,12 +174,15 @@ async def sql_create_dev_db() -> UserId: club_id_1 = await insert_dummy(DUMMY_CLUB, ClubId) await insert_dummy( - UserXClub(user_id=user_id_1, club_id=club_id_1, relation=UserXClubRelation.OWNER), int + UserXClubInsertable(user_id=user_id_1, club_id=club_id_1, relation=UserXClubRelation.OWNER), + int, ) if real_user_id is not None: await insert_dummy( - UserXClub(user_id=real_user_id, club_id=club_id_1, relation=UserXClubRelation.OWNER), + UserXClubInsertable( + user_id=real_user_id, club_id=club_id_1, relation=UserXClubRelation.OWNER + ), int, ) diff --git a/backend/bracket/utils/dummy_records.py b/backend/bracket/utils/dummy_records.py index 1a0f1478..326220db 100644 --- a/backend/bracket/utils/dummy_records.py +++ b/backend/bracket/utils/dummy_records.py @@ -4,18 +4,18 @@ from zoneinfo import ZoneInfo from heliclockter import datetime_utc from bracket.models.db.account import UserAccountType -from bracket.models.db.club import Club -from bracket.models.db.court import Court -from bracket.models.db.match import Match -from bracket.models.db.player import Player -from bracket.models.db.player_x_team import PlayerXTeam +from bracket.models.db.club import ClubInsertable +from bracket.models.db.court import CourtInsertable +from bracket.models.db.match import MatchInsertable +from bracket.models.db.player import PlayerInsertable +from bracket.models.db.player_x_team import PlayerXTeamInsertable from bracket.models.db.ranking import RankingInsertable -from bracket.models.db.round import Round -from bracket.models.db.stage import Stage -from bracket.models.db.stage_item import StageItemToInsert, StageType -from bracket.models.db.team import Team -from bracket.models.db.tournament import Tournament -from bracket.models.db.user import User +from bracket.models.db.round import RoundInsertable +from bracket.models.db.stage import StageInsertable +from bracket.models.db.stage_item import StageItemInsertable, StageType +from bracket.models.db.team import TeamInsertable +from bracket.models.db.tournament import TournamentInsertable +from bracket.models.db.user import UserInsertable from bracket.utils.id_types import ( ClubId, CourtId, @@ -34,12 +34,12 @@ DUMMY_MOCK_TIME = datetime_utc(2022, 1, 11, 4, 32, 11, tzinfo=ZoneInfo("UTC")) # We don't know any db IDs here, so we use a placeholder for foreign keys. DB_PLACEHOLDER_ID = -42 -DUMMY_CLUB = Club( +DUMMY_CLUB = ClubInsertable( name="Some Cool Club", created=DUMMY_MOCK_TIME, ) -DUMMY_TOURNAMENT = Tournament( +DUMMY_TOURNAMENT = TournamentInsertable( club_id=ClubId(DB_PLACEHOLDER_ID), name="Some Cool Tournament", created=DUMMY_MOCK_TIME, @@ -53,21 +53,21 @@ DUMMY_TOURNAMENT = Tournament( margin_minutes=5, ) -DUMMY_STAGE1 = Stage( +DUMMY_STAGE1 = StageInsertable( tournament_id=TournamentId(DB_PLACEHOLDER_ID), created=DUMMY_MOCK_TIME, is_active=True, name="Group Stage", ) -DUMMY_STAGE2 = Stage( +DUMMY_STAGE2 = StageInsertable( tournament_id=TournamentId(DB_PLACEHOLDER_ID), created=DUMMY_MOCK_TIME, is_active=False, name="Knockout Stage", ) -DUMMY_STAGE_ITEM1 = StageItemToInsert( +DUMMY_STAGE_ITEM1 = StageItemInsertable( stage_id=StageId(DB_PLACEHOLDER_ID), ranking_id=RankingId(DB_PLACEHOLDER_ID), created=DUMMY_MOCK_TIME, @@ -76,7 +76,7 @@ DUMMY_STAGE_ITEM1 = StageItemToInsert( name="Group A", ) -DUMMY_STAGE_ITEM2 = StageItemToInsert( +DUMMY_STAGE_ITEM2 = StageItemInsertable( stage_id=StageId(DB_PLACEHOLDER_ID), ranking_id=RankingId(DB_PLACEHOLDER_ID), created=DUMMY_MOCK_TIME, @@ -85,7 +85,7 @@ DUMMY_STAGE_ITEM2 = StageItemToInsert( name="Group B", ) -DUMMY_STAGE_ITEM3 = StageItemToInsert( +DUMMY_STAGE_ITEM3 = StageItemInsertable( stage_id=StageId(DB_PLACEHOLDER_ID), ranking_id=RankingId(DB_PLACEHOLDER_ID), created=DUMMY_MOCK_TIME, @@ -94,28 +94,28 @@ DUMMY_STAGE_ITEM3 = StageItemToInsert( name="Bracket A", ) -DUMMY_ROUND1 = Round( +DUMMY_ROUND1 = RoundInsertable( stage_item_id=StageItemId(DB_PLACEHOLDER_ID), created=DUMMY_MOCK_TIME, is_draft=False, name="Round 1", ) -DUMMY_ROUND2 = Round( +DUMMY_ROUND2 = RoundInsertable( stage_item_id=StageItemId(DB_PLACEHOLDER_ID), created=DUMMY_MOCK_TIME, is_draft=True, name="Round 2", ) -DUMMY_ROUND3 = Round( +DUMMY_ROUND3 = RoundInsertable( stage_item_id=StageItemId(DB_PLACEHOLDER_ID), created=DUMMY_MOCK_TIME, is_draft=False, name="Round 3", ) -DUMMY_MATCH1 = Match( +DUMMY_MATCH1 = MatchInsertable( created=DUMMY_MOCK_TIME, start_time=DUMMY_MOCK_TIME, round_id=RoundId(DB_PLACEHOLDER_ID), @@ -137,7 +137,7 @@ DUMMY_MATCH1 = Match( position_in_schedule=1, ) -DUMMY_USER = User( +DUMMY_USER = UserInsertable( email="admin@example.com", name="Admin", password_hash=hash_password("adminadmin"), @@ -145,28 +145,28 @@ DUMMY_USER = User( account_type=UserAccountType.REGULAR, ) -DUMMY_TEAM1 = Team( +DUMMY_TEAM1 = TeamInsertable( created=DUMMY_MOCK_TIME, name="Team 1", tournament_id=TournamentId(DB_PLACEHOLDER_ID), active=True, ) -DUMMY_TEAM2 = Team( +DUMMY_TEAM2 = TeamInsertable( created=DUMMY_MOCK_TIME, name="Team 2", tournament_id=TournamentId(DB_PLACEHOLDER_ID), active=True, ) -DUMMY_TEAM3 = Team( +DUMMY_TEAM3 = TeamInsertable( created=DUMMY_MOCK_TIME, name="Team 3", tournament_id=TournamentId(DB_PLACEHOLDER_ID), active=True, ) -DUMMY_TEAM4 = Team( +DUMMY_TEAM4 = TeamInsertable( created=DUMMY_MOCK_TIME, name="Team 4", tournament_id=TournamentId(DB_PLACEHOLDER_ID), @@ -174,74 +174,74 @@ DUMMY_TEAM4 = Team( ) -DUMMY_PLAYER1 = Player( +DUMMY_PLAYER1 = PlayerInsertable( name="Player 01", active=True, created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_PLAYER2 = Player( +DUMMY_PLAYER2 = PlayerInsertable( name="Player 02", active=True, created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_PLAYER3 = Player( +DUMMY_PLAYER3 = PlayerInsertable( name="Player 03", active=True, created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_PLAYER4 = Player( +DUMMY_PLAYER4 = PlayerInsertable( name="Player 04", active=True, created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_PLAYER5 = Player( +DUMMY_PLAYER5 = PlayerInsertable( name="Player 05", active=True, created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_PLAYER6 = Player( +DUMMY_PLAYER6 = PlayerInsertable( name="Player 06", active=True, created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_PLAYER7 = Player( +DUMMY_PLAYER7 = PlayerInsertable( name="Player 07", active=True, created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_PLAYER8 = Player( +DUMMY_PLAYER8 = PlayerInsertable( name="Player 08", active=True, created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_PLAYER_X_TEAM = PlayerXTeam( +DUMMY_PLAYER_X_TEAM = PlayerXTeamInsertable( player_id=PlayerId(DB_PLACEHOLDER_ID), team_id=TeamId(DB_PLACEHOLDER_ID), ) -DUMMY_COURT1 = Court( +DUMMY_COURT1 = CourtInsertable( name="Court 1", created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), ) -DUMMY_COURT2 = Court( +DUMMY_COURT2 = CourtInsertable( name="Court 2", created=DUMMY_MOCK_TIME, tournament_id=TournamentId(DB_PLACEHOLDER_ID), diff --git a/backend/cli.py b/backend/cli.py index 3d9d0166..9fdb4add 100755 --- a/backend/cli.py +++ b/backend/cli.py @@ -10,14 +10,13 @@ from bracket.config import config from bracket.database import database from bracket.logger import get_logger from bracket.models.db.account import UserAccountType -from bracket.models.db.user import User +from bracket.models.db.user import UserInsertable from bracket.sql.users import ( check_whether_email_is_in_use, create_user, ) from bracket.utils.db_init import sql_create_dev_db from bracket.utils.security import hash_password -from bracket.utils.types import assert_some logger = get_logger("cli") @@ -72,7 +71,7 @@ async def create_dev_db() -> None: @click.option("--name", prompt="Name", help="The name associated with the account.") @run_async async def register_user(email: str, password: str, name: str) -> None: - user = User( + user = UserInsertable( email=email, password_hash=hash_password(password), name=name, @@ -83,7 +82,6 @@ async def register_user(email: str, password: str, name: str) -> None: logger.error("Email address already in use") raise SystemExit(1) user_created = await create_user(user) - assert_some(user_created.id) logger.info(f"Created user with id: {user_created.id}") diff --git a/backend/tests/integration_tests/api/activate_next_stage_test.py b/backend/tests/integration_tests/api/activate_next_stage_test.py index 4625373e..3337a501 100644 --- a/backend/tests/integration_tests/api/activate_next_stage_test.py +++ b/backend/tests/integration_tests/api/activate_next_stage_test.py @@ -19,7 +19,6 @@ from bracket.utils.dummy_records import ( DUMMY_TEAM1, ) from bracket.utils.http import HTTPMethod -from bracket.utils.types import assert_some from tests.integration_tests.api.shared import SUCCESS_RESPONSE, send_tournament_request from tests.integration_tests.models import AuthContext from tests.integration_tests.sql import ( @@ -55,30 +54,30 @@ async def test_activate_next_stage( DUMMY_TEAM1.model_copy(update={"tournament_id": auth_context.tournament.id}) ) as team_inserted_4, ): - tournament_id = assert_some(auth_context.tournament.id) + tournament_id = auth_context.tournament.id stage_item_1 = await sql_create_stage_item( tournament_id, StageItemCreateBody( - stage_id=assert_some(stage_inserted_1.id), + stage_id=stage_inserted_1.id, name=DUMMY_STAGE_ITEM1.name, team_count=DUMMY_STAGE_ITEM1.team_count, type=DUMMY_STAGE_ITEM1.type, inputs=[ StageItemInputCreateBodyFinal( slot=1, - team_id=assert_some(team_inserted_1.id), + team_id=team_inserted_1.id, ), StageItemInputCreateBodyFinal( slot=2, - team_id=assert_some(team_inserted_2.id), + team_id=team_inserted_2.id, ), StageItemInputCreateBodyFinal( slot=3, - team_id=assert_some(team_inserted_3.id), + team_id=team_inserted_3.id, ), StageItemInputCreateBodyFinal( slot=4, - team_id=assert_some(team_inserted_4.id), + team_id=team_inserted_4.id, ), ], ), @@ -86,7 +85,7 @@ async def test_activate_next_stage( stage_item_2 = await sql_create_stage_item( tournament_id, StageItemCreateBody( - stage_id=assert_some(stage_inserted_2.id), + stage_id=stage_inserted_2.id, name=DUMMY_STAGE_ITEM3.name, team_count=2, type=DUMMY_STAGE_ITEM3.type, @@ -108,12 +107,12 @@ async def test_activate_next_stage( await build_matches_for_stage_item(stage_item_2, tournament_id) # Set match score to get a winner (team 2) that goes to the next round - [prev_stage, _] = await get_full_tournament_details(assert_some(auth_context.tournament.id)) + [prev_stage, _] = await get_full_tournament_details(auth_context.tournament.id) match1 = prev_stage.stage_items[0].rounds[0].matches[0] assert isinstance(match1, MatchWithDetailsDefinitive) assert match1.team2.id == team_inserted_2.id await sql_update_match( - assert_some(match1.id), + match1.id, MatchBody(**match1.model_copy(update={"team2_score": 42}).model_dump()), auth_context.tournament, ) @@ -121,7 +120,7 @@ async def test_activate_next_stage( response = await send_tournament_request( HTTPMethod.POST, "stages/activate?direction=next", auth_context, json={} ) - [_, next_stage] = await get_full_tournament_details(assert_some(auth_context.tournament.id)) + [_, next_stage] = await get_full_tournament_details(auth_context.tournament.id) await sql_delete_stage_item_with_foreign_keys(stage_item_2.id) await sql_delete_stage_item_with_foreign_keys(stage_item_1.id) diff --git a/backend/tests/integration_tests/api/auto_scheduling_matches_test.py b/backend/tests/integration_tests/api/auto_scheduling_matches_test.py index a9734624..4c09019b 100644 --- a/backend/tests/integration_tests/api/auto_scheduling_matches_test.py +++ b/backend/tests/integration_tests/api/auto_scheduling_matches_test.py @@ -1,6 +1,6 @@ from heliclockter import datetime_utc -from bracket.models.db.round import RoundToInsert +from bracket.models.db.round import RoundInsertable from bracket.models.db.stage_item import StageItemCreateBody, StageType from bracket.models.db.stage_item_inputs import ( StageItemInputCreateBodyFinal, @@ -21,6 +21,7 @@ from tests.integration_tests.api.shared import ( SUCCESS_RESPONSE, send_tournament_request, ) +from tests.integration_tests.mocks import MOCK_NOW from tests.integration_tests.models import AuthContext from tests.integration_tests.sql import ( inserted_court, @@ -46,28 +47,34 @@ async def test_schedule_matches_auto( DUMMY_TEAM1.model_copy(update={"tournament_id": auth_context.tournament.id}) ) as team_inserted_2, ): - tournament_id = assert_some(auth_context.tournament.id) + tournament_id = auth_context.tournament.id stage_item_1 = await sql_create_stage_item( tournament_id, StageItemCreateBody( - stage_id=assert_some(stage_inserted_1.id), + stage_id=stage_inserted_1.id, name=DUMMY_STAGE_ITEM1.name, team_count=2, type=StageType.SWISS, inputs=[ StageItemInputCreateBodyFinal( slot=1, - team_id=assert_some(team_inserted_1.id), + team_id=team_inserted_1.id, ), StageItemInputCreateBodyFinal( slot=2, - team_id=assert_some(team_inserted_2.id), + team_id=team_inserted_2.id, ), ], ), ) await sql_create_round( - RoundToInsert(stage_item_id=stage_item_1.id, name="", is_draft=True, is_active=False), + RoundInsertable( + stage_item_id=stage_item_1.id, + name="", + is_draft=True, + is_active=False, + created=MOCK_NOW, + ), ) response = await send_tournament_request( @@ -103,31 +110,43 @@ async def test_start_next_round( DUMMY_TEAM1.model_copy(update={"tournament_id": auth_context.tournament.id}) ) as team_inserted_2, ): - tournament_id = assert_some(auth_context.tournament.id) + tournament_id = auth_context.tournament.id stage_item_1 = await sql_create_stage_item( tournament_id, StageItemCreateBody( - stage_id=assert_some(stage_inserted_1.id), + stage_id=stage_inserted_1.id, name=DUMMY_STAGE_ITEM1.name, team_count=2, type=StageType.SWISS, inputs=[ StageItemInputCreateBodyFinal( slot=1, - team_id=assert_some(team_inserted_1.id), + team_id=team_inserted_1.id, ), StageItemInputCreateBodyFinal( slot=2, - team_id=assert_some(team_inserted_2.id), + team_id=team_inserted_2.id, ), ], ), ) round_1_id = await sql_create_round( - RoundToInsert(stage_item_id=stage_item_1.id, name="", is_draft=True, is_active=False), + RoundInsertable( + stage_item_id=stage_item_1.id, + name="", + is_draft=True, + is_active=False, + created=MOCK_NOW, + ), ) round_2_id = await sql_create_round( - RoundToInsert(stage_item_id=stage_item_1.id, name="", is_draft=True, is_active=False), + RoundInsertable( + stage_item_id=stage_item_1.id, + name="", + is_draft=True, + is_active=False, + created=MOCK_NOW, + ), ) try: diff --git a/backend/tests/integration_tests/api/clubs_test.py b/backend/tests/integration_tests/api/clubs_test.py index d4c62022..39da0e2b 100644 --- a/backend/tests/integration_tests/api/clubs_test.py +++ b/backend/tests/integration_tests/api/clubs_test.py @@ -1,8 +1,7 @@ -from bracket.models.db.user_x_club import UserXClub, UserXClubRelation +from bracket.models.db.user_x_club import UserXClubInsertable, UserXClubRelation from bracket.sql.clubs import get_clubs_for_user_id, sql_delete_club from bracket.utils.dummy_records import DUMMY_CLUB, DUMMY_MOCK_TIME from bracket.utils.http import HTTPMethod -from bracket.utils.types import assert_some from tests.integration_tests.api.shared import send_auth_request from tests.integration_tests.models import AuthContext from tests.integration_tests.sql import inserted_club, inserted_user_x_club @@ -27,7 +26,7 @@ async def test_create_club( ) -> None: payload = {"name": "Some Cool Club"} response = await send_auth_request(HTTPMethod.POST, "clubs", auth_context, json=payload) - user_id = assert_some(auth_context.user.id) + user_id = auth_context.user.id clubs = await get_clubs_for_user_id(user_id) club_id = response["data"]["id"] @@ -43,9 +42,9 @@ async def test_delete_club( ) -> None: async with inserted_club(DUMMY_CLUB) as club_inserted: async with inserted_user_x_club( - UserXClub( - user_id=assert_some(auth_context.user.id), - club_id=assert_some(club_inserted.id), + UserXClubInsertable( + user_id=auth_context.user.id, + club_id=club_inserted.id, relation=UserXClubRelation.OWNER, ) ): diff --git a/backend/tests/integration_tests/api/matches_test.py b/backend/tests/integration_tests/api/matches_test.py index 81a81d57..98cb4c1a 100644 --- a/backend/tests/integration_tests/api/matches_test.py +++ b/backend/tests/integration_tests/api/matches_test.py @@ -19,7 +19,6 @@ from bracket.utils.dummy_records import ( DUMMY_TEAM2, ) from bracket.utils.http import HTTPMethod -from bracket.utils.types import assert_some from tests.integration_tests.api.shared import SUCCESS_RESPONSE, send_tournament_request from tests.integration_tests.models import AuthContext from tests.integration_tests.sql import ( @@ -286,25 +285,25 @@ async def test_upcoming_matches_endpoint( DUMMY_PLAYER1.model_copy( update={"elo_score": Decimal("1100.0"), "tournament_id": auth_context.tournament.id} ), - assert_some(team1_inserted.id), + team1_inserted.id, ) as player_inserted_1, inserted_player_in_team( DUMMY_PLAYER2.model_copy( update={"elo_score": Decimal("1300.0"), "tournament_id": auth_context.tournament.id} ), - assert_some(team2_inserted.id), + team2_inserted.id, ) as player_inserted_2, inserted_player_in_team( DUMMY_PLAYER3.model_copy( update={"elo_score": Decimal("1200.0"), "tournament_id": auth_context.tournament.id} ), - assert_some(team1_inserted.id), + team1_inserted.id, ) as player_inserted_3, inserted_player_in_team( DUMMY_PLAYER4.model_copy( update={"elo_score": Decimal("1400.0"), "tournament_id": auth_context.tournament.id} ), - assert_some(team2_inserted.id), + team2_inserted.id, ) as player_inserted_4, ): json_response = await send_tournament_request( diff --git a/backend/tests/integration_tests/api/rankings_test.py b/backend/tests/integration_tests/api/rankings_test.py index 6b7cf7f5..9165e403 100644 --- a/backend/tests/integration_tests/api/rankings_test.py +++ b/backend/tests/integration_tests/api/rankings_test.py @@ -11,7 +11,6 @@ from bracket.sql.rankings import ( from bracket.utils.db import fetch_one_parsed_certain from bracket.utils.dummy_records import DUMMY_RANKING1, DUMMY_TEAM1 from bracket.utils.http import HTTPMethod -from bracket.utils.types import assert_some from tests.integration_tests.api.shared import SUCCESS_RESPONSE, send_tournament_request from tests.integration_tests.models import AuthContext from tests.integration_tests.sql import inserted_ranking, inserted_team @@ -45,7 +44,7 @@ async def test_create_ranking( response = await send_tournament_request(HTTPMethod.POST, "rankings", auth_context, json={}) assert response.get("success") is True, response - tournament_id = assert_some(auth_context.tournament.id) + tournament_id = auth_context.tournament.id for ranking in await get_all_rankings_in_tournament(tournament_id): if ranking.position != 0: await sql_delete_ranking(tournament_id, ranking.id) diff --git a/backend/tests/integration_tests/api/rescheduling_matches_test.py b/backend/tests/integration_tests/api/rescheduling_matches_test.py index 8d5fd056..592e3cd6 100644 --- a/backend/tests/integration_tests/api/rescheduling_matches_test.py +++ b/backend/tests/integration_tests/api/rescheduling_matches_test.py @@ -12,7 +12,6 @@ from bracket.utils.dummy_records import ( DUMMY_TEAM2, ) from bracket.utils.http import HTTPMethod -from bracket.utils.types import assert_some from tests.integration_tests.api.shared import SUCCESS_RESPONSE, send_tournament_request from tests.integration_tests.models import AuthContext from tests.integration_tests.sql import ( @@ -65,9 +64,9 @@ async def test_reschedule_match( ) as match_inserted, ): body = MatchRescheduleBody( - old_court_id=assert_some(court1_inserted.id), + old_court_id=court1_inserted.id, old_position=1, - new_court_id=assert_some(court2_inserted.id), + new_court_id=court2_inserted.id, new_position=2, ) assert ( @@ -79,7 +78,7 @@ async def test_reschedule_match( ) == SUCCESS_RESPONSE ) - match = await sql_get_match(assert_some(match_inserted.id)) + match = await sql_get_match(match_inserted.id) await assert_row_count_and_clear(matches, 0) assert match.court_id == body.new_court_id diff --git a/backend/tests/integration_tests/api/scheduling_matches_test.py b/backend/tests/integration_tests/api/scheduling_matches_test.py index 983e4a62..168545ed 100644 --- a/backend/tests/integration_tests/api/scheduling_matches_test.py +++ b/backend/tests/integration_tests/api/scheduling_matches_test.py @@ -15,7 +15,6 @@ from bracket.utils.dummy_records import ( DUMMY_TEAM1, ) from bracket.utils.http import HTTPMethod -from bracket.utils.types import assert_some from tests.integration_tests.api.shared import ( SUCCESS_RESPONSE, send_tournament_request, @@ -51,30 +50,30 @@ async def test_schedule_all_matches( DUMMY_TEAM1.model_copy(update={"tournament_id": auth_context.tournament.id}) ) as team_inserted_4, ): - tournament_id = assert_some(auth_context.tournament.id) + tournament_id = auth_context.tournament.id stage_item_1 = await sql_create_stage_item( tournament_id, StageItemCreateBody( - stage_id=assert_some(stage_inserted_1.id), + stage_id=stage_inserted_1.id, name=DUMMY_STAGE_ITEM1.name, team_count=DUMMY_STAGE_ITEM1.team_count, type=DUMMY_STAGE_ITEM1.type, inputs=[ StageItemInputCreateBodyFinal( slot=1, - team_id=assert_some(team_inserted_1.id), + team_id=team_inserted_1.id, ), StageItemInputCreateBodyFinal( slot=2, - team_id=assert_some(team_inserted_2.id), + team_id=team_inserted_2.id, ), StageItemInputCreateBodyFinal( slot=3, - team_id=assert_some(team_inserted_3.id), + team_id=team_inserted_3.id, ), StageItemInputCreateBodyFinal( slot=4, - team_id=assert_some(team_inserted_4.id), + team_id=team_inserted_4.id, ), ], ), @@ -82,7 +81,7 @@ async def test_schedule_all_matches( stage_item_2 = await sql_create_stage_item( tournament_id, StageItemCreateBody( - stage_id=assert_some(stage_inserted_1.id), + stage_id=stage_inserted_1.id, name=DUMMY_STAGE_ITEM3.name, team_count=2, type=DUMMY_STAGE_ITEM3.type, diff --git a/backend/tests/integration_tests/api/stages_test.py b/backend/tests/integration_tests/api/stages_test.py index 692b0b3f..217a1de7 100644 --- a/backend/tests/integration_tests/api/stages_test.py +++ b/backend/tests/integration_tests/api/stages_test.py @@ -11,7 +11,6 @@ from bracket.utils.dummy_records import ( DUMMY_TEAM1, ) from bracket.utils.http import HTTPMethod -from bracket.utils.types import assert_some from tests.integration_tests.api.shared import ( SUCCESS_RESPONSE, send_request, @@ -147,7 +146,7 @@ async def test_update_stage( ) == SUCCESS_RESPONSE ) - [updated_stage] = await get_full_tournament_details(assert_some(auth_context.tournament.id)) + [updated_stage] = await get_full_tournament_details(auth_context.tournament.id) assert len(updated_stage.stage_items) == 1 assert updated_stage.name == body["name"] @@ -173,9 +172,7 @@ async def test_activate_stage( ) == SUCCESS_RESPONSE ) - [prev_stage, next_stage] = await get_full_tournament_details( - assert_some(auth_context.tournament.id) - ) + [prev_stage, next_stage] = await get_full_tournament_details(auth_context.tournament.id) assert prev_stage.is_active is False assert next_stage.is_active is True diff --git a/backend/tests/integration_tests/api/tournaments_test.py b/backend/tests/integration_tests/api/tournaments_test.py index 3c364e52..5666dbea 100644 --- a/backend/tests/integration_tests/api/tournaments_test.py +++ b/backend/tests/integration_tests/api/tournaments_test.py @@ -88,7 +88,7 @@ async def test_create_tournament( # Cleanup tournament = assert_some(await sql_get_tournament_by_endpoint_name(dashboard_endpoint)) - await sql_delete_tournament_completely(assert_some(tournament.id)) + await sql_delete_tournament_completely(tournament.id) async def test_update_tournament( @@ -133,7 +133,7 @@ async def test_delete_tournament( == SUCCESS_RESPONSE ) - await sql_delete_tournament(assert_some(tournament_inserted.id)) + await sql_delete_tournament(tournament_inserted.id) async def test_tournament_upload_and_remove_logo( diff --git a/backend/tests/integration_tests/cronjobs/demo_user_deletion_test.py b/backend/tests/integration_tests/cronjobs/demo_user_deletion_test.py index deab43ad..4d7a9837 100644 --- a/backend/tests/integration_tests/cronjobs/demo_user_deletion_test.py +++ b/backend/tests/integration_tests/cronjobs/demo_user_deletion_test.py @@ -1,13 +1,12 @@ from bracket.cronjobs.scheduling import delete_demo_accounts from bracket.models.db.account import UserAccountType from bracket.sql.users import get_user_by_id, update_user_account_type -from bracket.utils.types import assert_some from tests.integration_tests.sql import inserted_auth_context async def test_delete_demo_accounts() -> None: async with inserted_auth_context() as auth_context: - user_id = assert_some(auth_context.user.id) + user_id = auth_context.user.id await update_user_account_type(user_id, UserAccountType.DEMO) assert await get_user_by_id(user_id) is not None diff --git a/backend/tests/integration_tests/mocks.py b/backend/tests/integration_tests/mocks.py index 6b9daa62..9a1bb14f 100644 --- a/backend/tests/integration_tests/mocks.py +++ b/backend/tests/integration_tests/mocks.py @@ -4,7 +4,7 @@ from zoneinfo import ZoneInfo from heliclockter import datetime_utc, timedelta from bracket.models.db.account import UserAccountType -from bracket.models.db.user import User +from bracket.models.db.user import UserInsertable from bracket.routes.auth import ACCESS_TOKEN_EXPIRE_MINUTES, create_access_token MOCK_NOW = datetime_utc( @@ -16,8 +16,8 @@ def generate_email() -> str: return f"donald_duck-{uuid4()}" -def get_mock_user() -> User: - return User( +def get_mock_user() -> UserInsertable: + return UserInsertable( email=generate_email(), name="Donald Duck", # hash of 'mypassword' @@ -27,7 +27,7 @@ def get_mock_user() -> User: ) -def get_mock_token(mock_user: User) -> str: +def get_mock_token(mock_user: UserInsertable) -> str: return create_access_token( data={"user": mock_user.email}, expires_delta=timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES), diff --git a/backend/tests/integration_tests/sql.py b/backend/tests/integration_tests/sql.py index 4383b3cf..b335dbe5 100644 --- a/backend/tests/integration_tests/sql.py +++ b/backend/tests/integration_tests/sql.py @@ -5,19 +5,19 @@ from typing import cast from sqlalchemy import Table from bracket.database import database -from bracket.models.db.club import Club -from bracket.models.db.court import Court -from bracket.models.db.match import Match -from bracket.models.db.player import Player -from bracket.models.db.player_x_team import PlayerXTeam +from bracket.models.db.club import Club, ClubInsertable +from bracket.models.db.court import Court, CourtInsertable +from bracket.models.db.match import Match, MatchInsertable +from bracket.models.db.player import Player, PlayerInsertable +from bracket.models.db.player_x_team import PlayerXTeamInsertable from bracket.models.db.ranking import Ranking, RankingInsertable -from bracket.models.db.round import Round -from bracket.models.db.stage import Stage -from bracket.models.db.stage_item import StageItem, StageItemToInsert -from bracket.models.db.team import Team -from bracket.models.db.tournament import Tournament -from bracket.models.db.user import User, UserInDB -from bracket.models.db.user_x_club import UserXClub, UserXClubRelation +from bracket.models.db.round import Round, RoundInsertable +from bracket.models.db.stage import Stage, StageInsertable +from bracket.models.db.stage_item import StageItem, StageItemInsertable +from bracket.models.db.team import Team, TeamInsertable +from bracket.models.db.tournament import Tournament, TournamentInsertable +from bracket.models.db.user import UserInDB, UserInsertable +from bracket.models.db.user_x_club import UserXClub, UserXClubInsertable, UserXClubRelation from bracket.schema import ( clubs, courts, @@ -36,7 +36,7 @@ from bracket.schema import ( from bracket.utils.db import insert_generic from bracket.utils.dummy_records import DUMMY_CLUB, DUMMY_RANKING1, DUMMY_TOURNAMENT from bracket.utils.id_types import TeamId -from bracket.utils.types import BaseModelT, assert_some +from bracket.utils.types import BaseModelT from tests.integration_tests.mocks import get_mock_token, get_mock_user from tests.integration_tests.models import AuthContext @@ -59,33 +59,33 @@ async def inserted_generic( @asynccontextmanager -async def inserted_user(user: User) -> AsyncIterator[UserInDB]: +async def inserted_user(user: UserInsertable) -> AsyncIterator[UserInDB]: async with inserted_generic(user, users, UserInDB) as row_inserted: yield cast(UserInDB, row_inserted) @asynccontextmanager -async def inserted_club(club: Club) -> AsyncIterator[Club]: +async def inserted_club(club: ClubInsertable) -> AsyncIterator[Club]: async with inserted_generic(club, clubs, Club) as row_inserted: - yield row_inserted + yield cast(Club, row_inserted) @asynccontextmanager -async def inserted_tournament(tournament: Tournament) -> AsyncIterator[Tournament]: +async def inserted_tournament(tournament: TournamentInsertable) -> AsyncIterator[Tournament]: async with inserted_generic(tournament, tournaments, Tournament) as row_inserted: - yield row_inserted + yield cast(Tournament, row_inserted) @asynccontextmanager -async def inserted_team(team: Team) -> AsyncIterator[Team]: +async def inserted_team(team: TeamInsertable) -> AsyncIterator[Team]: async with inserted_generic(team, teams, Team) as row_inserted: - yield row_inserted + yield cast(Team, row_inserted) @asynccontextmanager -async def inserted_court(court: Court) -> AsyncIterator[Court]: +async def inserted_court(court: CourtInsertable) -> AsyncIterator[Court]: async with inserted_generic(court, courts, Court) as row_inserted: - yield row_inserted + yield cast(Court, row_inserted) @asynccontextmanager @@ -95,50 +95,52 @@ async def inserted_ranking(ranking: RankingInsertable) -> AsyncIterator[Ranking] @asynccontextmanager -async def inserted_player(player: Player) -> AsyncIterator[Player]: +async def inserted_player(player: PlayerInsertable) -> AsyncIterator[Player]: async with inserted_generic(player, players, Player) as row_inserted: - yield row_inserted + yield cast(Player, row_inserted) @asynccontextmanager -async def inserted_player_in_team(player: Player, team_id: TeamId) -> AsyncIterator[Player]: +async def inserted_player_in_team( + player: PlayerInsertable, team_id: TeamId +) -> AsyncIterator[Player]: async with inserted_generic(player, players, Player) as row_inserted: async with inserted_generic( - PlayerXTeam(player_id=assert_some(row_inserted.id), team_id=team_id), + PlayerXTeamInsertable(player_id=cast(Player, row_inserted).id, team_id=team_id), players_x_teams, - PlayerXTeam, + PlayerXTeamInsertable, ): - yield row_inserted + yield cast(Player, row_inserted) @asynccontextmanager -async def inserted_stage(stage: Stage) -> AsyncIterator[Stage]: +async def inserted_stage(stage: StageInsertable) -> AsyncIterator[Stage]: async with inserted_generic(stage, stages, Stage) as row_inserted: - yield row_inserted + yield cast(Stage, row_inserted) @asynccontextmanager -async def inserted_stage_item(stage_item: StageItemToInsert) -> AsyncIterator[StageItem]: +async def inserted_stage_item(stage_item: StageItemInsertable) -> AsyncIterator[StageItem]: async with inserted_generic(stage_item, stage_items, StageItem) as row_inserted: yield StageItem(**row_inserted.model_dump()) @asynccontextmanager -async def inserted_round(round_: Round) -> AsyncIterator[Round]: +async def inserted_round(round_: RoundInsertable) -> AsyncIterator[Round]: async with inserted_generic(round_, rounds, Round) as row_inserted: - yield row_inserted + yield cast(Round, row_inserted) @asynccontextmanager -async def inserted_match(match: Match) -> AsyncIterator[Match]: +async def inserted_match(match: MatchInsertable) -> AsyncIterator[Match]: async with inserted_generic(match, matches, Match) as row_inserted: - yield row_inserted + yield cast(Match, row_inserted) @asynccontextmanager -async def inserted_user_x_club(user_x_club: UserXClub) -> AsyncIterator[UserXClub]: +async def inserted_user_x_club(user_x_club: UserXClubInsertable) -> AsyncIterator[UserXClub]: async with inserted_generic(user_x_club, users_x_clubs, UserXClub) as row_inserted: - yield row_inserted + yield cast(UserXClub, row_inserted) @asynccontextmanager @@ -155,9 +157,9 @@ async def inserted_auth_context() -> AsyncIterator[AuthContext]: DUMMY_RANKING1.model_copy(update={"tournament_id": tournament_inserted.id}) ) as ranking_inserted, inserted_user_x_club( - UserXClub( + UserXClubInsertable( user_id=user_inserted.id, - club_id=assert_some(club_inserted.id), + club_id=club_inserted.id, relation=UserXClubRelation.OWNER, ) ) as user_x_club_inserted, diff --git a/backend/tests/unit_tests/ranking_calculation_test.py b/backend/tests/unit_tests/ranking_calculation_test.py index 4ca6bf00..c9580382 100644 --- a/backend/tests/unit_tests/ranking_calculation_test.py +++ b/backend/tests/unit_tests/ranking_calculation_test.py @@ -11,7 +11,16 @@ from bracket.models.db.stage_item_inputs import StageItemInputFinal from bracket.models.db.team import FullTeamWithPlayers from bracket.models.db.util import RoundWithMatches, StageItemWithRounds from bracket.utils.dummy_records import DUMMY_TEAM1, DUMMY_TEAM2 -from bracket.utils.id_types import RankingId, RoundId, StageId, StageItemId, TeamId, TournamentId +from bracket.utils.id_types import ( + MatchId, + RankingId, + RoundId, + StageId, + StageItemId, + StageItemInputId, + TeamId, + TournamentId, +) def test_determine_ranking_for_stage_item_elimination() -> None: @@ -21,8 +30,10 @@ def test_determine_ranking_for_stage_item_elimination() -> None: StageItemWithRounds( rounds=[ RoundWithMatches( + id=RoundId(-1), matches=[ MatchWithDetailsDefinitive( + id=MatchId(-1), team1=FullTeamWithPlayers( **DUMMY_TEAM1.model_dump(), players=[], id=TeamId(-1) ), @@ -37,6 +48,7 @@ def test_determine_ranking_for_stage_item_elimination() -> None: team2_score=0, ), MatchWithDetailsDefinitive( + id=MatchId(-1), team1=FullTeamWithPlayers( **DUMMY_TEAM1.model_dump(), players=[], id=TeamId(-1) ), @@ -51,6 +63,7 @@ def test_determine_ranking_for_stage_item_elimination() -> None: team2_score=2, ), MatchWithDetails( # This gets ignored in ranking calculation + id=MatchId(-1), created=now, duration_minutes=90, margin_minutes=15, @@ -66,8 +79,12 @@ def test_determine_ranking_for_stage_item_elimination() -> None: ) ], inputs=[ - StageItemInputFinal(team_id=TeamId(-1), slot=1, tournament_id=tournament_id), - StageItemInputFinal(team_id=TeamId(-2), slot=2, tournament_id=tournament_id), + StageItemInputFinal( + id=StageItemInputId(-1), team_id=TeamId(-1), slot=1, tournament_id=tournament_id + ), + StageItemInputFinal( + id=StageItemInputId(-2), team_id=TeamId(-2), slot=2, tournament_id=tournament_id + ), ], type_name="Single Elimination", team_count=4, @@ -103,8 +120,10 @@ def test_determine_ranking_for_stage_item_swiss() -> None: StageItemWithRounds( rounds=[ RoundWithMatches( + id=RoundId(-1), matches=[ MatchWithDetailsDefinitive( + id=MatchId(-1), team1=FullTeamWithPlayers( **DUMMY_TEAM1.model_dump(), players=[], id=TeamId(-1) ), @@ -119,6 +138,7 @@ def test_determine_ranking_for_stage_item_swiss() -> None: team2_score=0, ), MatchWithDetailsDefinitive( + id=MatchId(-2), team1=FullTeamWithPlayers( **DUMMY_TEAM1.model_dump(), players=[], id=TeamId(-1) ), @@ -133,6 +153,7 @@ def test_determine_ranking_for_stage_item_swiss() -> None: team2_score=2, ), MatchWithDetails( # This gets ignored in ranking calculation + id=MatchId(-3), created=now, duration_minutes=90, margin_minutes=15, @@ -148,8 +169,12 @@ def test_determine_ranking_for_stage_item_swiss() -> None: ) ], inputs=[ - StageItemInputFinal(team_id=TeamId(-1), slot=1, tournament_id=tournament_id), - StageItemInputFinal(team_id=TeamId(-2), slot=2, tournament_id=tournament_id), + StageItemInputFinal( + id=StageItemInputId(-1), team_id=TeamId(-1), slot=1, tournament_id=tournament_id + ), + StageItemInputFinal( + id=StageItemInputId(-2), team_id=TeamId(-2), slot=2, tournament_id=tournament_id + ), ], type_name="Swiss", team_count=4, @@ -185,6 +210,7 @@ def test_determine_ranking_for_stage_item_swiss_no_matches() -> None: StageItemWithRounds( rounds=[ RoundWithMatches( + id=RoundId(-1), matches=[], stage_item_id=StageItemId(-1), created=now, @@ -193,8 +219,12 @@ def test_determine_ranking_for_stage_item_swiss_no_matches() -> None: ) ], inputs=[ - StageItemInputFinal(team_id=TeamId(-1), slot=1, tournament_id=tournament_id), - StageItemInputFinal(team_id=TeamId(-2), slot=2, tournament_id=tournament_id), + StageItemInputFinal( + id=StageItemInputId(-1), team_id=TeamId(-1), slot=1, tournament_id=tournament_id + ), + StageItemInputFinal( + id=StageItemInputId(-2), team_id=TeamId(-2), slot=2, tournament_id=tournament_id + ), ], type_name="Swiss", team_count=4, diff --git a/backend/tests/unit_tests/swiss_test.py b/backend/tests/unit_tests/swiss_test.py index 7f03a4c6..c6db1fc2 100644 --- a/backend/tests/unit_tests/swiss_test.py +++ b/backend/tests/unit_tests/swiss_test.py @@ -5,7 +5,7 @@ from fastapi import HTTPException from bracket.logic.scheduling.ladder_teams import get_possible_upcoming_matches_for_swiss from bracket.models.db.match import Match, MatchFilter, MatchWithDetailsDefinitive, SuggestedMatch -from bracket.models.db.team import FullTeamWithPlayers, Team +from bracket.models.db.team import FullTeamWithPlayers, TeamInsertable from bracket.models.db.util import RoundWithMatches from bracket.utils.dummy_records import ( DUMMY_MATCH1, @@ -14,7 +14,7 @@ from bracket.utils.dummy_records import ( DUMMY_TEAM3, DUMMY_TEAM4, ) -from bracket.utils.id_types import StageItemId, TeamId +from bracket.utils.id_types import MatchId, RoundId, StageItemId, TeamId from tests.integration_tests.mocks import MOCK_NOW MATCH_FILTER = MatchFilter(elo_diff_threshold=50, iterations=100, limit=20, only_recommended=False) @@ -25,7 +25,7 @@ def test_no_draft_round() -> None: get_possible_upcoming_matches_for_swiss(MATCH_FILTER, [], []) -def get_team(team: Team, team_id: TeamId) -> FullTeamWithPlayers: +def get_team(team: TeamInsertable, team_id: TeamId) -> FullTeamWithPlayers: return FullTeamWithPlayers( id=team_id, **team.model_dump(exclude={"id"}), @@ -60,14 +60,26 @@ def test_constraints() -> None: rounds = [ RoundWithMatches( - matches=[get_match(DUMMY_MATCH1, team1, team2)], + id=RoundId(-1), + matches=[ + get_match( + Match.model_validate(DUMMY_MATCH1.model_dump() | {"id": MatchId(-1)}), + team1, + team2, + ) + ], is_draft=False, stage_item_id=StageItemId(-1), name="R1", created=MOCK_NOW, ), RoundWithMatches( - matches=[], is_draft=True, stage_item_id=StageItemId(-1), name="R2", created=MOCK_NOW + id=RoundId(-1), + matches=[], + is_draft=True, + stage_item_id=StageItemId(-1), + name="R2", + created=MOCK_NOW, ), ] teams = [team1, team2, team3, team4]