diff --git a/litellm/proxy/management_endpoints/access_group_endpoints.py b/litellm/proxy/management_endpoints/access_group_endpoints.py index e73ec5eafc..100b1d2659 100644 --- a/litellm/proxy/management_endpoints/access_group_endpoints.py +++ b/litellm/proxy/management_endpoints/access_group_endpoints.py @@ -153,10 +153,19 @@ async def update_access_group( for field, value in data.model_dump(exclude_unset=True).items(): update_data[field] = value - record = await prisma_client.db.litellm_accessgrouptable.update( - where={"access_group_id": access_group_id}, - data=update_data, - ) + try: + record = await prisma_client.db.litellm_accessgrouptable.update( + where={"access_group_id": access_group_id}, + data=update_data, + ) + except Exception as e: + # Unique constraint violation (e.g. access_group_name already exists). + if "unique constraint" in str(e).lower() or "P2002" in str(e): + raise HTTPException( + status_code=status.HTTP_409_CONFLICT, + detail=f"Access group '{update_data.get('access_group_name', '')}' already exists", + ) + raise return _record_to_response(record) diff --git a/litellm/types/access_group.py b/litellm/types/access_group.py index 09ffd60996..e26ebe0062 100644 --- a/litellm/types/access_group.py +++ b/litellm/types/access_group.py @@ -15,6 +15,7 @@ class AccessGroupCreateRequest(BaseModel): class AccessGroupUpdateRequest(BaseModel): + access_group_name: Optional[str] = None description: Optional[str] = None access_model_names: Optional[List[str]] = None access_mcp_server_ids: Optional[List[str]] = None diff --git a/tests/test_litellm/proxy/management_endpoints/test_access_group_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_access_group_endpoints.py index ee64c534b4..54df8941fa 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_access_group_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_access_group_endpoints.py @@ -431,6 +431,57 @@ def test_update_access_group_empty_body(client_and_mocks): assert call_kwargs["data"]["updated_by"] == "admin_user" +def test_update_access_group_name_success(client_and_mocks): + """Update access_group_name succeeds when new name is unique.""" + client, _, mock_table = client_and_mocks + + existing = _make_access_group_record(access_group_id="ag-update", access_group_name="old-name") + mock_table.find_unique = AsyncMock(return_value=existing) + + resp = client.put("/v1/access_group/ag-update", json={"access_group_name": "new-name"}) + assert resp.status_code == 200 + mock_table.update.assert_awaited_once() + call_kwargs = mock_table.update.call_args.kwargs + assert call_kwargs["data"]["access_group_name"] == "new-name" + + +def test_update_access_group_name_duplicate_conflict(client_and_mocks): + """Update access_group_name to existing name returns 409 (unique constraint).""" + client, _, mock_table = client_and_mocks + + existing = _make_access_group_record(access_group_id="ag-update", access_group_name="old-name") + mock_table.find_unique = AsyncMock(return_value=existing) + mock_table.update = AsyncMock( + side_effect=Exception("Unique constraint failed on the fields: (`access_group_name`)") + ) + + resp = client.put("/v1/access_group/ag-update", json={"access_group_name": "taken-name"}) + assert resp.status_code == 409 + assert "already exists" in resp.json()["detail"] + mock_table.update.assert_awaited_once() + + +@pytest.mark.parametrize( + "error_message", + [ + "Unique constraint failed on the fields: (`access_group_name`)", + "P2002: Unique constraint failed", + "unique constraint violation", + ], +) +def test_update_access_group_name_unique_constraint_returns_409(client_and_mocks, error_message): + """Update access_group_name: Prisma unique constraint surfaces as 409.""" + client, _, mock_table = client_and_mocks + + existing = _make_access_group_record(access_group_id="ag-update", access_group_name="old-name") + mock_table.find_unique = AsyncMock(return_value=existing) + mock_table.update = AsyncMock(side_effect=Exception(error_message)) + + resp = client.put("/v1/access_group/ag-update", json={"access_group_name": "race-name"}) + assert resp.status_code == 409 + assert "already exists" in resp.json()["detail"] + + # --------------------------------------------------------------------------- # DELETE # ---------------------------------------------------------------------------