diff --git a/litellm/proxy/management_endpoints/scim/scim_v2.py b/litellm/proxy/management_endpoints/scim/scim_v2.py index 5957f290a3d..18a59d79434 100644 --- a/litellm/proxy/management_endpoints/scim/scim_v2.py +++ b/litellm/proxy/management_endpoints/scim/scim_v2.py @@ -2706,11 +2706,16 @@ async def _restore_team_on_roster_member(user_id: str, team_id: str) -> None: async with prisma_client.tx() as tx: await tx.query_raw(TEAM_ADVISORY_LOCK_SQL, team_id) roster: Final = await TeamRepository(prisma_client).get_members_with_roles_locked(tx, team_id) - if roster is not None and any(member.user_id == user_id for member in roster): - await user_tx_db(tx).update_many( - where={"user_id": user_id, "NOT": {"teams": {"has": team_id}}}, - data={"teams": {"push": [team_id]}}, - ) + if roster is None or all(member.user_id != user_id for member in roster): + return + restored: Final = await user_tx_db(tx).update_many( + where={"user_id": user_id, "NOT": {"teams": {"has": team_id}}}, + data={"teams": {"push": [team_id]}}, + ) + if restored: + from litellm.proxy.proxy_server import user_api_key_cache + + await evict_and_broadcast(cache_keys=(user_id,), user_api_key_cache=user_api_key_cache) async def _remove_user_from_team(user_id: str, team_id: str) -> None: diff --git a/tests/unit/proxy/management_endpoints/scim/test_scim_v2_endpoints.py b/tests/unit/proxy/management_endpoints/scim/test_scim_v2_endpoints.py index 2ed2fa538e7..654b5e6dd37 100644 --- a/tests/unit/proxy/management_endpoints/scim/test_scim_v2_endpoints.py +++ b/tests/unit/proxy/management_endpoints/scim/test_scim_v2_endpoints.py @@ -1136,6 +1136,10 @@ async def test_handle_existing_user_by_email_roster_add_already_member_repairs_u tx_context.__aenter__ = AsyncMock(return_value=tx) tx_context.__aexit__ = AsyncMock(return_value=False) mock_prisma_client.tx = mocker.MagicMock(return_value=tx_context) + evict = mocker.patch( # test-quality-ok: cache eviction is module-level, not injectable + "litellm.proxy.management_endpoints.scim.scim_v2.evict_and_broadcast", + AsyncMock(), + ) mocker.patch( # test-quality-ok: the roster helpers resolve the proxy prisma client at module level "litellm.proxy.management_endpoints.scim.scim_v2._get_prisma_client_or_raise_exception", AsyncMock(return_value=mock_prisma_client), @@ -1179,6 +1183,7 @@ async def test_handle_existing_user_by_email_roster_add_already_member_repairs_u ) else: tx.litellm_usertable.update_many.assert_not_awaited() + assert [call.kwargs["cache_keys"] for call in evict.await_args_list] == ([("uid",)] if repaired else []) update_calls = mock_prisma_client.db.litellm_usertable.update.call_args_list assert len(update_calls) == 1