Skip to content

fix: refactor user Redis keys to prevent WRONGTYPE error in admin panel#816

Merged
PythonFZ merged 1 commit intomainfrom
fix/admin-panel-list-users
Dec 17, 2025
Merged

fix: refactor user Redis keys to prevent WRONGTYPE error in admin panel#816
PythonFZ merged 1 commit intomainfrom
fix/admin-panel-list-users

Conversation

@PythonFZ
Copy link
Copy Markdown
Member

@PythonFZ PythonFZ commented Dec 17, 2025

The list_all_users method was scanning user:* which also matched user:{username}:visited_rooms keys (Redis Sets). Calling hgetall() on Sets causes WRONGTYPE errors.

Refactored key structure to users:{type}:{username} format:

  • users:data:{username} - user data hash
  • users:admin:{username} - admin status
  • users:visited:{username} - visited rooms set

This allows efficient scanning of users:data:* without conflicts.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor
    • Improved consistency of internal data handling mechanisms across multiple backend services to enhance reliability and maintainability.

✏️ Tip: You can customize this high-level summary in your review settings.

The list_all_users method was scanning user:* which also matched
user:{username}:visited_rooms keys (Redis Sets). Calling hgetall()
on Sets causes WRONGTYPE errors.

Refactored key structure to users:{type}:{username} format:
- users:data:{username} - user data hash
- users:admin:{username} - admin status
- users:visited:{username} - visited rooms set

This allows efficient scanning of users:data:* without conflicts.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 17, 2025

Walkthrough

The changes refactor Redis key management by introducing a UserKeys class that centralizes key generation and extraction logic. Hard-coded key strings are replaced throughout the codebase with dedicated methods like hash_key(), admin_key(), and visited_rooms(), along with utility functions for pattern matching and username extraction.

Changes

Cohort / File(s) Summary
Core key abstraction
src/zndraw/app/redis_keys.py
Introduces UserKeys class with instance methods (hash_key(), admin_key(), visited_rooms()) and static methods (data_pattern(), admin_pattern(), username_from_data_key(), username_from_admin_key()) to centralize Redis key generation and extraction.
Service layer refactoring
src/zndraw/services/admin_service.py, src/zndraw/services/client_service.py, src/zndraw/services/user_service.py
Updates service methods to use UserKeys helper instead of hard-coded key strings. Replaces patterns like user:<name> with keys.hash_key() and scans now use UserKeys.data_pattern() and UserKeys.admin_pattern() with username extraction utilities.
Test updates
tests/services/test_client_service.py, tests/test_admin_service.py, tests/test_auth_integration.py, tests/test_room_management.py, tests/test_user_service.py
Test assertions updated to construct UserKeys instances and use derived key methods (hash_key(), admin_key(), visited_rooms()) instead of hard-coded key strings. Adds new test for visited rooms edge case.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Areas requiring attention:
    • Consistency of UserKeys instantiation and method usage across all service layer and test files
    • Verification that pattern matching methods (data_pattern(), admin_pattern()) correctly capture all intended keys
    • Username extraction logic in username_from_data_key() and username_from_admin_key() must handle edge cases in key format parsing
    • New test test_list_all_users_with_visited_rooms should validate collision prevention between user hash and visited_rooms set keys

Poem

🐰 Keys once scattered, scattered wide,
Now in classes they reside!
UserKeys guides the way,
Hash and admin in array,
Redis strings? Refactored pride! ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: refactoring user Redis keys to resolve WRONGTYPE errors in the admin panel, which is the primary objective of this PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/admin-panel-list-users

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2f3d6f1 and 22299d0.

