mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-06 02:48:13 +00:00
Adding backend
This commit is contained in:
parent
e92db2d718
commit
74842de78e
3 changed files with 137 additions and 16 deletions
|
|
@ -69,7 +69,7 @@ async def _get_cloudzero_settings():
|
|||
Retrieve CloudZero settings from the database with decrypted API key.
|
||||
|
||||
Returns:
|
||||
dict: CloudZero settings with decrypted API key
|
||||
dict: CloudZero settings with decrypted API key, or empty dict if not configured
|
||||
"""
|
||||
from litellm.proxy.proxy_server import prisma_client
|
||||
|
||||
|
|
@ -82,10 +82,16 @@ async def _get_cloudzero_settings():
|
|||
cloudzero_config = await prisma_client.db.litellm_config.find_first(
|
||||
where={"param_name": "cloudzero_settings"}
|
||||
)
|
||||
if cloudzero_config is None:
|
||||
if cloudzero_config is None or cloudzero_config.param_value is None:
|
||||
return {}
|
||||
|
||||
settings = dict(cloudzero_config.param_value)
|
||||
# Handle both dict and JSON string cases
|
||||
if isinstance(cloudzero_config.param_value, dict):
|
||||
settings = cloudzero_config.param_value
|
||||
elif isinstance(cloudzero_config.param_value, str):
|
||||
settings = json.loads(cloudzero_config.param_value)
|
||||
else:
|
||||
settings = dict(cloudzero_config.param_value)
|
||||
|
||||
# Decrypt the API key
|
||||
encrypted_api_key = settings.get("api_key")
|
||||
|
|
@ -119,6 +125,7 @@ async def get_cloudzero_settings(
|
|||
|
||||
Returns the current CloudZero configuration with the API key masked for security.
|
||||
Only the first 4 and last 4 characters of the API key are shown.
|
||||
Returns null/empty values when settings are not configured (consistent with other settings endpoints).
|
||||
|
||||
Only admin users can view CloudZero settings.
|
||||
"""
|
||||
|
|
@ -133,22 +140,27 @@ async def get_cloudzero_settings(
|
|||
# Get CloudZero settings using the accessor method
|
||||
settings = await _get_cloudzero_settings()
|
||||
|
||||
# If settings are empty, return null/empty values (consistent with other endpoints)
|
||||
if not settings:
|
||||
return CloudZeroSettingsView(
|
||||
api_key_masked=None,
|
||||
connection_id=None,
|
||||
timezone=None,
|
||||
status=None,
|
||||
)
|
||||
|
||||
# Use SensitiveDataMasker to mask the API key
|
||||
masked_settings = _sensitive_masker.mask_dict(settings)
|
||||
|
||||
return CloudZeroSettingsView(
|
||||
api_key_masked=masked_settings["api_key"],
|
||||
connection_id=settings["connection_id"],
|
||||
timezone=settings["timezone"],
|
||||
api_key_masked=masked_settings.get("api_key"),
|
||||
connection_id=settings.get("connection_id"),
|
||||
timezone=settings.get("timezone"),
|
||||
status="configured",
|
||||
)
|
||||
|
||||
except HTTPException as e:
|
||||
if e.status_code == 400:
|
||||
# Settings not configured
|
||||
raise HTTPException(
|
||||
status_code=404, detail={"error": "CloudZero settings not configured"}
|
||||
)
|
||||
# Re-raise HTTPExceptions as-is
|
||||
raise e
|
||||
except Exception as e:
|
||||
verbose_proxy_logger.error(f"Error retrieving CloudZero settings: {str(e)}")
|
||||
|
|
|
|||
|
|
@ -45,10 +45,10 @@ class CloudZeroExportResponse(BaseModel):
|
|||
class CloudZeroSettingsView(BaseModel):
|
||||
"""Response model for viewing CloudZero settings with masked API key"""
|
||||
|
||||
api_key_masked: str = Field(..., description="Masked API key showing only first 4 and last 4 characters")
|
||||
connection_id: str = Field(..., description="CloudZero connection ID for data submission")
|
||||
timezone: str = Field(..., description="Timezone for date handling")
|
||||
status: str = Field(..., description="Configuration status")
|
||||
api_key_masked: Optional[str] = Field(None, description="Masked API key showing only first 4 and last 4 characters")
|
||||
connection_id: Optional[str] = Field(None, description="CloudZero connection ID for data submission")
|
||||
timezone: Optional[str] = Field(None, description="Timezone for date handling")
|
||||
status: Optional[str] = Field(None, description="Configuration status")
|
||||
|
||||
|
||||
class CloudZeroSettingsUpdate(BaseModel):
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import sys
|
||||
from unittest.mock import AsyncMock, MagicMock
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from fastapi.testclient import TestClient
|
||||
|
|
@ -77,3 +77,112 @@ async def test_delete_cloudzero_settings_not_found(client, monkeypatch):
|
|||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_cloudzero_settings_success(client, monkeypatch):
|
||||
"""Test GET /cloudzero/settings returns settings when configured"""
|
||||
mock_config = MagicMock()
|
||||
mock_config.param_name = "cloudzero_settings"
|
||||
mock_config.param_value = {
|
||||
"api_key": "encrypted_key",
|
||||
"connection_id": "conn_123",
|
||||
"timezone": "UTC"
|
||||
}
|
||||
|
||||
mock_litellm_config = MagicMock()
|
||||
mock_litellm_config.find_first = AsyncMock(return_value=mock_config)
|
||||
|
||||
mock_prisma = MagicMock()
|
||||
mock_prisma.db = MagicMock()
|
||||
mock_prisma.db.litellm_config = mock_litellm_config
|
||||
|
||||
monkeypatch.setattr(ps, "prisma_client", mock_prisma)
|
||||
|
||||
# Mock the decrypt function to return a decrypted key
|
||||
with patch("litellm.proxy.spend_tracking.cloudzero_endpoints.decrypt_value_helper") as mock_decrypt:
|
||||
mock_decrypt.return_value = "decrypted_api_key"
|
||||
|
||||
# Mock the masker
|
||||
with patch("litellm.proxy.spend_tracking.cloudzero_endpoints._sensitive_masker") as mock_masker:
|
||||
mock_masker.mask_dict.return_value = {"api_key": "test****key"}
|
||||
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
|
||||
try:
|
||||
response = client.get("/cloudzero/settings")
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["connection_id"] == "conn_123"
|
||||
assert data["timezone"] == "UTC"
|
||||
assert data["status"] == "configured"
|
||||
assert data["api_key_masked"] == "test****key"
|
||||
mock_litellm_config.find_first.assert_awaited_once()
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_cloudzero_settings_not_configured(client, monkeypatch):
|
||||
"""Test GET /cloudzero/settings returns 200 with null values when not configured (consistent with other endpoints)"""
|
||||
mock_litellm_config = MagicMock()
|
||||
mock_litellm_config.find_first = AsyncMock(return_value=None)
|
||||
|
||||
mock_prisma = MagicMock()
|
||||
mock_prisma.db = MagicMock()
|
||||
mock_prisma.db.litellm_config = mock_litellm_config
|
||||
|
||||
monkeypatch.setattr(ps, "prisma_client", mock_prisma)
|
||||
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
|
||||
try:
|
||||
response = client.get("/cloudzero/settings")
|
||||
# Should return 200 with null values (not 404) - consistent with other settings endpoints
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["api_key_masked"] is None
|
||||
assert data["connection_id"] is None
|
||||
assert data["timezone"] is None
|
||||
assert data["status"] is None
|
||||
mock_litellm_config.find_first.assert_awaited_once()
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_cloudzero_settings_empty_param_value(client, monkeypatch):
|
||||
"""Test GET /cloudzero/settings returns 200 with null values when param_value is None"""
|
||||
mock_config = MagicMock()
|
||||
mock_config.param_name = "cloudzero_settings"
|
||||
mock_config.param_value = None
|
||||
|
||||
mock_litellm_config = MagicMock()
|
||||
mock_litellm_config.find_first = AsyncMock(return_value=mock_config)
|
||||
|
||||
mock_prisma = MagicMock()
|
||||
mock_prisma.db = MagicMock()
|
||||
mock_prisma.db.litellm_config = mock_litellm_config
|
||||
|
||||
monkeypatch.setattr(ps, "prisma_client", mock_prisma)
|
||||
|
||||
app.dependency_overrides[ps.user_api_key_auth] = lambda: UserAPIKeyAuth(
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN, user_id="admin_user"
|
||||
)
|
||||
|
||||
try:
|
||||
response = client.get("/cloudzero/settings")
|
||||
# Should return 200 with null values (not 404) - consistent with other settings endpoints
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["api_key_masked"] is None
|
||||
assert data["connection_id"] is None
|
||||
assert data["timezone"] is None
|
||||
assert data["status"] is None
|
||||
mock_litellm_config.find_first.assert_awaited_once()
|
||||
finally:
|
||||
app.dependency_overrides.pop(ps.user_api_key_auth, None)
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue