Spaces:
Configuration error
Configuration error
""" | |
Test cases for spend log cleanup functionality | |
""" | |
from datetime import UTC, datetime, timedelta, timezone | |
from unittest.mock import AsyncMock, MagicMock | |
import pytest | |
from litellm.proxy.db.db_transaction_queue.spend_log_cleanup import SpendLogCleanup | |
async def test_should_delete_spend_logs(): | |
# Test case 1: No retention set | |
cleaner = SpendLogCleanup(general_settings={}) | |
assert cleaner._should_delete_spend_logs() is False | |
# Test case 2: Valid seconds string | |
cleaner = SpendLogCleanup( | |
general_settings={"maximum_spend_logs_retention_period": "3600s"} | |
) | |
assert cleaner._should_delete_spend_logs() is True | |
# Test case 3: Valid days string | |
cleaner = SpendLogCleanup( | |
general_settings={"maximum_spend_logs_retention_period": "30d"} | |
) | |
assert cleaner._should_delete_spend_logs() is True | |
# Test case 4: Valid hours string | |
cleaner = SpendLogCleanup( | |
general_settings={"maximum_spend_logs_retention_period": "24h"} | |
) | |
assert cleaner._should_delete_spend_logs() is True | |
# Test case 5: Invalid format | |
cleaner = SpendLogCleanup( | |
general_settings={"maximum_spend_logs_retention_period": "invalid"} | |
) | |
assert cleaner._should_delete_spend_logs() is False | |
async def test_cleanup_old_spend_logs_batch_deletion(): | |
from types import SimpleNamespace | |
from unittest.mock import AsyncMock, MagicMock, patch | |
# Setup Prisma client | |
mock_prisma_client = MagicMock() | |
mock_db = MagicMock() | |
# Mock spendlogs table | |
mock_spendlogs = MagicMock() | |
mock_spendlogs.find_many = AsyncMock() | |
mock_spendlogs.delete_many = AsyncMock() | |
# Create 1500 mocked logs with .request_id | |
mock_logs = [SimpleNamespace(request_id=f"req_{i}") for i in range(1500)] | |
mock_spendlogs.find_many.side_effect = [ | |
mock_logs[:1000], # Batch 1 | |
mock_logs[1000:], # Batch 2 | |
[], # Done | |
] | |
# Wire up mocks | |
mock_db.litellm_spendlogs = mock_spendlogs | |
mock_prisma_client.db = mock_db | |
# Mock Redis cache and pod_lock_manager | |
mock_redis_cache = MagicMock() | |
mock_pod_lock_manager = MagicMock() | |
mock_pod_lock_manager.redis_cache = mock_redis_cache | |
mock_pod_lock_manager.acquire_lock = AsyncMock(return_value=True) | |
mock_pod_lock_manager.release_lock = AsyncMock() | |
# Run cleanup with mocked pod_lock_manager | |
test_settings = {"maximum_spend_logs_retention_period": "7d"} | |
cleaner = SpendLogCleanup(general_settings=test_settings) | |
cleaner.pod_lock_manager = mock_pod_lock_manager | |
assert cleaner._should_delete_spend_logs() is True | |
await cleaner.cleanup_old_spend_logs(mock_prisma_client) | |
# Validate batching and deletion | |
assert mock_spendlogs.find_many.call_count == 3 | |
assert mock_spendlogs.delete_many.call_count == 2 | |
mock_spendlogs.delete_many.assert_any_call( | |
where={"request_id": {"in": [f"req_{i}" for i in range(1000)]}} | |
) | |
mock_spendlogs.delete_many.assert_any_call( | |
where={"request_id": {"in": [f"req_{i}" for i in range(1000, 1500)]}} | |
) | |
async def test_cleanup_old_spend_logs_retention_period_cutoff(): | |
""" | |
Test that logs are filtered using correct cutoff based on retention | |
""" | |
# Setup Prisma client | |
mock_prisma_client = MagicMock() | |
mock_db = MagicMock() | |
mock_spendlogs = MagicMock() | |
mock_spendlogs.find_many = AsyncMock(return_value=[]) | |
mock_spendlogs.delete_many = AsyncMock() | |
mock_db.litellm_spendlogs = mock_spendlogs | |
mock_prisma_client.db = mock_db | |
# Mock Redis cache and pod_lock_manager | |
mock_redis_cache = MagicMock() | |
mock_pod_lock_manager = MagicMock() | |
mock_pod_lock_manager.redis_cache = mock_redis_cache | |
mock_pod_lock_manager.acquire_lock = AsyncMock(return_value=True) | |
mock_pod_lock_manager.release_lock = AsyncMock() | |
# Run cleanup with mocked pod_lock_manager | |
test_settings = {"maximum_spend_logs_retention_period": "24h"} | |
cleaner = SpendLogCleanup(general_settings=test_settings) | |
cleaner.pod_lock_manager = mock_pod_lock_manager | |
assert cleaner._should_delete_spend_logs() is True | |
await cleaner.cleanup_old_spend_logs(mock_prisma_client) | |
# Verify the cutoff date is correct | |
cutoff_date = mock_spendlogs.find_many.call_args[1]["where"]["startTime"]["lt"] | |
expected_cutoff = datetime.now(timezone.utc) - timedelta(seconds=86400) | |
assert ( | |
abs((cutoff_date - expected_cutoff).total_seconds()) < 1 | |
) # Allow 1 second difference for test execution time | |
async def test_cleanup_old_spend_logs_no_retention_period(): | |
""" | |
Test that no logs are deleted when no retention period is set | |
""" | |
mock_prisma_client = MagicMock() | |
mock_prisma_client.db.litellm_spendlogs.find_many = AsyncMock() | |
mock_prisma_client.db.litellm_spendlogs.delete = AsyncMock() | |
cleaner = SpendLogCleanup(general_settings={}) # no retention | |
await cleaner.cleanup_old_spend_logs(mock_prisma_client) | |
mock_prisma_client.db.litellm_spendlogs.find_many.assert_not_called() | |
mock_prisma_client.db.litellm_spendlogs.delete.assert_not_called() | |
def test_cleanup_batch_size_env_var(monkeypatch): | |
"""Ensure batch size is configurable via environment variable""" | |
import importlib | |
import litellm.constants as constants_module | |
import litellm.proxy.db.db_transaction_queue.spend_log_cleanup as cleanup_module | |
# Set env var and reload modules to pick up new value | |
monkeypatch.setenv("SPEND_LOG_CLEANUP_BATCH_SIZE", "25") | |
importlib.reload(constants_module) | |
importlib.reload(cleanup_module) | |
cleaner = cleanup_module.SpendLogCleanup(general_settings={}) | |
assert cleaner.batch_size == 25 | |
# Remove env var and reload to restore default for other tests | |
monkeypatch.delenv("SPEND_LOG_CLEANUP_BATCH_SIZE", raising=False) | |
importlib.reload(constants_module) | |
importlib.reload(cleanup_module) | |