fix(proxy): remove a bulk-deleted user's email-only roster entries without touching same-email teammates

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
ryan 2026-09-14 06:08:42 +00:00
parent dbb4de7bc2
commit c25498b66d
2 changed files with 33 additions and 7 deletions

View file

@ -150,16 +150,20 @@ def _org_membership_tx_db(tx: "Prisma") -> "TableActions[prisma_models.LiteLLM_O
return tx.litellm_organizationmembership # pyright: ignore[reportReturnType] # TableActions widens the generated inputs to Mapping, as the repositories do
def _same_email(email: str | None, request: MemberDeleteRequest) -> bool:
return request.user_email is not None and request.user_email == email
def _addresses_member(member: Member, request: MemberDeleteRequest) -> bool:
return (request.user_id is not None and request.user_id == member.user_id) or (
request.user_email is not None and request.user_email == member.user_email
)
if request.user_id is None:
return _same_email(member.user_email, request)
return request.user_id == member.user_id or (member.user_id is None and _same_email(member.user_email, request))
def _addresses_user(user: "prisma_models.LiteLLM_UserTable", request: MemberDeleteRequest) -> bool:
return (request.user_id is not None and request.user_id == user.user_id) or (
request.user_email is not None and request.user_email == user.user_email
)
if request.user_id is None:
return _same_email(user.user_email, request)
return request.user_id == user.user_id
def _error_message(exc: BaseException) -> str:
@ -395,7 +399,11 @@ async def _delete_users_tx(
prisma_client,
tx,
tid,
tuple(MemberDeleteRequest(user_id=u.user_id) for u in users if tid in teams_of[u.user_id]),
tuple(
MemberDeleteRequest(user_id=u.user_id, user_email=u.user_email)
for u in users
if tid in teams_of[u.user_id]
),
user_api_key_dict,
)
for tid in team_ids

View file

@ -308,6 +308,24 @@ async def test_bulk_delete_leaves_teammates_who_share_the_deleted_users_email_al
assert [t["token"] for t in prisma.db.litellm_verificationtoken.rows] == ["k-twin"]
@pytest.mark.asyncio
async def test_bulk_delete_removes_the_deleted_users_email_only_roster_entry():
team = LiteLLM_TeamTable(
team_id="t1",
members_with_roles=[
Member(user_id=None, user_email="u1@example.com", role="user"),
Member(user_id="keep", user_email="keep@example.com", role="user"),
],
)
prisma = _FakePrisma(users=[_user("u1", "t1"), _user("keep", "t1")], teams=[team])
response = await _delete(prisma, ["u1"])
assert [(r.success, r.teams_removed) for r in response.results] == [(True, ("t1",))]
assert _roster(prisma, "t1") == ["keep"]
assert set(prisma.db.litellm_usertable.rows) == {"keep"}
@pytest.mark.asyncio
async def test_bulk_delete_finds_teams_through_membership_rows_when_user_teams_array_is_stale():
prisma = _FakePrisma(