litellm/tests/enterprise/litellm_enterprise/proxy/guardrails/conftest.py
Sameer Kankute 5bd59b33e6
feat(guardrails): wire apply_guardrail into proxy logging callbacks (#28970)
* feat(guardrails): wire apply_guardrail into proxy logging callbacks

Route /apply_guardrail through pre/post proxy hooks and LiteLLM success/failure handlers so Langfuse and OTEL integrations receive input/output on guardrail-only requests.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(guardrails): fix Greptile review comments on apply_guardrail logging

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(apply_guardrail): preserve original exception and capture modified response

- Capture return value from post_call_success_hook so callback-modified
  responses propagate to the caller.
- Wrap success/failure logging calls in defensive try/except so logging
  infrastructure failures don't replace the user-visible response or mask
  the original guardrail exception.

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* Fix mypy

* fix(apply_guardrail): isolate failure logging and use post-hook response for logging

- Split async_failure_handler and post_call_failure_hook into independent
  try/except blocks so a callback bug in one does not silently skip the
  other.
- Build response_for_logging inside _emit_guardrail_success_logs after
  post_call_success_hook runs, so logged data matches the response the
  caller actually receives when the hook modifies the response.

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(apply_guardrail): fix black formatting and update tests for fastapi_request param

- Run black on guardrail_endpoints.py to fix CI formatting check
- Add _mock_proxy_logging() helper to enterprise guardrail tests to patch
  proxy-server globals imported at call time
- Pass fastapi_request=Mock() in all direct apply_guardrail test calls
  to match updated function signature

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix(guardrails): use transformed exception from post_call_failure_hook in apply_guardrail

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(guardrails): isolate sync/async logging handlers in apply_guardrail

Separate each logging handler call into its own try/except so a failure
in the async handler does not silently skip the sync handler submission
(and vice versa). Matches the docstring's defensive intent.

Co-authored-by: Yassin Kortam <yassin@berri.ai>

* fix(apply_guardrail): guard transformed_exception with isinstance check

Co-authored-by: Cursor <cursoragent@cursor.com>

* test(guardrails): mock proxy globals in not_found test and share apply_guardrail logging fixture

- Add proxy-server global mocks to test_apply_guardrail_not_found so the
  failure-path post_call_failure_hook call doesn't touch the real proxy
  logging singleton.
- Extract the duplicated _mock_proxy_logging context manager out of the
  two enterprise apply_guardrail test files into a shared conftest fixture
  so the helper stays in one place.

* fix(guardrails): use update_messages to keep logging obj in sync

Co-authored-by: Yassin Kortam <yassin@berri.ai>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Yassin Kortam <yassin@berri.ai>
Co-authored-by: mateo-berri <277851410+mateo-berri@users.noreply.github.com>
2026-05-28 09:41:02 -07:00

42 lines
1.6 KiB
Python

"""Shared fixtures for guardrail apply_guardrail tests."""
from contextlib import contextmanager
from unittest.mock import AsyncMock, MagicMock, patch
import pytest
@contextmanager
def _mock_proxy_logging():
"""Patch the proxy-server globals that apply_guardrail imports at call time."""
mock_proxy_logging = MagicMock()
mock_proxy_logging.post_call_success_hook = AsyncMock(return_value=None)
mock_proxy_logging.post_call_failure_hook = AsyncMock(return_value=None)
mock_logging_obj = MagicMock()
mock_logging_obj.async_success_handler = AsyncMock(return_value=None)
mock_logging_obj.async_failure_handler = AsyncMock(return_value=None)
mock_logging_obj.success_handler = MagicMock(return_value=None)
mock_logging_obj.failure_handler = MagicMock(return_value=None)
mock_logging_obj.model_call_details = {}
with (
patch(
"litellm.proxy.common_request_processing.ProxyBaseLLMRequestProcessing"
) as mock_proc_cls,
patch("litellm.proxy.proxy_server.proxy_logging_obj", mock_proxy_logging),
patch("litellm.proxy.proxy_server.general_settings", {}),
patch("litellm.proxy.proxy_server.proxy_config", MagicMock()),
patch("litellm.proxy.proxy_server.version", "0.0.0"),
):
mock_proc = MagicMock()
mock_proc.common_processing_pre_call_logic = AsyncMock(
return_value=({}, mock_logging_obj)
)
mock_proc_cls.return_value = mock_proc
yield mock_proxy_logging
@pytest.fixture
def mock_proxy_logging_ctx():
"""Return the proxy-logging context manager factory for use as `with ctx():`."""
return _mock_proxy_logging