mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-08 03:08:45 +00:00
test(model_management): cover actor edges and wildcard models
This commit is contained in:
parent
db37977307
commit
db17841b3d
2 changed files with 356 additions and 3 deletions
|
|
@ -1224,11 +1224,11 @@ class TestUpdateModel:
|
||||||
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
|
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
|
||||||
new=AsyncMock(return_value=None),
|
new=AsyncMock(return_value=None),
|
||||||
),
|
),
|
||||||
patch(
|
patch( # test-quality-ok: [TQ008] isolate persistence from encryption implementation
|
||||||
"litellm.proxy.management_endpoints.model_management_endpoints.encrypt_value_helper",
|
"litellm.proxy.management_endpoints.model_management_endpoints.encrypt_value_helper",
|
||||||
side_effect=lambda value: value,
|
side_effect=lambda value: value,
|
||||||
),
|
),
|
||||||
patch(
|
patch( # test-quality-ok: [TQ008] isolate persistence from router reload implementation
|
||||||
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
|
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
|
||||||
new=AsyncMock(
|
new=AsyncMock(
|
||||||
return_value=ReconcileOutcome(still_desired=None, live_after=None)
|
return_value=ReconcileOutcome(still_desired=None, live_after=None)
|
||||||
|
|
@ -4021,7 +4021,7 @@ class TestPatchModelBlockedAuthGate:
|
||||||
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
|
"litellm.proxy.management_endpoints.model_management_endpoints.ModelManagementAuthChecks.can_user_make_model_call",
|
||||||
new=AsyncMock(return_value=None),
|
new=AsyncMock(return_value=None),
|
||||||
),
|
),
|
||||||
patch(
|
patch( # test-quality-ok: [TQ008] isolate persistence from router reload implementation
|
||||||
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
|
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
|
||||||
new=AsyncMock(
|
new=AsyncMock(
|
||||||
return_value=ReconcileOutcome(still_desired=None, live_after=None)
|
return_value=ReconcileOutcome(still_desired=None, live_after=None)
|
||||||
|
|
@ -6631,3 +6631,337 @@ class TestTeamMemberAutoRouterWrites:
|
||||||
assert json.loads(written["model_info"])["member_auto_router"] is True
|
assert json.loads(written["model_info"])["member_auto_router"] is True
|
||||||
assert appended.await_args.kwargs["data"].models == ["new-personal-router"]
|
assert appended.await_args.kwargs["data"].models == ["new-personal-router"]
|
||||||
assert appended.await_args.kwargs["data"].team_id == "member-team"
|
assert appended.await_args.kwargs["data"].team_id == "member-team"
|
||||||
|
|
||||||
|
|
||||||
|
class TestModelManagementActorEdges:
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_model_rejects_non_team_internal_user(self):
|
||||||
|
from litellm.proxy._types import ProxyException
|
||||||
|
from litellm.proxy.management_endpoints.model_management_endpoints import add_new_model
|
||||||
|
|
||||||
|
actor: Final = UserAPIKeyAuth(user_id="internal-user", user_role=LitellmUserRoles.INTERNAL_USER)
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
deployment: Final = Deployment(
|
||||||
|
model_name="internal-model",
|
||||||
|
litellm_params=LiteLLM_Params(model="openai/test-model"),
|
||||||
|
model_info=ModelInfo(id="internal-model-id"),
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.general_settings", {}), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
):
|
||||||
|
with pytest.raises(ProxyException) as exc_info:
|
||||||
|
await add_new_model(model_params=deployment, user_api_key_dict=actor)
|
||||||
|
|
||||||
|
assert str(exc_info.value.code) == "403"
|
||||||
|
assert "permission" in str(exc_info.value).lower()
|
||||||
|
prisma.db.litellm_proxymodeltable.create.assert_not_called()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_model_rejects_proxy_admin_viewer(self):
|
||||||
|
from litellm.proxy._types import ProxyException
|
||||||
|
from litellm.proxy.management_endpoints.model_management_endpoints import add_new_model
|
||||||
|
|
||||||
|
actor: Final = UserAPIKeyAuth(
|
||||||
|
user_id="view-only-user", user_role=LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY
|
||||||
|
)
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
deployment: Final = Deployment(
|
||||||
|
model_name="view-only-model",
|
||||||
|
litellm_params=LiteLLM_Params(model="openai/test-model"),
|
||||||
|
model_info=ModelInfo(id="view-only-model-id"),
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.general_settings", {}), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
):
|
||||||
|
with pytest.raises(ProxyException) as exc_info:
|
||||||
|
await add_new_model(model_params=deployment, user_api_key_dict=actor)
|
||||||
|
|
||||||
|
assert str(exc_info.value.code) == "403"
|
||||||
|
assert "view-only" in str(exc_info.value).lower()
|
||||||
|
prisma.db.litellm_proxymodeltable.create.assert_not_called()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_add_model_requires_database_storage(self):
|
||||||
|
from litellm.proxy._types import ProxyException
|
||||||
|
from litellm.proxy.management_endpoints.model_management_endpoints import add_new_model
|
||||||
|
|
||||||
|
actor: Final = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
deployment: Final = Deployment(
|
||||||
|
model_name="database-disabled-model",
|
||||||
|
litellm_params=LiteLLM_Params(model="openai/test-model"),
|
||||||
|
model_info=ModelInfo(id="database-disabled-model-id"),
|
||||||
|
)
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", False), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.general_settings", {}), # test-quality-ok: [TQ008] endpoint reads proxy-server state through its only test seam
|
||||||
|
):
|
||||||
|
with pytest.raises(ProxyException) as exc_info:
|
||||||
|
await add_new_model(model_params=deployment, user_api_key_dict=actor)
|
||||||
|
|
||||||
|
assert str(exc_info.value.code) == "500"
|
||||||
|
assert "STORE_MODEL_IN_DB" in str(exc_info.value)
|
||||||
|
prisma.db.litellm_proxymodeltable.create.assert_not_called()
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_legacy_model_update_persists_changed_field(self):
|
||||||
|
from litellm.proxy.management_endpoints.model_management_endpoints import update_model
|
||||||
|
|
||||||
|
model_id: Final = "legacy-update-model-id"
|
||||||
|
existing_row: Final = MagicMock()
|
||||||
|
existing_row.litellm_params = {"model": "openai/test-model", "timeout": 30}
|
||||||
|
existing_row.model_dump.return_value = {
|
||||||
|
"model_name": "legacy-update-model",
|
||||||
|
"litellm_params": existing_row.litellm_params,
|
||||||
|
"model_info": {"id": model_id},
|
||||||
|
}
|
||||||
|
existing_row.model_dump_json.return_value = "{}"
|
||||||
|
updated_row: Final = MagicMock()
|
||||||
|
updated_row.model_dump_json.return_value = "{}"
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
prisma.db.litellm_proxymodeltable.find_unique = AsyncMock(return_value=existing_row)
|
||||||
|
prisma.db.litellm_proxymodeltable.update = AsyncMock(return_value=updated_row)
|
||||||
|
router: Final = MagicMock()
|
||||||
|
router.get_model_ids.return_value = [model_id]
|
||||||
|
actor: Final = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] update endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.llm_router", router), # test-quality-ok: [TQ008] update endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: [TQ008] update endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] update endpoint reads proxy-server state through its only test seam
|
||||||
|
patch( # test-quality-ok: [TQ008] isolate persistence from encryption implementation
|
||||||
|
"litellm.proxy.management_endpoints.model_management_endpoints.encrypt_value_helper",
|
||||||
|
side_effect=lambda value: value,
|
||||||
|
),
|
||||||
|
patch( # test-quality-ok: [TQ008] isolate persistence from router reload implementation
|
||||||
|
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
|
||||||
|
new=AsyncMock(return_value=ReconcileOutcome(still_desired=None, live_after=None)),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
await update_model(
|
||||||
|
model_params=updateDeployment(
|
||||||
|
litellm_params=updateLiteLLMParams(timeout=42),
|
||||||
|
model_info=ModelInfo(id=model_id),
|
||||||
|
),
|
||||||
|
user_api_key_dict=actor,
|
||||||
|
)
|
||||||
|
|
||||||
|
written: Final = json.loads(
|
||||||
|
prisma.db.litellm_proxymodeltable.update.await_args.kwargs["data"]["litellm_params"]
|
||||||
|
)
|
||||||
|
assert written["timeout"] == 42
|
||||||
|
assert written["model"] == "openai/test-model"
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_legacy_model_update_explicit_null_preserves_existing_field(self):
|
||||||
|
from litellm.proxy.management_endpoints.model_management_endpoints import update_model
|
||||||
|
|
||||||
|
model_id: Final = "legacy-null-model-id"
|
||||||
|
existing_row: Final = MagicMock()
|
||||||
|
existing_row.litellm_params = {"model": "openai/test-model", "timeout": 30}
|
||||||
|
existing_row.model_dump.return_value = {
|
||||||
|
"model_name": "legacy-null-model",
|
||||||
|
"litellm_params": existing_row.litellm_params,
|
||||||
|
"model_info": {"id": model_id},
|
||||||
|
}
|
||||||
|
existing_row.model_dump_json.return_value = "{}"
|
||||||
|
updated_row: Final = MagicMock()
|
||||||
|
updated_row.model_dump_json.return_value = "{}"
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
prisma.db.litellm_proxymodeltable.find_unique = AsyncMock(return_value=existing_row)
|
||||||
|
prisma.db.litellm_proxymodeltable.update = AsyncMock(return_value=updated_row)
|
||||||
|
router: Final = MagicMock()
|
||||||
|
router.get_model_ids.return_value = [model_id]
|
||||||
|
actor: Final = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] update endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.llm_router", router), # test-quality-ok: [TQ008] update endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: [TQ008] update endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] update endpoint reads proxy-server state through its only test seam
|
||||||
|
patch( # test-quality-ok: [TQ008] isolate persistence from encryption implementation
|
||||||
|
"litellm.proxy.management_endpoints.model_management_endpoints.encrypt_value_helper",
|
||||||
|
side_effect=lambda value: value,
|
||||||
|
),
|
||||||
|
patch( # test-quality-ok: [TQ008] isolate persistence from router reload implementation
|
||||||
|
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
|
||||||
|
new=AsyncMock(return_value=ReconcileOutcome(still_desired=None, live_after=None)),
|
||||||
|
),
|
||||||
|
):
|
||||||
|
await update_model(
|
||||||
|
model_params=updateDeployment(
|
||||||
|
litellm_params=updateLiteLLMParams(timeout=None),
|
||||||
|
model_info=ModelInfo(id=model_id),
|
||||||
|
),
|
||||||
|
user_api_key_dict=actor,
|
||||||
|
)
|
||||||
|
|
||||||
|
written: Final = json.loads(
|
||||||
|
prisma.db.litellm_proxymodeltable.update.await_args.kwargs["data"]["litellm_params"]
|
||||||
|
)
|
||||||
|
assert written["timeout"] == 30
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_patch_model_rejects_config_file_model(self):
|
||||||
|
from litellm.proxy._types import ProxyException
|
||||||
|
from litellm.proxy.management_endpoints.model_management_endpoints import patch_model
|
||||||
|
|
||||||
|
model_id: Final = "config-model-id"
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
prisma.db.litellm_proxymodeltable.find_unique = AsyncMock(return_value=None)
|
||||||
|
prisma.db.litellm_proxymodeltable.update = AsyncMock()
|
||||||
|
router: Final = MagicMock()
|
||||||
|
router.get_deployment.return_value = Deployment(
|
||||||
|
model_name="config-model",
|
||||||
|
litellm_params=LiteLLM_Params(model="openai/test-model"),
|
||||||
|
model_info=ModelInfo(id=model_id),
|
||||||
|
)
|
||||||
|
actor: Final = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] patch endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.llm_router", router), # test-quality-ok: [TQ008] patch endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: [TQ008] patch endpoint reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] patch endpoint reads proxy-server state through its only test seam
|
||||||
|
):
|
||||||
|
with pytest.raises(ProxyException) as exc_info:
|
||||||
|
await patch_model(
|
||||||
|
model_id=model_id,
|
||||||
|
patch_data=updateDeployment(
|
||||||
|
litellm_params=updateLiteLLMParams(timeout=42),
|
||||||
|
model_info=ModelInfo(id=model_id),
|
||||||
|
),
|
||||||
|
user_api_key_dict=actor,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert str(exc_info.value.code) == "400"
|
||||||
|
assert "Cannot edit config-based model" in str(exc_info.value)
|
||||||
|
prisma.db.litellm_proxymodeltable.update.assert_not_awaited()
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _client_for(self, actor: UserAPIKeyAuth) -> Iterator[TestClient]:
|
||||||
|
import litellm.proxy.proxy_server as proxy_server
|
||||||
|
from litellm.proxy.proxy_server import app
|
||||||
|
|
||||||
|
app.dependency_overrides[proxy_server.user_api_key_auth] = lambda: actor
|
||||||
|
try:
|
||||||
|
yield TestClient(app)
|
||||||
|
finally:
|
||||||
|
app.dependency_overrides.pop(proxy_server.user_api_key_auth, None)
|
||||||
|
|
||||||
|
def test_post_model_new_binds_to_actor_guard(self):
|
||||||
|
actor: Final = UserAPIKeyAuth(user_id="internal-user", user_role=LitellmUserRoles.INTERNAL_USER)
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.general_settings", {}), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
self._client_for(actor) as client,
|
||||||
|
):
|
||||||
|
response: Final = client.post(
|
||||||
|
"/model/new",
|
||||||
|
json={
|
||||||
|
"model_name": "internal-model",
|
||||||
|
"litellm_params": {"model": "openai/test-model"},
|
||||||
|
"model_info": {"id": "internal-model-id"},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 403
|
||||||
|
assert "permission" in response.text.lower()
|
||||||
|
prisma.db.litellm_proxymodeltable.create.assert_not_called()
|
||||||
|
|
||||||
|
def test_post_legacy_model_update_binds_to_persistence(self):
|
||||||
|
model_id: Final = "legacy-route-model-id"
|
||||||
|
existing_row: Final = LiteLLM_ProxyModelTable(
|
||||||
|
model_id=model_id,
|
||||||
|
model_name="legacy-route-model",
|
||||||
|
litellm_params={"model": "openai/test-model", "timeout": 30},
|
||||||
|
model_info={"id": model_id},
|
||||||
|
created_by="admin",
|
||||||
|
updated_by="admin",
|
||||||
|
)
|
||||||
|
updated_row: Final = LiteLLM_ProxyModelTable(
|
||||||
|
model_id=model_id,
|
||||||
|
model_name="legacy-route-model",
|
||||||
|
litellm_params={"model": "openai/test-model", "timeout": 42},
|
||||||
|
model_info={"id": model_id},
|
||||||
|
created_by="admin",
|
||||||
|
updated_by="admin",
|
||||||
|
)
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
prisma.db.litellm_proxymodeltable.find_unique = AsyncMock(return_value=existing_row)
|
||||||
|
prisma.db.litellm_proxymodeltable.update = AsyncMock(return_value=updated_row)
|
||||||
|
router: Final = MagicMock()
|
||||||
|
router.get_model_ids.return_value = [model_id]
|
||||||
|
actor: Final = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.llm_router", router), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch( # test-quality-ok: [TQ008] isolate persistence from encryption implementation
|
||||||
|
"litellm.proxy.management_endpoints.model_management_endpoints.encrypt_value_helper",
|
||||||
|
side_effect=lambda value: value,
|
||||||
|
),
|
||||||
|
patch( # test-quality-ok: [TQ008] isolate persistence from router reload implementation
|
||||||
|
"litellm.proxy.management_endpoints.model_management_endpoints.clear_cache",
|
||||||
|
new=AsyncMock(return_value=ReconcileOutcome(still_desired=None, live_after=None)),
|
||||||
|
),
|
||||||
|
patch( # test-quality-ok: [TQ008] audit logging is outside the persistence contract
|
||||||
|
"litellm.proxy.management_endpoints.model_management_endpoints.create_object_audit_log",
|
||||||
|
new=AsyncMock(return_value=None),
|
||||||
|
),
|
||||||
|
self._client_for(actor) as client,
|
||||||
|
):
|
||||||
|
response: Final = client.post(
|
||||||
|
"/model/update",
|
||||||
|
json={
|
||||||
|
"litellm_params": {"timeout": 42},
|
||||||
|
"model_info": {"id": model_id},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200, response.text
|
||||||
|
written: Final = json.loads(
|
||||||
|
prisma.db.litellm_proxymodeltable.update.await_args.kwargs["data"]["litellm_params"]
|
||||||
|
)
|
||||||
|
assert written["timeout"] == 42
|
||||||
|
|
||||||
|
def test_patch_config_model_binds_to_patch_route(self):
|
||||||
|
model_id: Final = "config-route-model-id"
|
||||||
|
prisma: Final = MagicMock()
|
||||||
|
prisma.db.litellm_proxymodeltable.find_unique = AsyncMock(return_value=None)
|
||||||
|
prisma.db.litellm_proxymodeltable.update = AsyncMock()
|
||||||
|
router: Final = MagicMock()
|
||||||
|
router.get_deployment.return_value = Deployment(
|
||||||
|
model_name="config-route-model",
|
||||||
|
litellm_params=LiteLLM_Params(model="openai/test-model"),
|
||||||
|
model_info=ModelInfo(id=model_id),
|
||||||
|
)
|
||||||
|
actor: Final = UserAPIKeyAuth(user_id="admin", user_role=LitellmUserRoles.PROXY_ADMIN)
|
||||||
|
with (
|
||||||
|
patch("litellm.proxy.proxy_server.prisma_client", prisma), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.llm_router", router), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.store_model_in_db", True), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
patch("litellm.proxy.proxy_server.premium_user", True), # test-quality-ok: [TQ008] route reads proxy-server state through its only test seam
|
||||||
|
self._client_for(actor) as client,
|
||||||
|
):
|
||||||
|
response: Final = client.patch(
|
||||||
|
f"/model/{model_id}/update",
|
||||||
|
json={
|
||||||
|
"litellm_params": {"timeout": 42},
|
||||||
|
"model_info": {"id": model_id},
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 400
|
||||||
|
assert "Cannot edit config-based model" in response.text
|
||||||
|
prisma.db.litellm_proxymodeltable.update.assert_not_awaited()
|
||||||
|
|
|
||||||
|
|
@ -101,4 +101,23 @@ describe("prepareModelAddRequest", () => {
|
||||||
expect(deployment.litellmParamsObj.litellm_credential_name).toBe("from-json");
|
expect(deployment.litellmParamsObj.litellm_credential_name).toBe("from-json");
|
||||||
expect(deployment.litellmParamsObj.timeout).toBe(5);
|
expect(deployment.litellmParamsObj.timeout).toBe(5);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
["OpenAI", "openai/*"],
|
||||||
|
["Azure_AI_Studio", "azure_ai/*"],
|
||||||
|
["Petals", "petals/*"],
|
||||||
|
])("composes wildcard names for the all-model selection", async (custom_llm_provider, wildcardModel) => {
|
||||||
|
const formValues = {
|
||||||
|
model_mappings: [],
|
||||||
|
model: "all-wildcard",
|
||||||
|
custom_llm_provider,
|
||||||
|
};
|
||||||
|
|
||||||
|
const deployments = await prepareModelAddRequest({ ...formValues }, "token", null);
|
||||||
|
|
||||||
|
expect(deployments).toHaveLength(1);
|
||||||
|
const [deployment] = deployments!;
|
||||||
|
expect(deployment.modelName).toBe(wildcardModel);
|
||||||
|
expect(deployment.litellmParamsObj.model).toBe(wildcardModel);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue