fix: clear team assignments before appending in teams/save #174
No reviewers
Labels
No labels
domain:backend
domain:devops
domain:frontend
status:approved
status:in-progress
status:needs-fix
status:qa
type:bug
type:devops
type:feature
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
forgejo_admin/basketball-api!174
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "173-fix-teams-save-replace-not-append"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
The
POST /admin/teams/saveendpoint used append-only logic for team assignments, causing players to accumulate multiple teams in theplayer_teamsjunction table instead of being moved to the new team. This addsplayer.teams.clear()beforeplayer.teams.append()in both the temp-team and existing-team branches, enforcing single-team draft board semantics.Changes
src/basketball_api/routes/admin.py-- Inadmin_teams_save(), changed the idempotency check from "skip if player is already on this team" (real_team.id not in cids) to "skip if player is exclusively on this team" (current_ids != {target_id}). When a change is needed, existing team memberships are cleared before the new assignment is appended. Applied to both the temp-team branch (line ~749) and the existing-team branch (line ~769).Test Plan
test_temp_team_dedup-- previously failing, now passes (player ends up with 1 team, not 2)test_no_change_not_counted-- still passes (assigning to current team is a no-op)test_admin_teams.pytests passruff formatandruff checkcleanReview Checklist
player.teams.clear()called before appending in both temp-team and existing-team branchesRelated
Review: LGTM
Diff: +6/-4 in 1 file -- minimal, surgical fix.
Correctness
real_team.id not in cids) only tested whether the target team was already present, but never removed other teams. A player with team A assigned to team B would pass the check (B not in {A}), append B, and end up with {A, B}.current_ids != {real_team.id}) correctly tests whether the player is exclusively on the target team. If not, it clears all memberships first, then appends. This enforces single-team draft board semantics.Idempotency
current_ids == {target_id}evaluates true, the block is skipped, andupdatedis not incremented. This preserves thetest_no_change_not_countedbehavior.Edge cases considered
current_ids = set(),set() != {team_id}is true,clear()on empty list is a no-op, append works. Correct.clear()removes all, append adds the one. Correct.Test coverage
test_temp_team_dedupnow passes. No regressions.No fixes needed. Ready for merge.
PR #174 Review
DOMAIN REVIEW
Tech stack: Python / FastAPI / SQLAlchemy (many-to-many relationship via
player_teamsjunction table).What the fix does: The
admin_teams_save()endpoint previously used an append-only check (real_team.id not in cids) which silently accumulated multiple team assignments in theplayer_teamsjunction table. The fix changes both the temp-team and existing-team branches to:current_ids != {target_id}) -- preserving idempotency.player.teams.clear()beforeplayer.teams.append(...)-- enforcing single-team semantics.Correctness analysis:
player.team_idproperty (line 229 inmodels.py) returnsself.teams[0].id if self.teams else None, confirming the codebase assumes at most one team per player. The clear-then-append pattern is the correct fix for this invariant.current_ids != {real_team.id}is semantically correct: if the player is exclusively on the target team already, no work is done andupdatedis not incremented. This is a tighter check than the oldnot in-- it also triggers reassignment when the player is on multiple teams (cleanup of prior corruption), which is desirable.tid is None) was already usingplayer.teams.clear()and is unchanged -- correct.lazy="selectin"on the relationship meansplayer.teamsis loaded eagerly, so the{t.id for t in player.teams}comprehension will not cause N+1 query issues within the loop.SQLAlchemy session safety:
player.teams.clear()followed byplayer.teams.append(real_team)within the same transaction (committed at line 777) is safe. SQLAlchemy will issue DELETE then INSERT on the junction table within the flush.No issues found in domain review.
BLOCKERS
None.
test_temp_team_dedup,test_no_change_not_counted) validate the corrected behavior. 17 tests intest_admin_teams.pycover happy path, edge cases (empty assignments, nonexistent player/team, mixed operations, string integer team IDs), and auth (401 without auth).team_idandplayer_idare validated (404 for missing player/team, 422 for invalid team_id format).require_admindependency is used consistently.NITS
The variable
cidswas renamed tocurrent_idsin both branches -- good improvement for readability. No further naming concerns.The two branches (temp-team at line ~750 and existing-team at line ~771) share identical clear-then-append logic. A future refactor could extract a
_reassign_player(player, team)helper, but for a 2-line pattern this is acceptable as-is and would be scope creep for a bug fix PR.SOP COMPLIANCE
173-fix-teams-save-replace-not-appendreferences issue #173)Closes #173) and downstream dependency (#172)test_admin_teams.py).envfiles, or credentials committedfix: clear team assignments before appending in teams/save)ruff formatandruff checkclean per PR bodyPROCESS OBSERVATIONS
VERDICT: APPROVED