From b48c0377127055e08da340ffba021d67eb42d78c Mon Sep 17 00:00:00 2001 From: Yuneng Jiang Date: Mon, 30 Mar 2026 21:30:57 -0700 Subject: [PATCH] test: replace test_add_and_delete_models integration test with mock MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Skip the integration test that requires a live proxy and OPENAI_API_KEY (removed from CI/CD). Add deterministic mock test covering the same add → delete → double-delete-fails lifecycle. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../test_model_management_endpoints.py | 98 +++++++++++++++++++ tests/test_models.py | 3 + 2 files changed, 101 insertions(+) diff --git a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py index 2e566ab6222..cbc6f1db3c7 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py @@ -14,6 +14,7 @@ sys.path.insert( ) # Adds the parent directory to the system path from litellm.proxy._types import ( LiteLLM_ModelTable, + LiteLLM_ProxyModelTable, LiteLLM_TeamTable, LitellmUserRoles, Member, @@ -1176,3 +1177,100 @@ class TestModelInfoEndpoint: assert result["id"] == "team-model-1" assert result["object"] == "model" assert result["owned_by"] == "custom" + + +class TestAddAndDeleteModelLifecycle: + """ + Mock replacement for test_add_and_delete_models in tests/test_models.py. + + The original integration test required a live proxy + OPENAI_API_KEY. + This test verifies the same lifecycle (add → delete → double-delete fails) + by calling the endpoint handlers directly with mocked DB. + """ + + @pytest.mark.asyncio + async def test_add_then_delete_model(self): + """ + - Add model via add_new_model → returns model_id + - Delete model via delete_model → returns success + - Delete same model again → raises (model not found) + """ + from litellm.proxy.management_endpoints.model_management_endpoints import ( + add_new_model, + delete_model as delete_model_endpoint, + ) + from litellm.proxy.management_endpoints.model_management_endpoints import ( + ModelInfoDelete, + ) + + model_id = "lifecycle-test-model-123" + admin_user = UserAPIKeyAuth( + user_id="test-admin", user_role=LitellmUserRoles.PROXY_ADMIN + ) + + # Build a real LiteLLM_ProxyModelTable for the DB mock to return + db_row = LiteLLM_ProxyModelTable( + model_id=model_id, + model_name="lifecycle-model", + litellm_params={"model": "openai/gpt-4.1-nano"}, + model_info={"id": model_id}, + created_by="test-admin", + updated_by="test-admin", + ) + + mock_prisma = MagicMock() + mock_prisma.db = MagicMock() + mock_prisma.db.litellm_proxymodeltable = AsyncMock() + mock_prisma.db.litellm_proxymodeltable.create = AsyncMock(return_value=db_row) + mock_prisma.db.litellm_proxymodeltable.find_unique = AsyncMock( + return_value=db_row + ) + mock_prisma.db.litellm_proxymodeltable.delete = AsyncMock(return_value=db_row) + + mock_proxy_config = MagicMock() + mock_proxy_config.add_deployment = AsyncMock() + + mock_router = MagicMock() + mock_router.delete_deployment = MagicMock() + + _PS = "litellm.proxy.proxy_server" + with patch(f"{_PS}.prisma_client", mock_prisma), \ + patch(f"{_PS}.store_model_in_db", True), \ + patch(f"{_PS}.proxy_config", mock_proxy_config), \ + patch(f"{_PS}.proxy_logging_obj", MagicMock()), \ + patch(f"{_PS}.general_settings", {}), \ + patch(f"{_PS}.premium_user", True), \ + patch(f"{_PS}.llm_router", mock_router): + + # --- ADD --- + add_result = await add_new_model( + model_params=Deployment( + model_name="lifecycle-model", + litellm_params=LiteLLM_Params( + model="openai/gpt-4.1-nano", api_key="fake-key" + ), + model_info={"id": model_id}, + ), + user_api_key_dict=admin_user, + ) + assert add_result.model_id == model_id + + # --- DELETE --- + delete_result = await delete_model_endpoint( + model_info=ModelInfoDelete(id=model_id), + user_api_key_dict=admin_user, + ) + assert "deleted successfully" in delete_result["message"] + + # --- DELETE again should fail (model not found) --- + mock_prisma.db.litellm_proxymodeltable.find_unique = AsyncMock( + return_value=None + ) + from litellm.proxy.proxy_server import ProxyException + + with pytest.raises(ProxyException) as exc_info: + await delete_model_endpoint( + model_info=ModelInfoDelete(id=model_id), + user_api_key_dict=admin_user, + ) + assert str(exc_info.value.code) == "400" diff --git a/tests/test_models.py b/tests/test_models.py index a4b7c6a44fd..151fb70b665 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -268,6 +268,9 @@ async def delete_model(session, model_id="123", key="sk-1234"): return await response.json() +@pytest.mark.skip( + reason="Requires live proxy + OPENAI_API_KEY. Deterministic mock version in tests/test_litellm/proxy/management_endpoints/test_model_management_endpoints.py::TestAddAndDeleteModelLifecycle" +) @pytest.mark.asyncio async def test_add_and_delete_models(): """