feat(python): add user management methods to IggyClient#3695
feat(python): add user management methods to IggyClient#3695ethanlin01x wants to merge 8 commits into
Conversation
321886e to
6fec2bc
Compare
The Python bindings need these types to expose user management, but only UserStatus was re-exported so far.
These types are needed by the upcoming user management methods on IggyClient. Permissions stay unexposed for now, so UserInfoDetails carries the same fields as UserInfo until the follow-up that maps the Permissions structure.
Expose get_user, get_users, create_user, update_user and delete_user, wrapping the Rust SDK UserClient functions like the existing topic and consumer group bindings. create_user always passes no permissions; the Permissions type mapping is left to a follow-up together with update_permissions, change_password and logout_user.
Cover create, get, list, update and delete against a live server, including default status, inactive users, login with created credentials, numeric and name identifiers, repeated listing stability, and pre-connection and pre-authentication failures.
6fec2bc to
7e57428
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #3695 +/- ##
============================================
- Coverage 73.99% 65.11% -8.88%
Complexity 937 937
============================================
Files 1301 1301
Lines 147393 132516 -14877
Branches 122945 108032 -14913
============================================
- Hits 109063 86292 -22771
- Misses 34858 42722 +7864
- Partials 3472 3502 +30
🚀 New features to boost your workflow:
|
|
The 3 lines Codecov flags as uncovered in foreign/python/src/user.rs are the #[pyclass(...)] and #[pymethods] attribute lines. They are not missing tests. All hand-written logic in the file is fully exercised by tests/test_user.py. Should these attribute lines be brought to 100% as well, or is it fine to leave them as is? |
Fine as it is |
| ) | ||
| assert user_by_name is None | ||
|
|
||
| user_by_id = await iggy_client.get_user(999999) |
There was a problem hiding this comment.
This is not stable. There could be a user that has user id 999999.
There was a problem hiding this comment.
In a continuously used server, the ID could eventually come into existence.
There was a problem hiding this comment.
Fixed. The test now creates a user, deletes it, and asserts the vacated id returns None, so it no longer depends on any hardcoded id.
d41fb76
There was a problem hiding this comment.
Here, you do not test username and password length boundaries. Add tests for the following for update_user and create_user:
- 2-byte and 51-byte usernames failing.
- 2-byte and 101-byte passwords failing.
- Non-ascii characters like Japanese, Korean, Thai, emojis. The choice of languages to test is up to you.
There was a problem hiding this comment.
Please add the error-before-login pattern to the rest of the tests that do not have this.
There was a problem hiding this comment.
For create_user: add the following tests:
- An inactive user cannot log in even with the correct password
- A deleted user cannot start a fresh authenticated session.
For get_user:
- Empty string identifiers failing locally.
- Negative and greater-than-
u32integer identifiers failing.
For get_users:
- It returns the users in a strict ascending order, so please assert that as well if not yet asserted.
- Do not assert absolute counts since the server may contain other users as well.
For update_user:
- Calling with both optional fields as
Noneand verifying the user remains unchanged, because Rust permits that no-op request. - Rejected updates preserving both the original username and status. (could be folded into existing tests)
For delete_user:
- Root deletion failing by username and numeric ID
0. - Deleting the same user twice failing on the second call.
- Deleting an inactive user. (could be folded into existing happy path test)
- A live session owned by the deleted user failing its next operation.
- The deleted username being reusable without old password state.
- The target disappearing from both
get_user()andget_users().
There was a problem hiding this comment.
Please also remove the match=... blocks in error catching blocks since those messages could change in the future.
There was a problem hiding this comment.
Thanks for the detailed review!
-
Added the boundary tests in a671663: 2/51-byte usernames and 2/101-byte passwords failing, plus Japanese, Korean, Thai and emoji credentials within the byte limits succeeding and over-limit ones failing.
-
error-before-login: the parametrized test_user_management_requires_connection_and_auth already covers all five methods. Let me know if you meant additional scenarios beyond that.
-
Added the per-method tests in da1d23a (inactive/deleted login, local identifier validation, ascending get_users order, update noop, and the delete_user cases). One behavioral note: deleting a user does not invalidate its live session outright, and a regular user has no permissions to lose, so the test asserts get_user on itself flipping to None plus privileged calls staying rejected.
-
Removed the match= blocks in 3d899e7; only the exception type is asserted now.
|
|
||
| from .utils import get_server_config, wait_for_ping, wait_for_server | ||
|
|
||
| # Server-side limits: usernames are 3-50 characters, passwords 3-100. |
There was a problem hiding this comment.
It is not 3-50 characters, but 3-50 bytes.
There was a problem hiding this comment.
Fixed the comment and added MIN/MAX constants for both limits in bytes. The byte semantics are now covered by tests as well.
a671663
| assert isinstance(created.id, int) | ||
| assert created.username == username | ||
| assert created.status == UserStatus.Active | ||
| assert created.created_at > 0 |
| assert first_username in usernames | ||
| assert second_username in usernames | ||
| assert all(isinstance(user.id, int) for user in users) | ||
| assert all(user.created_at > 0 for user in users) |
There was a problem hiding this comment.
Do not assert creation date
| first = await iggy_client.get_users() | ||
| second = await iggy_client.get_users() | ||
|
|
||
| assert [user.id for user in first] == [user.id for user in second] |
There was a problem hiding this comment.
For a persistent/shared server, this will not hold.
There was a problem hiding this comment.
Removed the test. Its remaining value (a stable view of a single user) is already covered by the repeated get_user test, and the ordering guarantee is now asserted directly via the strictly ascending id check.
|
Also, please do commit the new changes as new commits. This will help me in understanding what changed after my review. |
|
/author |
Server error messages are not a stable contract and created_at is server generated, so assert neither.
Replace the hardcoded nonexistent id with a freshly vacated one, drop the repeated get_users snapshot comparison that other clients can invalidate, and assert the strictly ascending id order instead.
The server validates username and password length in bytes (3-50 and 3-100 respectively), not characters. Exercise both bounds for create_user and update_user, including multibyte credentials that fit the byte limits and ones that exceed them while staying within the character count.
Inactive and deleted users cannot log in, invalid identifiers fail client-side without a server round trip, an update with no fields is accepted as a noop, and deletion covers root protection, double deletion, removal from listings, live session behavior, and username reuse with fresh credentials.
|
/ready |
Which issue does this PR address?
Closes #3682
Rationale
Adds the missing user management operations to the Python SDK, which previously had no binding beyond
login_userand forced callers to the CLI or another SDK to provision users.What changed?
The Python SDK exposed no part of the Rust
UserClientsurface besideslogin_user, so users could not be listed, inspected, created, updated, or deleted from Python.get_user,get_users,create_user,update_user, anddelete_usernow bind through to the RustUserClient, andUserInfo,UserInfoDetails, andUserStatusare exposed as Python classes. As scoped in the issue,create_useralways passesNonefor permissions; thePermissionsmapping is left to a follow-up together withupdate_permissions,change_password, andlogout_user.Local Execution
AI Usage
Claude was used to help generate and review this PR and all the changes are checked by the human.