Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion test-quality-budget.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"limit": 1078
},
"TQ004": {
"limit": 757
"limit": 557
},
"TQ005": {
"limit": 2810
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1508,7 +1508,7 @@ def test_multiple_tool_calls_in_single_choice():
print("✓ Multiple tool calls are correctly grouped in a single choice")


def test_map_reasoning_effort_adds_summary_detailed():
def test_map_reasoning_effort_adds_summary_detailed(monkeypatch):
"""
Test that _map_reasoning_effort behavior with reasoning_auto_summary flag.

Expand Down Expand Up @@ -1571,7 +1571,7 @@ def test_map_reasoning_effort_adds_summary_detailed():

# Test 3: With env var enabled (flag disabled) - summary IS added
litellm.reasoning_auto_summary = False
os.environ["LITELLM_REASONING_AUTO_SUMMARY"] = "true"
monkeypatch.setenv("LITELLM_REASONING_AUTO_SUMMARY", "true")

result = handler._map_reasoning_effort("high")
assert (
Expand Down Expand Up @@ -1603,7 +1603,7 @@ def test_map_reasoning_effort_adds_summary_detailed():
# Restore original values
litellm.reasoning_auto_summary = original_flag
if original_env is not None:
os.environ["LITELLM_REASONING_AUTO_SUMMARY"] = original_env
monkeypatch.setenv("LITELLM_REASONING_AUTO_SUMMARY", original_env)
elif "LITELLM_REASONING_AUTO_SUMMARY" in os.environ:
del os.environ["LITELLM_REASONING_AUTO_SUMMARY"]

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -341,10 +341,10 @@ def test_transform_with_none_optional_params(self):
assert data["expires_after"] is None
assert data["file_ids"] is None

def test_container_create_response_includes_cost(self):
def test_container_create_response_includes_cost(self, monkeypatch):
"""Test that container create response includes code interpreter cost calculation."""
# Force use of local model cost map for CI/CD consistency
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")

from litellm.litellm_core_utils.llm_cost_calc.tool_call_cost_tracking import (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,49 +88,44 @@ async def test_send_email_success(mock_env_vars):


@pytest.mark.asyncio
async def test_send_email_missing_api_key():
async def test_send_email_missing_api_key(monkeypatch):
# Remove the API key from environment before initializing logger
original_key = os.environ.pop("RESEND_API_KEY", None)
monkeypatch.delenv("RESEND_API_KEY", raising=False)

try:
# Initialize the logger after removing the API key
logger = ResendEmailLogger()
# Initialize the logger after removing the API key
logger = ResendEmailLogger()

# Test data
from_email = "test@example.com"
to_email = ["recipient@example.com"]
subject = "Test Subject"
html_body = "<p>Test email body</p>"
# Test data
from_email = "test@example.com"
to_email = ["recipient@example.com"]
subject = "Test Subject"
html_body = "<p>Test email body</p>"

# Create mock HTTP client and inject it directly into the logger
# This ensures the mock is used regardless of any caching issues
mock_response = mock.Mock(spec=Response)
mock_response.raise_for_status.return_value = None
mock_response.status_code = 200
mock_response.json.return_value = {"id": "test_email_id"}
# Create mock HTTP client and inject it directly into the logger
# This ensures the mock is used regardless of any caching issues
mock_response = mock.Mock(spec=Response)
mock_response.raise_for_status.return_value = None
mock_response.status_code = 200
mock_response.json.return_value = {"id": "test_email_id"}

mock_async_client = mock.AsyncMock()
mock_async_client.post.return_value = mock_response
mock_async_client = mock.AsyncMock()
mock_async_client.post.return_value = mock_response

# Directly inject the mock client to bypass any caching
logger.async_httpx_client = mock_async_client
# Directly inject the mock client to bypass any caching
logger.async_httpx_client = mock_async_client

# Send email
await logger.send_email(
from_email=from_email,
to_email=to_email,
subject=subject,
html_body=html_body,
)
# Send email
await logger.send_email(
from_email=from_email,
to_email=to_email,
subject=subject,
html_body=html_body,
)

# Verify the HTTP client was called with None as the API key
mock_async_client.post.assert_called_once()
call_args = mock_async_client.post.call_args
assert call_args[1]["headers"] == {"Authorization": "Bearer None"}
finally:
# Restore the original key if it existed
if original_key is not None:
os.environ["RESEND_API_KEY"] = original_key
# Verify the HTTP client was called with None as the API key
mock_async_client.post.assert_called_once()
call_args = mock_async_client.post.call_args
assert call_args[1]["headers"] == {"Authorization": "Bearer None"}


@pytest.mark.asyncio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,22 +98,18 @@ async def test_send_email_success(mock_env_vars, mock_async_client):


@pytest.mark.asyncio
async def test_send_email_missing_api_key():
original_key = os.environ.pop("SENDGRID_API_KEY", None)
async def test_send_email_missing_api_key(monkeypatch):
monkeypatch.delenv("SENDGRID_API_KEY", raising=False)

try:
logger = SendGridEmailLogger()

with pytest.raises(ValueError, match='SENDGRID_API_KEY is not set'):
await logger.send_email(
from_email="test@example.com",
to_email=["recipient@example.com"],
subject="Test Subject",
html_body="<p>Test email body</p>",
)
finally:
if original_key is not None:
os.environ["SENDGRID_API_KEY"] = original_key
logger = SendGridEmailLogger()

with pytest.raises(ValueError, match='SENDGRID_API_KEY is not set'):
await logger.send_email(
from_email="test@example.com",
to_email=["recipient@example.com"],
subject="Test Subject",
html_body="<p>Test email body</p>",
)


@pytest.mark.asyncio
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@


class TestGCSBucketBase:
def test_construct_request_headers_with_project_id(self):
def test_construct_request_headers_with_project_id(self, monkeypatch):
"""Test that construct_request_headers correctly uses project_id if passed from env"""
test_project_id = "test-project"
os.environ["GOOGLE_SECRET_MANAGER_PROJECT_ID"] = test_project_id
monkeypatch.setenv("GOOGLE_SECRET_MANAGER_PROJECT_ID", test_project_id)

try:
# Create handler
Expand Down
12 changes: 6 additions & 6 deletions tests/test_litellm/integrations/test_openmeter.py
Original file line number Diff line number Diff line change
Expand Up @@ -236,9 +236,9 @@ def test_cloudevents_structure(self):
assert result["data"]["completion_tokens"] == 8
assert result["data"]["total_tokens"] == 23

def test_custom_event_type(self):
def test_custom_event_type(self, monkeypatch):
"""Test that custom event type is used when set"""
os.environ["OPENMETER_EVENT_TYPE"] = "custom_event_type"
monkeypatch.setenv("OPENMETER_EVENT_TYPE", "custom_event_type")

logger = OpenMeterLogger()

Expand Down Expand Up @@ -374,10 +374,10 @@ def test_common_logic_integer_token_user_id(self):
assert isinstance(result["subject"], str)
assert result["subject"] == "12345"

def test_common_logic_trust_request_user_false_ignores_request_user(self):
def test_common_logic_trust_request_user_false_ignores_request_user(self, monkeypatch):
"""OPENMETER_TRUST_REQUEST_USER=false makes the key-bound user_id win
over a request-supplied `user` (forge-attribution mitigation)."""
os.environ["OPENMETER_TRUST_REQUEST_USER"] = "false"
monkeypatch.setenv("OPENMETER_TRUST_REQUEST_USER", "false")
logger = OpenMeterLogger()

kwargs = {
Expand All @@ -400,11 +400,11 @@ def test_common_logic_trust_request_user_false_ignores_request_user(self):
assert result["subject"] == "real-tenant-id"
assert result["subject"] != "forged-by-client"

def test_common_logic_trust_request_user_false_still_raises_without_key_user(self):
def test_common_logic_trust_request_user_false_still_raises_without_key_user(self, monkeypatch):
"""OPENMETER_TRUST_REQUEST_USER=false still raises when no
user_api_key_user_id is available — the request `user` is not a
fallback in this mode."""
os.environ["OPENMETER_TRUST_REQUEST_USER"] = "false"
monkeypatch.setenv("OPENMETER_TRUST_REQUEST_USER", "false")
logger = OpenMeterLogger()

kwargs = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,8 @@ def test_bedrock_guardrail_cost_no_pricing_entry(monkeypatch):
assert bedrock_guardrail_cost(usage_units={"contentPolicyUnits": 1}, aws_region_name="us-east-1") == 0.0


def test_shipped_bedrock_guardrail_prices_match_aws_pricing_page():
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
def test_shipped_bedrock_guardrail_prices_match_aws_pricing_page(monkeypatch):
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
assert litellm.model_cost["bedrock/guardrails"]["guardrail_cost_per_unit"] == {
"automatedReasoningPolicyUnits": 0.00017,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -377,12 +377,12 @@ def test_get_cost_for_vertex_ai_gemini_web_search(model, custom_llm_provider):
assert cost == 0.035, f"Expected $0.035 grounding cost, got ${cost}"


def test_azure_assistant_features_integrated_cost_tracking():
def test_azure_assistant_features_integrated_cost_tracking(monkeypatch):
"""
Test integrated cost tracking for Azure assistant features.
"""
# Force use of local model cost map for CI/CD consistency
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")

model = "azure/gpt-4o"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2844,7 +2844,7 @@ def test_anthropic_messages_pt_file_block_preserves_cache_control():
assert text_block["cache_control"]["type"] == "ephemeral"


def test_add_cache_point_tool_block_passes_ttl_for_claude_4_5():
def test_add_cache_point_tool_block_passes_ttl_for_claude_4_5(monkeypatch):
"""
Tools with cache_control ttl should preserve the ttl in the cachePoint
block for Claude 4.5+ models on Bedrock, matching the behavior of system
Expand All @@ -2867,7 +2867,7 @@ def test_add_cache_point_tool_block_passes_ttl_for_claude_4_5():

old_env = os.environ.get("LITELLM_LOCAL_MODEL_COST_MAP")
old_cost = litellm.model_cost
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
try:
tool_with_1h = {
Expand Down Expand Up @@ -2927,10 +2927,10 @@ def test_add_cache_point_tool_block_passes_ttl_for_claude_4_5():
if old_env is None:
os.environ.pop("LITELLM_LOCAL_MODEL_COST_MAP", None)
else:
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = old_env
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", old_env)


def test_bedrock_tools_pt_passes_ttl_for_claude_4_5():
def test_bedrock_tools_pt_passes_ttl_for_claude_4_5(monkeypatch):
"""
End-to-end: _bedrock_tools_pt should produce cachePoint blocks with ttl
for Claude 4.5+ models when tools have cache_control with ttl.
Expand All @@ -2944,7 +2944,7 @@ def test_bedrock_tools_pt_passes_ttl_for_claude_4_5():

old_env = os.environ.get("LITELLM_LOCAL_MODEL_COST_MAP")
old_cost = litellm.model_cost
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = "True"
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", "True")
litellm.model_cost = litellm.get_model_cost_map(url="")
try:
tools = [
Expand Down Expand Up @@ -2980,7 +2980,7 @@ def test_bedrock_tools_pt_passes_ttl_for_claude_4_5():
if old_env is None:
os.environ.pop("LITELLM_LOCAL_MODEL_COST_MAP", None)
else:
os.environ["LITELLM_LOCAL_MODEL_COST_MAP"] = old_env
monkeypatch.setenv("LITELLM_LOCAL_MODEL_COST_MAP", old_env)


def test_convert_to_anthropic_tool_result_openai_file_pdf_becomes_document():
Expand Down
18 changes: 9 additions & 9 deletions tests/test_litellm/litellm_core_utils/test_litellm_logging.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ def test_post_call_serializes_dict_with_datetime(logging_obj):
assert "2026-05-11" in serialized


def test_sentry_sample_rate():
def test_sentry_sample_rate(monkeypatch):
existing_sample_rate = os.getenv("SENTRY_API_SAMPLE_RATE")
try:
# test with default value by removing the environment variable
Expand All @@ -76,7 +76,7 @@ def test_sentry_sample_rate():
assert os.environ.get("SENTRY_API_SAMPLE_RATE") == "1.0"

# test with custom value
os.environ["SENTRY_API_SAMPLE_RATE"] = "0.5"
monkeypatch.setenv("SENTRY_API_SAMPLE_RATE", "0.5")

set_callbacks(["sentry"])
# Check if the custom sample rate is set correctly
Expand All @@ -86,13 +86,13 @@ def test_sentry_sample_rate():
finally:
# Restore the original environment variable
if existing_sample_rate:
os.environ["SENTRY_API_SAMPLE_RATE"] = existing_sample_rate
monkeypatch.setenv("SENTRY_API_SAMPLE_RATE", existing_sample_rate)
else:
if "SENTRY_API_SAMPLE_RATE" in os.environ:
del os.environ["SENTRY_API_SAMPLE_RATE"]


def test_sentry_environment():
def test_sentry_environment(monkeypatch):
"""Test that SENTRY_ENVIRONMENT is properly handled during Sentry initialization"""
existing_environment = os.getenv("SENTRY_ENVIRONMENT")
existing_dsn = os.getenv("SENTRY_DSN")
Expand All @@ -115,7 +115,7 @@ def test_sentry_environment():

try:
# Set a mock DSN to allow Sentry initialization
os.environ["SENTRY_DSN"] = "https://test@sentry.io/123456"
monkeypatch.setenv("SENTRY_DSN", "https://test@sentry.io/123456")

# Test with default value (no environment set)
if existing_environment:
Expand All @@ -129,7 +129,7 @@ def test_sentry_environment():
assert call_kwargs["environment"] == "production"

# Test with custom environment value
os.environ["SENTRY_ENVIRONMENT"] = "development"
monkeypatch.setenv("SENTRY_ENVIRONMENT", "development")

mock_init.reset_mock()
set_callbacks(["sentry"])
Expand All @@ -139,7 +139,7 @@ def test_sentry_environment():
assert call_kwargs["environment"] == "development"

# Test with staging environment
os.environ["SENTRY_ENVIRONMENT"] = "staging"
monkeypatch.setenv("SENTRY_ENVIRONMENT", "staging")

mock_init.reset_mock()
set_callbacks(["sentry"])
Expand All @@ -154,13 +154,13 @@ def test_sentry_environment():
finally:
# Restore the original environment variables
if existing_environment:
os.environ["SENTRY_ENVIRONMENT"] = existing_environment
monkeypatch.setenv("SENTRY_ENVIRONMENT", existing_environment)
else:
if "SENTRY_ENVIRONMENT" in os.environ:
del os.environ["SENTRY_ENVIRONMENT"]

if existing_dsn:
os.environ["SENTRY_DSN"] = existing_dsn
monkeypatch.setenv("SENTRY_DSN", existing_dsn)
else:
if "SENTRY_DSN" in os.environ:
del os.environ["SENTRY_DSN"]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ def test_no_summary_by_default_dict_reasoning(self):
finally:
litellm.reasoning_auto_summary = original

def test_summary_added_when_env_var_set(self):
def test_summary_added_when_env_var_set(self, monkeypatch):
"""When LITELLM_REASONING_AUTO_SUMMARY env var is true, summary is added."""
import litellm
from litellm.llms.anthropic.experimental_pass_through.adapters.handler import (
Expand All @@ -538,7 +538,7 @@ def test_summary_added_when_env_var_set(self):
original = litellm.reasoning_auto_summary
try:
litellm.reasoning_auto_summary = False
os.environ["LITELLM_REASONING_AUTO_SUMMARY"] = "true"
monkeypatch.setenv("LITELLM_REASONING_AUTO_SUMMARY", "true")
completion_kwargs = {
"model": "responses/gpt-5.2",
"custom_llm_provider": "openai",
Expand Down
Loading
Loading