From c912236fa55fa2a4372a085669c056dc9c257232 Mon Sep 17 00:00:00 2001 From: "devin-ai-integration[bot]" <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 7 Oct 2026 20:03:44 -0700 Subject: [PATCH] fix(proxy): declare the Moyai settings write's service target and allowlist its routes (#45220) * fix(proxy): declare the config_params service target for the Moyai UI settings write Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): exercise the Moyai settings write against a real DualCache Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * fix(proxy): allowlist the Moyai quick-connect routes on the backend component Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> * test(proxy): observe the Moyai settings write target through a composed DualCache Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- backend/routes/allowlist.py | 1 + litellm/proxy/moyai_endpoints.py | 3 +++ tests/unit/proxy/test_moyai_endpoints.py | 34 ++++++++++++++++++++++++ 3 files changed, 38 insertions(+) diff --git a/backend/routes/allowlist.py b/backend/routes/allowlist.py index ea56a1fd6df..44541276be0 100644 --- a/backend/routes/allowlist.py +++ b/backend/routes/allowlist.py @@ -23,6 +23,7 @@ BACKEND_PATH_PREFIXES: tuple[str, ...] = ( "/end_user/", "/sso/", "/liteadmin/slack/connect/", + "/moyai/connect/", "/login", "/v2/login", "/v3/login", diff --git a/litellm/proxy/moyai_endpoints.py b/litellm/proxy/moyai_endpoints.py index 655fc0560f4..a2335c0a49b 100644 --- a/litellm/proxy/moyai_endpoints.py +++ b/litellm/proxy/moyai_endpoints.py @@ -20,6 +20,7 @@ from urllib.parse import urlencode, urlparse from fastapi import APIRouter, Depends, HTTPException, Request, status from pydantic import BaseModel +from litellm._internal_context import with_service_target from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth from litellm.proxy.auth.user_api_key_auth import user_api_key_auth from litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints import ( @@ -28,6 +29,7 @@ from litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints import ( _ui_settings_db, normalize_moyai_url, ) +from litellm.proxy.utils import CONFIG_PARAMS_TARGET from litellm.repositories.config_repository import ConfigRepository from litellm.repositories.table_repositories import UISettingsRepository @@ -189,6 +191,7 @@ async def _moyai_key_alias(prisma_client, moyai_url: str) -> str: return alias +@with_service_target(CONFIG_PARAMS_TARGET) async def _persist_moyai_url(prisma_client, moyai_url: str) -> None: from litellm.proxy.proxy_server import user_api_key_cache diff --git a/tests/unit/proxy/test_moyai_endpoints.py b/tests/unit/proxy/test_moyai_endpoints.py index a0b0d56b637..92a1261d952 100644 --- a/tests/unit/proxy/test_moyai_endpoints.py +++ b/tests/unit/proxy/test_moyai_endpoints.py @@ -1,10 +1,13 @@ import json import time from types import SimpleNamespace +from typing import Final from unittest.mock import AsyncMock, MagicMock import pytest +from litellm._internal_context import current_service_target +from litellm.caching.dual_cache import DualCache from litellm.proxy._types import LitellmUserRoles, UserAPIKeyAuth @@ -272,6 +275,37 @@ async def test_exchange_concurrent_replay_claims_nonce_once(monkeypatch: pytest. assert len(mint_calls) == 1 +def _target_recording_cache(cache: DualCache) -> SimpleNamespace: + async def _set(key: str, value: object, **kwargs: object) -> None: + await cache.async_set_cache(key=f"{key}:service_target", value=current_service_target()) + await cache.async_set_cache(key=key, value=value, **kwargs) + + return SimpleNamespace(async_set_cache=_set) + + +@pytest.mark.asyncio +async def test_persist_moyai_url_writes_ui_settings_cache_under_config_params_target( + monkeypatch: pytest.MonkeyPatch, +) -> None: + from litellm.proxy import proxy_server + from litellm.proxy.moyai_endpoints import _persist_moyai_url + from litellm.proxy.ui_crud_endpoints.proxy_setting_endpoints import UI_SETTINGS_CACHE_KEY + from litellm.proxy.utils import CONFIG_PARAMS_TARGET + + cache: Final = DualCache() + monkeypatch.setattr(proxy_server, "user_api_key_cache", _target_recording_cache(cache)) + + prisma: Final = MagicMock() + prisma.db.litellm_uisettings.find_unique = AsyncMock(return_value=None) + prisma.db.litellm_uisettings.upsert = AsyncMock() + + await _persist_moyai_url(prisma, "https://moyai.example.com") + + assert await cache.async_get_cache(key=f"{UI_SETTINGS_CACHE_KEY}:service_target") == CONFIG_PARAMS_TARGET + assert await cache.async_get_cache(key=UI_SETTINGS_CACHE_KEY) == {"moyai_url": "https://moyai.example.com"} + assert current_service_target() is None + + @pytest.mark.asyncio async def test_exchange_replay_survives_fresh_worker_cache(monkeypatch: pytest.MonkeyPatch) -> None: from fastapi import HTTPException