Reconcile legacy avatars.selected on avatar update events#1098
Reconcile legacy avatars.selected on avatar update events#1098Brutus5000 wants to merge 1 commit into
Conversation
The avatar lookup falls back to the legacy `avatars.selected = 1` row when `login.avatar_id` is null. Since an avatar-update event means the API (the authoritative writer of `login.avatar_id`) changed the selection, clearing the avatar (avatar_id set to null) was undone by the stale legacy flag. refresh_player_avatar now reconciles `avatars.selected` with `login.avatar_id` before re-reading, so an explicit clear sticks and a set lazily migrates the legacy flag. Login (fetch_player_data) still uses the fallback for players who have not yet touched the new system. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
📝 WalkthroughWalkthrough
Legacy Avatar Selection Reconciliation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@tests/unit_tests/test_player_service.py`:
- Around line 111-115: The legacy-avatar clear-path assertion in the player
service test is vacuous because all(...) will pass on an empty result. Update
the test around the _db.acquire() query for avatars.c.selected and
avatars.c.idUser == 50 to first assert that result contains at least one row,
then verify all returned rows are not selected so the test fails if no legacy
avatar rows exist.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: e6d2f9b5-0ebf-4370-82ae-954fe05d3ebd
📒 Files selected for processing (2)
server/player_service.pytests/unit_tests/test_player_service.py
| async with player_service._db.acquire() as conn: | ||
| result = await conn.execute( | ||
| select(avatars.c.selected).where(avatars.c.idUser == 50) | ||
| ) | ||
| assert all(not row.selected for row in result) |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
Make the clear-path assertion non-vacuous.
all(...) passes on an empty result set, so this test would still pass if player 50 no longer had any legacy avatar rows. Assert that rows exist before checking they were cleared.
Proposed test hardening
async with player_service._db.acquire() as conn:
result = await conn.execute(
select(avatars.c.selected).where(avatars.c.idUser == 50)
)
- assert all(not row.selected for row in result)
+ selected = [bool(row.selected) for row in result]
+ assert selected
+ assert all(not is_selected for is_selected in selected)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| async with player_service._db.acquire() as conn: | |
| result = await conn.execute( | |
| select(avatars.c.selected).where(avatars.c.idUser == 50) | |
| ) | |
| assert all(not row.selected for row in result) | |
| async with player_service._db.acquire() as conn: | |
| result = await conn.execute( | |
| select(avatars.c.selected).where(avatars.c.idUser == 50) | |
| ) | |
| selected = [bool(row.selected) for row in result] | |
| assert selected | |
| assert all(not is_selected for is_selected in selected) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/unit_tests/test_player_service.py` around lines 111 - 115, The
legacy-avatar clear-path assertion in the player service test is vacuous because
all(...) will pass on an empty result. Update the test around the _db.acquire()
query for avatars.c.selected and avatars.c.idUser == 50 to first assert that
result contains at least one row, then verify all returned rows are not selected
so the test fails if no legacy avatar rows exist.
Problem
Clearing one's avatar via the API doesn't stick. The API sets
login.avatar_id = NULLand publishessuccess.player_avatar.update(verified: Elide fires the update lifecycle hook on the relationship DELETE too). The lobby consumes the event and refreshes — but_avatar_grant_join_onclausefalls back to the legacyavatars.selected = 1row whenlogin.avatar_id IS NULL, so the just-cleared avatar gets resurrected from the stale legacy flag.Observed:
Root cause:
login.avatar_id = NULLis ambiguous — it means both "never migrated" and "explicitly cleared" — and the fallback always resolves it toward the legacy flag.Fix
An avatar-update event means the API (the authoritative writer of
login.avatar_id) just changed this player's selection. Sorefresh_player_avatarnow reconcilesavatars.selectedwithlogin.avatar_idbefore re-reading: the granted row matchingavatar_idis marked selected, all others deselected (all deselected whenavatar_idis null).avatar_idnull → legacy flags cleared → no avatar (sticks).fetch_player_data) is unchanged, so players who haven't touched the new system still get their legacy-selected avatar via the fallback until their first update event.Tests
test_refresh_player_avatar_connected— set case: authoritativeavatar_idwins and the legacy flag is reconciled to it.test_refresh_player_avatar_clears_legacy_fallback— clear case: no avatar, legacy flags cleaned.test_player_service.py+test_avatar_change_queue_service.pypass (24 tests) against a v144 schema.Summary by CodeRabbit