Skip to content

Commit 2a7b78b

Browse files
committed
Fixed useEffect loic bug for auto reset on models in create team functionality
1 parent f32f319 commit 2a7b78b

File tree

2 files changed

+194
-12
lines changed

2 files changed

+194
-12
lines changed

tests/test_team.py

Lines changed: 194 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -249,7 +249,7 @@ async def new_team(session, i, user_id=None, member_list=None, model_aliases=Non
249249
headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"}
250250
data = {"team_alias": "my-new-team"}
251251
if user_id is not None:
252-
data["members_with_roles"] = [{"role": "user", "user_id": user_id}]
252+
data["members_with_roles"] = [{"role": "user", "user_id": user_id}] # type: ignore
253253
elif member_list is not None:
254254
data["members_with_roles"] = member_list
255255

@@ -342,7 +342,7 @@ async def test_team_new():
342342
"""
343343
user_id = f"{uuid.uuid4()}"
344344
async with aiohttp.ClientSession() as session:
345-
new_user(session=session, i=0, user_id=user_id)
345+
await new_user(session=session, i=0, user_id=user_id)
346346
tasks = [new_team(session, i, user_id=user_id) for i in range(1, 11)]
347347
await asyncio.gather(*tasks)
348348

@@ -473,7 +473,7 @@ async def test_team_update_sc_2():
473473
if (
474474
k == "members_with_roles"
475475
): # assert 1 more member (role: "user", user_email: $user_email)
476-
len(new_team_data["data"][k]) == len(team_data[k]) + 1
476+
assert len(new_team_data["data"][k]) == len(team_data[k]) + 1
477477
elif (
478478
k == "created_at"
479479
or k == "updated_at"
@@ -608,16 +608,24 @@ async def test_member_delete(dimension):
608608
team_data = await new_team(session=session, i=0, member_list=member_list)
609609

610610
user_in_team = False
611-
for member in team_data["members_with_roles"]:
612-
if dimension == "user_id" and member["user_id"] == normal_user:
613-
user_in_team = True
614-
elif (
615-
dimension == "user_email" and member["user_email"] == normal_user_email
616-
):
617-
user_in_team = True
611+
updated_team_data = None
612+
if dimension == "user_id":
613+
updated_team_data = await delete_member(
614+
session=session, i=0, team_id=team_data["team_id"], user_id=normal_user
615+
)
616+
elif dimension == "user_email":
617+
updated_team_data = await delete_member(
618+
session=session,
619+
i=0,
620+
team_id=team_data["team_id"],
621+
user_email=normal_user_email,
622+
)
623+
624+
if updated_team_data is None:
625+
pytest.fail(f"Failed to delete member for dimension: {dimension}")
618626

619627
assert (
620-
user_in_team is True
628+
user_in_team is False
621629
), "User not in team. Team list={}, User details - id={}, email={}. Dimension={}".format(
622630
team_data["members_with_roles"], normal_user, normal_user_email, dimension
623631
)
@@ -735,3 +743,178 @@ async def test_users_in_team_budget():
735743
]
736744
== 0.0000001
737745
)
746+
747+
748+
@pytest.mark.asyncio
749+
async def test_team_creation_models_persist():
750+
"""
751+
Test that models selected during team creation are preserved and not reset.
752+
753+
This test verifies the fix for the bug where models would get reset
754+
during the team creation process due to useEffect callbacks in the UI.
755+
756+
Bug: When adding models in the create team modal, the models get reset
757+
Fix: Remove automatic form.setFieldValue("models", []) in useEffect
758+
"""
759+
async with aiohttp.ClientSession() as session:
760+
team_alias = f"test-models-persist-{uuid.uuid4()}"
761+
test_models = ["gpt-3.5-turbo", "gpt-4"]
762+
763+
# Create team with specific models using the existing new_team helper
764+
# but with explicit models parameter
765+
url = "http://0.0.0.0:4000/team/new"
766+
headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"}
767+
data = {
768+
"team_alias": team_alias,
769+
"models": test_models
770+
}
771+
772+
async with session.post(url, headers=headers, json=data) as response:
773+
status = response.status
774+
response_text = await response.text()
775+
print(f"Team creation response (Status: {status}): {response_text}")
776+
777+
if status != 200:
778+
raise Exception(f"Team creation failed with status {status}: {response_text}")
779+
780+
team_response = await response.json()
781+
782+
team_id = team_response["team_id"]
783+
784+
# Verify models were properly set using existing get_team_info helper
785+
team_info = await get_team_info(session, team_id, "sk-1234")
786+
created_models = team_info["team_info"]["models"]
787+
788+
assert set(created_models) == set(test_models), (
789+
f"Created team models {created_models} do not match requested models {test_models}"
790+
)
791+
792+
print(f"✅ Team created successfully with models: {created_models}")
793+
794+
795+
@pytest.mark.asyncio
796+
async def test_team_creation_models_stability_during_updates():
797+
"""
798+
Test that models remain stable when other team properties are updated.
799+
800+
This simulates the scenario where models might get reset due to
801+
form state management issues during callbacks.
802+
"""
803+
async with aiohttp.ClientSession() as session:
804+
team_alias = f"test-models-stability-{uuid.uuid4()}"
805+
initial_models = ["gpt-3.5-turbo", "claude-3-sonnet"]
806+
807+
# Create team with initial models
808+
team_response = await new_team(session, 0)
809+
team_id = team_response["team_id"]
810+
811+
# Update team with models first
812+
await update_team(
813+
session=session,
814+
i=0,
815+
team_id=team_id,
816+
models=initial_models
817+
)
818+
819+
# Update team properties (but not models)
820+
await update_team(
821+
session=session,
822+
i=0,
823+
team_id=team_id,
824+
team_alias=f"updated-{team_alias}",
825+
max_budget=200.0,
826+
tpm_limit=1000
827+
)
828+
829+
# Verify models are still intact after update
830+
team_info = await get_team_info(session, team_id, "sk-1234")
831+
current_models = team_info["team_info"]["models"]
832+
833+
assert set(current_models) == set(initial_models), (
834+
f"Models changed after team update. Expected: {initial_models}, Got: {current_models}"
835+
)
836+
837+
print(f"✅ Models remained stable during team updates: {current_models}")
838+
839+
840+
@pytest.mark.asyncio
841+
async def test_team_creation_with_all_proxy_models():
842+
"""
843+
Test team creation with "all-proxy-models" option.
844+
845+
Verifies that the special "all-proxy-models" selection is preserved.
846+
"""
847+
async with aiohttp.ClientSession() as session:
848+
team_alias = f"test-all-models-{uuid.uuid4()}"
849+
850+
# Create team with all-proxy-models using direct API call
851+
url = "http://0.0.0.0:4000/team/new"
852+
headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"}
853+
data = {
854+
"team_alias": team_alias,
855+
"models": ["all-proxy-models"]
856+
}
857+
858+
async with session.post(url, headers=headers, json=data) as response:
859+
status = response.status
860+
response_text = await response.text()
861+
862+
if status != 200:
863+
raise Exception(f"Team creation failed with status {status}: {response_text}")
864+
865+
team_response = await response.json()
866+
867+
team_id = team_response["team_id"]
868+
869+
# Verify all-proxy-models was set
870+
team_info = await get_team_info(session, team_id, "sk-1234")
871+
created_models = team_info["team_info"]["models"]
872+
873+
# When all-proxy-models is selected, the models list should be empty in the backend
874+
# (empty list means access to all models)
875+
assert created_models == [] or "all-proxy-models" in created_models, (
876+
f"Team should have all-proxy-models access, got: {created_models}"
877+
)
878+
879+
print(f"✅ Team created successfully with all-proxy-models access")
880+
881+
882+
@pytest.mark.asyncio
883+
async def test_team_creation_empty_models_list():
884+
"""
885+
Test team creation with empty models list.
886+
887+
Verifies that empty models list is handled correctly and defaults to all-proxy-models.
888+
"""
889+
async with aiohttp.ClientSession() as session:
890+
team_alias = f"test-empty-models-{uuid.uuid4()}"
891+
892+
# Create team with empty models list
893+
url = "http://0.0.0.0:4000/team/new"
894+
headers = {"Authorization": "Bearer sk-1234", "Content-Type": "application/json"}
895+
data = {
896+
"team_alias": team_alias,
897+
"models": []
898+
}
899+
900+
async with session.post(url, headers=headers, json=data) as response:
901+
status = response.status
902+
response_text = await response.text()
903+
904+
if status != 200:
905+
raise Exception(f"Team creation failed with status {status}: {response_text}")
906+
907+
team_response = await response.json()
908+
909+
team_id = team_response["team_id"]
910+
911+
# Verify team was created
912+
team_info = await get_team_info(session, team_id, "sk-1234")
913+
created_models = team_info["team_info"]["models"]
914+
915+
# Empty models list should default to all-proxy-models access
916+
assert created_models == [], (
917+
f"Empty models list should result in empty array (all-proxy access), got: {created_models}"
918+
)
919+
920+
print(f"✅ Team created with empty models list (all-proxy access): {created_models}")

ui/litellm-dashboard/src/components/teams.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,6 @@ const Teams: React.FC<TeamProps> = ({
214214
const models = getOrganizationModels(currentOrgForCreateTeam, userModels);
215215
console.log(`models: ${models}`);
216216
setModelsToPick(models);
217-
form.setFieldValue("models", []);
218217
}, [currentOrgForCreateTeam, userModels]);
219218

220219
// Add this useEffect to fetch guardrails

0 commit comments

Comments
 (0)