📒 Files selected for processing (9)
  • src/zndraw/app/redis_keys.py (2 hunks)
  • src/zndraw/services/admin_service.py (5 hunks)
  • src/zndraw/services/client_service.py (4 hunks)
  • src/zndraw/services/user_service.py (1 hunks)
  • tests/services/test_client_service.py (9 hunks)
  • tests/test_admin_service.py (2 hunks)
  • tests/test_auth_integration.py (2 hunks)
  • tests/test_room_management.py (2 hunks)
  • tests/test_user_service.py (8 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/*.py: If sensible, implement collections.abc interfaces for classes, such as MutableMapping or MutableSequence
Use numpy style docstrings
Docstrings must be concise and to the point
Use type hints wherever possible. Import typing as t if necessary, but use list[int|float] | None instead of t.Optional[t.List[int|float]]
Imports should always be at the top of the file

Files:

  • src/zndraw/services/user_service.py
  • tests/test_room_management.py
  • tests/test_auth_integration.py
  • src/zndraw/services/client_service.py
  • tests/test_admin_service.py
  • src/zndraw/app/redis_keys.py
  • src/zndraw/services/admin_service.py
  • tests/test_user_service.py
  • tests/services/test_client_service.py
**/test_*.py

📄 CodeRabbit inference engine (AGENTS.md)

**/test_*.py: Use pytest.mark.parametrize to avoid code duplication in tests
Tests should be very specific and test only one thing
Avoid complex test setups
Each test must be a function, not a method of a class

Files:

  • tests/test_room_management.py
  • tests/test_auth_integration.py
  • tests/test_admin_service.py
  • tests/test_user_service.py
  • tests/services/test_client_service.py
🧬 Code graph analysis (8)
src/zndraw/services/user_service.py (1)
src/zndraw/app/redis_keys.py (3)
  • UserKeys (484-530)
  • data_pattern (513-515)
  • username_from_data_key (523-525)
tests/test_room_management.py (1)
src/zndraw/app/redis_keys.py (2)
  • UserKeys (484-530)
  • admin_key (504-506)
tests/test_auth_integration.py (1)
src/zndraw/app/redis_keys.py (3)
  • UserKeys (484-530)
  • hash_key (500-502)
  • hash_key (592-594)
src/zndraw/services/client_service.py (1)
src/zndraw/app/redis_keys.py (4)
  • UserKeys (484-530)
  • hash_key (500-502)
  • hash_key (592-594)
  • visited_rooms (508-510)
tests/test_admin_service.py (2)
src/zndraw/app/redis_keys.py (2)
  • UserKeys (484-530)
  • admin_key (504-506)
src/zndraw/services/admin_service.py (4)
  • revoke_admin (93-103)
  • is_admin (105-128)
  • AdminService (15-146)
  • grant_admin (81-91)
src/zndraw/services/admin_service.py (1)
src/zndraw/app/redis_keys.py (4)
  • UserKeys (484-530)
  • admin_key (504-506)
  • admin_pattern (518-520)
  • username_from_admin_key (528-530)
tests/test_user_service.py (1)
src/zndraw/app/redis_keys.py (4)
  • UserKeys (484-530)
  • hash_key (500-502)
  • hash_key (592-594)
  • visited_rooms (508-510)
tests/services/test_client_service.py (1)
src/zndraw/app/redis_keys.py (3)
  • UserKeys (484-530)
  • hash_key (500-502)
  • hash_key (592-594)
🪛 Ruff (0.14.8)
tests/test_admin_service.py

268-268: Possible hardcoded password assigned to argument: "admin_password"

(S106)

tests/test_user_service.py

139-139: Possible hardcoded password assigned to: "password"

(S105)


162-162: Possible hardcoded password assigned to: "password"

(S105)


501-501: Possible hardcoded password assigned to: "same_password"

(S105)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: pytest (3.11, ubuntu-latest)
  • GitHub Check: pytest (3.13, ubuntu-latest)
  • GitHub Check: pytest (3.12, ubuntu-latest)
🔇 Additional comments (36)
src/zndraw/app/redis_keys.py (2)

8-8: LGTM!

The ClassVar import is correctly placed and necessary for the new UserKeys class implementation.


483-530: LGTM! Well-designed key abstraction.

The UserKeys class provides a clean, centralized approach to Redis key management:

  • ClassVar correctly prevents dataclass field treatment
  • frozen=True ensures immutability
  • Consistent key structure with clear documentation
  • Type hints and docstrings throughout
src/zndraw/services/user_service.py (1)

411-415: LGTM! Correctly resolves the WRONGTYPE error.

The refactoring to use UserKeys.data_pattern() ensures that list_all_users only scans user data hashes (users:data:*) and won't accidentally match other key types like users:visited:* sets, which was causing the WRONGTYPE error mentioned in the PR description.

src/zndraw/services/admin_service.py (5)

10-10: LGTM!

Import of UserKeys is correctly placed at the top of the file.


89-91: LGTM!

Correctly uses UserKeys abstraction for admin key management.


101-103: LGTM!

Correctly uses UserKeys abstraction for admin key management.


126-128: LGTM!

Correctly uses UserKeys abstraction for admin key lookups.


139-145: LGTM! Correctly handles admin key scanning.

The refactoring properly uses UserKeys.admin_pattern() to scan only admin keys and username_from_admin_key() to extract usernames. The bytes-to-string decoding is correctly handled.

src/zndraw/services/client_service.py (4)

11-11: LGTM!

Import of UserKeys is correctly placed at the top of the file.


41-43: LGTM!

Correctly uses UserKeys abstraction for user data hash access.


97-103: LGTM! Correctly tracks visited rooms.

The refactoring properly uses keys.visited_rooms() to maintain the set of visited rooms separately from user data hashes, which is key to preventing the WRONGTYPE error.


118-120: LGTM!

Correctly uses UserKeys abstraction for visited rooms set access.

tests/test_room_management.py (1)

788-806: LGTM! Test correctly uses the new key abstraction.

The test properly instantiates UserKeys and uses admin_key() to grant admin privileges, consistent with the refactored key management.

tests/test_auth_integration.py (1)

195-213: LGTM! Test correctly uses the new key abstraction.

The test properly instantiates UserKeys and uses hash_key() for Redis operations, consistent with the refactored key management.

tests/services/test_client_service.py (13)

11-11: LGTM!

Import of UserKeys is correctly placed at the top of the file.


18-21: LGTM!

Test correctly uses UserKeys abstraction for assertions.


27-31: LGTM!

Test correctly uses UserKeys abstraction for assertions.


37-41: LGTM!

Test correctly uses UserKeys abstraction for assertions.


51-53: LGTM!

Test correctly uses UserKeys abstraction for assertions.


59-62: LGTM!

Test correctly uses UserKeys abstraction for assertions.


78-80: LGTM!

Test correctly uses UserKeys abstraction for assertions.


238-243: LGTM!

Test correctly uses UserKeys abstraction for assertions.


270-277: LGTM!

Test correctly uses UserKeys abstraction for assertions.


288-293: LGTM!

Test correctly uses UserKeys abstraction for assertions.


352-357: LGTM!

Test correctly uses UserKeys abstraction and validates the new key format.


387-393: LGTM!

Test correctly uses UserKeys abstraction for assertions.


433-439: LGTM!

Test correctly uses UserKeys abstraction for assertions with special characters.

tests/test_user_service.py (7)

52-62: LGTM!

Test correctly uses UserKeys abstraction for assertions.


136-150: LGTM!

Test correctly uses UserKeys abstraction for password-related assertions.


158-179: LGTM!

Test correctly uses UserKeys abstraction for registration with username change.


223-246: LGTM!

Test correctly uses UserKeys abstraction to verify timestamp preservation.


362-381: LGTM!

Test correctly uses UserKeys abstraction for timestamp assertions.


467-493: LGTM! Excellent regression test for the WRONGTYPE error fix.

This test validates the core issue addressed by this PR. By creating a visited_rooms set alongside user data, it ensures that list_all_users() correctly scans only users:data:* keys and doesn't attempt hgetall() on set-type keys. The comment clearly explains the bug scenario.


497-522: LGTM!

Test correctly uses UserKeys abstraction for password hash assertions.

tests/test_admin_service.py (2)

110-111: LGTM! Correct usage of UserKeys API.

The test properly verifies the admin key format after granting and checks deletion after revoking. The validation correctly handles both string and bytes representations of "1".

Also applies to: 118-118


272-272: LGTM! Proper validation of new key format and old format absence.

The test correctly validates that the new users:admin:{username} format is used and the legacy admin:client:{user_name} format is not present. This is a good regression test to ensure the migration is complete.

Note: The duplicate import at line 263-264 was already flagged in the earlier comment.

Also applies to: 276-278

Comment on lines +108 to +109
from zndraw.app.redis_keys import UserKeys

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Move import to top of file.

According to coding guidelines, imports should always be at the top of the file, not inside functions. The same import appears again at line 263, creating duplication.

Apply this diff to move the import to the file header:

 """Tests for AdminService."""

 import pytest
 from znsocket import MemoryStorage

 from zndraw.services import AdminService
+from zndraw.app.redis_keys import UserKeys

Then remove the local imports at lines 108-109 and 263-264.

As per coding guidelines, imports should always be at the top of the file.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In tests/test_admin_service.py around lines 108-109 and 263-264, the import
"from zndraw.app.redis_keys import UserKeys" is inside the body and duplicated;
move that import to the top-of-file imports section (file header) and remove the
two local imports at lines 108-109 and 263-264 so the module is imported only
once at the top per coding guidelines.

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Dec 17, 2025

Codecov Report

❌ Patch coverage is 97.50000% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 78.29%. Comparing base (2f3d6f1) to head (22299d0).
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
src/zndraw/services/client_service.py 75.00% 2 Missing ⚠️
src/zndraw/services/admin_service.py 91.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #816      +/-   ##
==========================================
+ Coverage   78.26%   78.29%   +0.03%     
==========================================
  Files         153      153              
  Lines       18396    18461      +65     
==========================================
+ Hits        14397    14454      +57     
- Misses       3999     4007       +8     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@PythonFZ PythonFZ merged commit 71bef27 into main Dec 17, 2025
6 checks passed
@PythonFZ PythonFZ deleted the fix/admin-panel-list-users branch December 17, 2025 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants