From 4f974765fbce0f5bef53620541f2c4e90496541b Mon Sep 17 00:00:00 2001 From: pjdurden Date: Tue, 14 Jul 2026 18:53:49 -0500 Subject: [PATCH 1/6] fix(proxy): retry startup general_settings read on transient DB transport error --- litellm/proxy/proxy_server.py | 28 ++++++++--- tests/test_litellm/proxy/test_proxy_server.py | 46 +++++++++++++++++++ 2 files changed, 67 insertions(+), 7 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 70484eb1e4e..75c8e17e887 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -8048,14 +8048,28 @@ class ProxyStartupEvent: # but YAML config has False. if store_model_in_db is not True and prisma_client is not None: try: - _db_gs_record = await ConfigRepository(prisma_client).table.find_first( - where={"param_name": "general_settings"} + _db_gs_record = await call_with_db_reconnect_retry( + prisma_client, + lambda: ConfigRepository(prisma_client).table.find_first( + where={"param_name": "general_settings"} + ), + reason="startup_store_model_in_db_general_settings_lookup_failure", ) - if _db_gs_record is not None and isinstance(_db_gs_record.param_value, dict): - _db_val = _db_gs_record.param_value.get("store_model_in_db") - if _db_val is True or (isinstance(_db_val, str) and _db_val.lower() == "true"): - store_model_in_db = True - verbose_proxy_logger.info("store_model_in_db=True loaded from DB, overriding config/env") + if _db_gs_record is not None: + _db_param_value = _db_gs_record.param_value + # Prisma can return the JSON column as a raw string; normalize + # it the way ConfigRepository.get_param does before reading, so + # the override isn't silently skipped for string-valued rows. + if isinstance(_db_param_value, str): + try: + _db_param_value = json.loads(_db_param_value) + except (json.JSONDecodeError, TypeError): + _db_param_value = {} + if isinstance(_db_param_value, dict): + _db_val = _db_param_value.get("store_model_in_db") + if _db_val is True or (isinstance(_db_val, str) and _db_val.lower() == "true"): + store_model_in_db = True + verbose_proxy_logger.info("store_model_in_db=True loaded from DB, overriding config/env") except Exception as e: verbose_proxy_logger.debug("Failed to check DB for store_model_in_db: %s", str(e)) diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 62f5ced7a39..fef54825ecd 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -883,6 +883,52 @@ async def test_initialize_scheduled_jobs_hydrates_mcp_when_store_model_in_db_fal mock_proxy_config.init_mcp_servers_from_db.assert_awaited_once() +@pytest.mark.asyncio +async def test_initialize_scheduled_jobs_recovers_from_transient_read_error(monkeypatch): + """ + Regression (#32412): the startup `store_model_in_db` lookup reads + general_settings straight off the Prisma HTTP engine. A transient transport + blip (httpx.ReadError) on that first read must be healed by one + reconnect-and-retry, like every other DB read path, instead of leaving the + connection poisoned and the DB-persisted store_model_in_db=True ignored. + """ + import types + + monkeypatch.delenv("DISABLE_PRISMA_SCHEMA_UPDATE", raising=False) + monkeypatch.delenv("STORE_MODEL_IN_DB", raising=False) + from litellm.proxy.proxy_server import ProxyStartupEvent + from litellm.proxy.utils import ProxyLogging + + record = types.SimpleNamespace(param_value={"store_model_in_db": True}) + find_first = AsyncMock(side_effect=[httpx.ReadError("Server disconnected"), record]) + + mock_prisma_client = MagicMock() + mock_prisma_client.db.litellm_config.find_first = find_first + mock_prisma_client.attempt_db_reconnect = AsyncMock(return_value=True) + + mock_proxy_logging = MagicMock(spec=ProxyLogging) + mock_proxy_logging.slack_alerting_instance = MagicMock() + mock_proxy_config = AsyncMock() + + with ( + patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), + patch("litellm.proxy.proxy_server.store_model_in_db", False), + ): + await ProxyStartupEvent.initialize_scheduled_background_jobs( + general_settings={}, + prisma_client=mock_prisma_client, + proxy_budget_rescheduler_min_time=1, + proxy_budget_rescheduler_max_time=2, + proxy_batch_write_at=5, + proxy_logging_obj=mock_proxy_logging, + ) + + mock_prisma_client.attempt_db_reconnect.assert_awaited_once() + assert find_first.await_count == 2 + mock_proxy_config.add_deployment.assert_awaited() + mock_proxy_config.init_mcp_servers_from_db.assert_not_called() + + @pytest.mark.asyncio async def test_init_mcp_servers_from_db_respects_supported_db_objects(monkeypatch): """ From 2659eee85abd97c5e010f34740c8f2ab8ebb70f6 Mon Sep 17 00:00:00 2001 From: pjdurden Date: Wed, 15 Jul 2026 12:06:30 -0500 Subject: [PATCH 2/6] fix(proxy): narrow startup general_settings JSON except and cover string case The isinstance(_db_param_value, str) guard already ensures json.loads only sees a str, so json.loads can only raise json.JSONDecodeError here; drop the unreachable TypeError arm Add a regression test for the string-valued param_value branch: Prisma can return the general_settings JSON column as a raw string, and the startup store_model_in_db lookup must json.loads it before reading the flag or the DB override is silently skipped. The test asserts the override is applied (add_deployment scheduled, MCP-from-db path skipped) and fails if the normalization is removed --- litellm/proxy/proxy_server.py | 6 +-- tests/test_litellm/proxy/test_proxy_server.py | 46 +++++++++++++++++++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 75c8e17e887..34a5e346c3e 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -8050,9 +8050,7 @@ class ProxyStartupEvent: try: _db_gs_record = await call_with_db_reconnect_retry( prisma_client, - lambda: ConfigRepository(prisma_client).table.find_first( - where={"param_name": "general_settings"} - ), + lambda: ConfigRepository(prisma_client).table.find_first(where={"param_name": "general_settings"}), reason="startup_store_model_in_db_general_settings_lookup_failure", ) if _db_gs_record is not None: @@ -8063,7 +8061,7 @@ class ProxyStartupEvent: if isinstance(_db_param_value, str): try: _db_param_value = json.loads(_db_param_value) - except (json.JSONDecodeError, TypeError): + except json.JSONDecodeError: _db_param_value = {} if isinstance(_db_param_value, dict): _db_val = _db_param_value.get("store_model_in_db") diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index fef54825ecd..13af17ed76e 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -929,6 +929,52 @@ async def test_initialize_scheduled_jobs_recovers_from_transient_read_error(monk mock_proxy_config.init_mcp_servers_from_db.assert_not_called() +@pytest.mark.asyncio +async def test_initialize_scheduled_jobs_normalizes_string_general_settings(monkeypatch): + """ + Regression (#32412): Prisma can return the general_settings JSON column as a + raw string rather than a parsed dict. The startup store_model_in_db lookup + must json.loads that string before reading the flag, otherwise a + DB-persisted store_model_in_db=True is silently skipped for string-valued + rows and the proxy comes up as if model storage were off. + """ + import types + + monkeypatch.delenv("DISABLE_PRISMA_SCHEMA_UPDATE", raising=False) + monkeypatch.delenv("STORE_MODEL_IN_DB", raising=False) + from litellm.proxy.proxy_server import ProxyStartupEvent + from litellm.proxy.utils import ProxyLogging + + record = types.SimpleNamespace(param_value='{"store_model_in_db": true}') + find_first = AsyncMock(return_value=record) + + mock_prisma_client = MagicMock() + mock_prisma_client.db.litellm_config.find_first = find_first + mock_prisma_client.attempt_db_reconnect = AsyncMock(return_value=True) + + mock_proxy_logging = MagicMock(spec=ProxyLogging) + mock_proxy_logging.slack_alerting_instance = MagicMock() + mock_proxy_config = AsyncMock() + + with ( + patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), + patch("litellm.proxy.proxy_server.store_model_in_db", False), + ): + await ProxyStartupEvent.initialize_scheduled_background_jobs( + general_settings={}, + prisma_client=mock_prisma_client, + proxy_budget_rescheduler_min_time=1, + proxy_budget_rescheduler_max_time=2, + proxy_batch_write_at=5, + proxy_logging_obj=mock_proxy_logging, + ) + + assert find_first.await_count == 1 + mock_prisma_client.attempt_db_reconnect.assert_not_awaited() + mock_proxy_config.add_deployment.assert_awaited() + mock_proxy_config.init_mcp_servers_from_db.assert_not_called() + + @pytest.mark.asyncio async def test_init_mcp_servers_from_db_respects_supported_db_objects(monkeypatch): """ From aa96682ffcb1ef841e511032131a0f43a384a3b2 Mon Sep 17 00:00:00 2001 From: pjdurden Date: Sat, 15 Aug 2026 22:04:18 -0500 Subject: [PATCH 3/6] test(proxy): give the startup scheduled-jobs mocks a db_spend_update_writer The slack alerting path now reads pod_lock_manager off it, and a spec'd ProxyLogging mock has no such attribute, so both regression tests raised AttributeError after main was merged in. Every other test in the file already sets it. Signed-off-by: pjdurden --- tests/test_litellm/proxy/test_proxy_server.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_litellm/proxy/test_proxy_server.py b/tests/test_litellm/proxy/test_proxy_server.py index 53f193bd83f..1e0178eaaf7 100644 --- a/tests/test_litellm/proxy/test_proxy_server.py +++ b/tests/test_litellm/proxy/test_proxy_server.py @@ -942,6 +942,7 @@ async def test_initialize_scheduled_jobs_recovers_from_transient_read_error(monk mock_proxy_logging = MagicMock(spec=ProxyLogging) mock_proxy_logging.slack_alerting_instance = MagicMock() + mock_proxy_logging.db_spend_update_writer = MagicMock() mock_proxy_config = AsyncMock() with ( @@ -988,6 +989,7 @@ async def test_initialize_scheduled_jobs_normalizes_string_general_settings(monk mock_proxy_logging = MagicMock(spec=ProxyLogging) mock_proxy_logging.slack_alerting_instance = MagicMock() + mock_proxy_logging.db_spend_update_writer = MagicMock() mock_proxy_config = AsyncMock() with ( From b8851493ff7f1b0c351f19c47e48f9c89c57917a Mon Sep 17 00:00:00 2001 From: pjdurden Date: Wed, 7 Oct 2026 16:37:41 -0500 Subject: [PATCH 4/6] test(proxy): build startup proxy_config mock with _mock_scheduled_proxy_config Upstream's startup path now runs the heuristic-v1 tuning baseline gate, which calls sync ProxyConfig methods; a bare AsyncMock returns coroutines for them. Use the shared spec'd helper like the neighbouring tests. Co-Authored-By: Claude Opus 5.5 --- tests/unit/proxy/test_proxy_server_endpoints_and_startup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py b/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py index f760a871f29..703fa7eb1d8 100644 --- a/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py +++ b/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py @@ -1185,7 +1185,7 @@ async def test_initialize_scheduled_jobs_recovers_from_transient_read_error(monk mock_proxy_logging = MagicMock(spec=ProxyLogging) mock_proxy_logging.slack_alerting_instance = MagicMock() mock_proxy_logging.db_spend_update_writer = MagicMock() - mock_proxy_config = AsyncMock() + mock_proxy_config = _mock_scheduled_proxy_config() with ( patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), @@ -1232,7 +1232,7 @@ async def test_initialize_scheduled_jobs_normalizes_string_general_settings(monk mock_proxy_logging = MagicMock(spec=ProxyLogging) mock_proxy_logging.slack_alerting_instance = MagicMock() mock_proxy_logging.db_spend_update_writer = MagicMock() - mock_proxy_config = AsyncMock() + mock_proxy_config = _mock_scheduled_proxy_config() with ( patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), From 74faded4a08b7d76492bc7d87493edea7e4c2cd3 Mon Sep 17 00:00:00 2001 From: pjdurden Date: Mon, 12 Oct 2026 15:27:08 -0500 Subject: [PATCH 5/6] test(proxy): cover malformed string general_settings at startup The JSONDecodeError arm of the startup store_model_in_db lookup had no test, which left codecov/patch just under target. A non-JSON string row is now asserted to leave store_model_in_db off without tripping the outer failure path. --- ...test_proxy_server_endpoints_and_startup.py | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py b/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py index 703fa7eb1d8..256bdbb3db6 100644 --- a/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py +++ b/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py @@ -1253,6 +1253,53 @@ async def test_initialize_scheduled_jobs_normalizes_string_general_settings(monk mock_proxy_config.init_mcp_servers_from_db.assert_not_called() +@pytest.mark.asyncio +async def test_initialize_scheduled_jobs_ignores_malformed_string_general_settings(monkeypatch): + """ + A string-valued general_settings row that is not valid JSON must be treated + as empty, leaving store_model_in_db off, instead of failing the lookup. + """ + import types + + monkeypatch.delenv("DISABLE_PRISMA_SCHEMA_UPDATE", raising=False) + monkeypatch.delenv("STORE_MODEL_IN_DB", raising=False) + from litellm.proxy.proxy_server import ProxyStartupEvent + from litellm.proxy.utils import ProxyLogging + + record = types.SimpleNamespace(param_value="{not json") + find_first = AsyncMock(return_value=record) + + mock_prisma_client = MagicMock() + mock_prisma_client.db.litellm_config.find_first = find_first + mock_prisma_client.attempt_db_reconnect = AsyncMock(return_value=True) + + mock_proxy_logging = MagicMock(spec=ProxyLogging) + mock_proxy_logging.slack_alerting_instance = MagicMock() + mock_proxy_logging.db_spend_update_writer = MagicMock() + mock_proxy_config = _mock_scheduled_proxy_config() + + with ( + patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), + patch("litellm.proxy.proxy_server.store_model_in_db", False), + patch("litellm.proxy.proxy_server.verbose_proxy_logger") as mock_logger, + ): + await ProxyStartupEvent.initialize_scheduled_background_jobs( + general_settings={}, + prisma_client=mock_prisma_client, + proxy_budget_rescheduler_min_time=1, + proxy_budget_rescheduler_max_time=2, + proxy_batch_write_at=5, + proxy_logging_obj=mock_proxy_logging, + ) + + assert find_first.await_count == 1 + assert not any( + "Failed to check DB for store_model_in_db" in str(call.args[0]) for call in mock_logger.debug.call_args_list + ) + mock_proxy_config.add_deployment.assert_not_called() + mock_proxy_config.init_mcp_servers_from_db.assert_awaited_once() + + @pytest.mark.asyncio async def test_init_mcp_servers_from_db_respects_supported_db_objects(monkeypatch): """ From eb57fa1c62c55b260be7b5c35d2aa7d02f802c3f Mon Sep 17 00:00:00 2001 From: pjdurden Date: Sun, 18 Oct 2026 03:05:42 -0500 Subject: [PATCH 6/6] test(proxy): assert the scheduled add_deployment job in the startup DB lookup tests The three general_settings startup tests only inspected mocks, which the TQ002 mock-echo gate rejects. Inject a real AsyncIOScheduler and assert whether add_deployment_job was scheduled, which is what store_model_in_db actually controls. The retry and string-normalization tests fail on the pre-fix proxy_server.py. --- ...test_proxy_server_endpoints_and_startup.py | 126 +++++++++++------- 1 file changed, 75 insertions(+), 51 deletions(-) diff --git a/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py b/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py index 256bdbb3db6..19297a3ded1 100644 --- a/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py +++ b/tests/unit/proxy/test_proxy_server_endpoints_and_startup.py @@ -1170,6 +1170,8 @@ async def test_initialize_scheduled_jobs_recovers_from_transient_read_error(monk """ import types + from apscheduler.schedulers.asyncio import AsyncIOScheduler + monkeypatch.delenv("DISABLE_PRISMA_SCHEMA_UPDATE", raising=False) monkeypatch.delenv("STORE_MODEL_IN_DB", raising=False) from litellm.proxy.proxy_server import ProxyStartupEvent @@ -1187,23 +1189,29 @@ async def test_initialize_scheduled_jobs_recovers_from_transient_read_error(monk mock_proxy_logging.db_spend_update_writer = MagicMock() mock_proxy_config = _mock_scheduled_proxy_config() - with ( - patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), - patch("litellm.proxy.proxy_server.store_model_in_db", False), - ): - await ProxyStartupEvent.initialize_scheduled_background_jobs( - general_settings={}, - prisma_client=mock_prisma_client, - proxy_budget_rescheduler_min_time=1, - proxy_budget_rescheduler_max_time=2, - proxy_batch_write_at=5, - proxy_logging_obj=mock_proxy_logging, - ) + scheduler = AsyncIOScheduler() + try: + with ( + patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), + patch("litellm.proxy.proxy_server.store_model_in_db", False), + patch("litellm.proxy.proxy_server.AsyncIOScheduler", return_value=scheduler), + ): + await ProxyStartupEvent.initialize_scheduled_background_jobs( + general_settings={}, + prisma_client=mock_prisma_client, + proxy_budget_rescheduler_min_time=1, + proxy_budget_rescheduler_max_time=2, + proxy_batch_write_at=5, + proxy_logging_obj=mock_proxy_logging, + ) - mock_prisma_client.attempt_db_reconnect.assert_awaited_once() - assert find_first.await_count == 2 - mock_proxy_config.add_deployment.assert_awaited() - mock_proxy_config.init_mcp_servers_from_db.assert_not_called() + assert scheduler.get_job("add_deployment_job") is not None + mock_prisma_client.attempt_db_reconnect.assert_awaited_once() + assert find_first.await_count == 2 + mock_proxy_config.add_deployment.assert_awaited() + mock_proxy_config.init_mcp_servers_from_db.assert_not_called() + finally: + scheduler.shutdown(wait=False) @pytest.mark.asyncio @@ -1217,6 +1225,8 @@ async def test_initialize_scheduled_jobs_normalizes_string_general_settings(monk """ import types + from apscheduler.schedulers.asyncio import AsyncIOScheduler + monkeypatch.delenv("DISABLE_PRISMA_SCHEMA_UPDATE", raising=False) monkeypatch.delenv("STORE_MODEL_IN_DB", raising=False) from litellm.proxy.proxy_server import ProxyStartupEvent @@ -1234,23 +1244,29 @@ async def test_initialize_scheduled_jobs_normalizes_string_general_settings(monk mock_proxy_logging.db_spend_update_writer = MagicMock() mock_proxy_config = _mock_scheduled_proxy_config() - with ( - patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), - patch("litellm.proxy.proxy_server.store_model_in_db", False), - ): - await ProxyStartupEvent.initialize_scheduled_background_jobs( - general_settings={}, - prisma_client=mock_prisma_client, - proxy_budget_rescheduler_min_time=1, - proxy_budget_rescheduler_max_time=2, - proxy_batch_write_at=5, - proxy_logging_obj=mock_proxy_logging, - ) + scheduler = AsyncIOScheduler() + try: + with ( + patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), + patch("litellm.proxy.proxy_server.store_model_in_db", False), + patch("litellm.proxy.proxy_server.AsyncIOScheduler", return_value=scheduler), + ): + await ProxyStartupEvent.initialize_scheduled_background_jobs( + general_settings={}, + prisma_client=mock_prisma_client, + proxy_budget_rescheduler_min_time=1, + proxy_budget_rescheduler_max_time=2, + proxy_batch_write_at=5, + proxy_logging_obj=mock_proxy_logging, + ) - assert find_first.await_count == 1 - mock_prisma_client.attempt_db_reconnect.assert_not_awaited() - mock_proxy_config.add_deployment.assert_awaited() - mock_proxy_config.init_mcp_servers_from_db.assert_not_called() + assert scheduler.get_job("add_deployment_job") is not None + assert find_first.await_count == 1 + mock_prisma_client.attempt_db_reconnect.assert_not_awaited() + mock_proxy_config.add_deployment.assert_awaited() + mock_proxy_config.init_mcp_servers_from_db.assert_not_called() + finally: + scheduler.shutdown(wait=False) @pytest.mark.asyncio @@ -1261,6 +1277,8 @@ async def test_initialize_scheduled_jobs_ignores_malformed_string_general_settin """ import types + from apscheduler.schedulers.asyncio import AsyncIOScheduler + monkeypatch.delenv("DISABLE_PRISMA_SCHEMA_UPDATE", raising=False) monkeypatch.delenv("STORE_MODEL_IN_DB", raising=False) from litellm.proxy.proxy_server import ProxyStartupEvent @@ -1278,26 +1296,32 @@ async def test_initialize_scheduled_jobs_ignores_malformed_string_general_settin mock_proxy_logging.db_spend_update_writer = MagicMock() mock_proxy_config = _mock_scheduled_proxy_config() - with ( - patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), - patch("litellm.proxy.proxy_server.store_model_in_db", False), - patch("litellm.proxy.proxy_server.verbose_proxy_logger") as mock_logger, - ): - await ProxyStartupEvent.initialize_scheduled_background_jobs( - general_settings={}, - prisma_client=mock_prisma_client, - proxy_budget_rescheduler_min_time=1, - proxy_budget_rescheduler_max_time=2, - proxy_batch_write_at=5, - proxy_logging_obj=mock_proxy_logging, - ) + scheduler = AsyncIOScheduler() + try: + with ( + patch("litellm.proxy.proxy_server.proxy_config", mock_proxy_config), + patch("litellm.proxy.proxy_server.store_model_in_db", False), + patch("litellm.proxy.proxy_server.AsyncIOScheduler", return_value=scheduler), + patch("litellm.proxy.proxy_server.verbose_proxy_logger") as mock_logger, + ): + await ProxyStartupEvent.initialize_scheduled_background_jobs( + general_settings={}, + prisma_client=mock_prisma_client, + proxy_budget_rescheduler_min_time=1, + proxy_budget_rescheduler_max_time=2, + proxy_batch_write_at=5, + proxy_logging_obj=mock_proxy_logging, + ) - assert find_first.await_count == 1 - assert not any( - "Failed to check DB for store_model_in_db" in str(call.args[0]) for call in mock_logger.debug.call_args_list - ) - mock_proxy_config.add_deployment.assert_not_called() - mock_proxy_config.init_mcp_servers_from_db.assert_awaited_once() + assert scheduler.get_job("add_deployment_job") is None + assert find_first.await_count == 1 + assert not any( + "Failed to check DB for store_model_in_db" in str(call.args[0]) for call in mock_logger.debug.call_args_list + ) + mock_proxy_config.add_deployment.assert_not_called() + mock_proxy_config.init_mcp_servers_from_db.assert_awaited_once() + finally: + scheduler.shutdown(wait=False) @pytest.mark.asyncio