test(guardrails): align logging_only_scope integration rows with abort-on-input-failure and encrypted params

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
yucheng 2026-10-05 09:52:18 +00:00
parent 443f9e3db4
commit 733c2fcfdc
2 changed files with 43 additions and 6 deletions

View file

@ -31,6 +31,8 @@ from openai import AsyncOpenAI, OpenAI
from openai.types.chat import ChatCompletionChunk
from pydantic import JsonValue, TypeAdapter
from litellm.proxy.common_utils.callback_utils import CALLBACK_VAR_ENCRYPTED_PREFIX
from litellm.proxy.guardrails.guardrail_registry import decrypt_guardrail_litellm_params
from tests.integration.cost_calculation.cost_tracking_case import JsonResponse, SseResponse
JSON_OBJECT: Final = TypeAdapter(dict[str, JsonValue])
@ -850,7 +852,7 @@ def _create_guardrail(candidate: Gateway, identity: str, params: Mapping[str, Js
def _management_guardrail_rows(identity: str) -> tuple[dict[str, JsonValue], ...]:
return tuple(
rows: Final = tuple(
object_value(row)
for row in read_rows(
"SELECT guardrail_id, guardrail_name, litellm_params, guardrail_info "
@ -858,6 +860,32 @@ def _management_guardrail_rows(identity: str) -> tuple[dict[str, JsonValue], ...
(identity,),
)
)
return tuple({**row, "litellm_params": _decrypted_management_litellm_params(row["litellm_params"])} for row in rows)
def _decrypted_management_litellm_params(stored_value: JsonValue) -> dict[str, JsonValue]:
stored: Final = object_value(stored_value)
salt_key: Final = os.environ.get("LITELLM_SALT_KEY", "sk-integration-salt")
with pytest.MonkeyPatch.context() as monkeypatch:
monkeypatch.setenv("LITELLM_SALT_KEY", salt_key)
decrypted: Final = JSON_OBJECT.validate_python(decrypt_guardrail_litellm_params(stored))
assert _decryption_only_changes_encrypted_values(stored, decrypted)
return decrypted
def _decryption_only_changes_encrypted_values(stored: JsonValue, decrypted: JsonValue) -> bool:
if isinstance(stored, dict) and isinstance(decrypted, dict):
return stored.keys() == decrypted.keys() and all(
_decryption_only_changes_encrypted_values(value, decrypted[key]) for key, value in stored.items()
)
if isinstance(stored, list) and isinstance(decrypted, list):
return len(stored) == len(decrypted) and all(
_decryption_only_changes_encrypted_values(stored_value, decrypted_value)
for stored_value, decrypted_value in zip(stored, decrypted)
)
if isinstance(stored, str) and stored.startswith(CALLBACK_VAR_ENCRYPTED_PREFIX):
return isinstance(decrypted, str) and not decrypted.startswith(CALLBACK_VAR_ENCRYPTED_PREFIX)
return stored == decrypted
def _drain_upstream(upstream_url: str) -> tuple[dict[str, JsonValue], ...]:
@ -865,8 +893,10 @@ def _drain_upstream(upstream_url: str) -> tuple[dict[str, JsonValue], ...]:
response.raise_for_status()
requests: Final = object_value(JSON_OBJECT.validate_python(response.json())).get("requests")
assert isinstance(requests, list), response.text
_record_upstream_request_count(len(requests))
return tuple(object_value(request) for request in requests)
observations: Final = tuple(object_value(request) for request in requests)
forwarded_requests: Final = tuple(request for request in observations if request.get("method", "POST") != "GET")
_record_upstream_request_count(len(forwarded_requests))
return forwarded_requests
def _json_contains_exact_string(value: JsonValue, expected: str) -> bool:

View file

@ -502,7 +502,7 @@ def test_F7_guardrail_ui_settings_classify_directional_scope_support(gateway: Ga
"logging_only",
"sideways",
200,
("request", "response"),
("request",),
"logging_only",
id="G3-yaml-logging-invalid-literal",
),
@ -1337,7 +1337,14 @@ def test_H10_management_patch_null_scope_restores_both_directions(gateway: Gatew
def policy(request: Request) -> Reply:
assert request.target == "/beta/litellm_basic_guardrail_api", request.target
return Reply(body=b'{"action":"BLOCKED","blocked_reason":"synthetic reset-scope monitor"}')
payload: Final = JSON_OBJECT.validate_json(request.body)
direction: Final = _direction(payload)
verdict: Final = (
{"action": "NONE"}
if direction == "request"
else {"action": "BLOCKED", "blocked_reason": "synthetic reset-scope monitor"}
)
return Reply(body=json.dumps(verdict).encode())
try:
with wire_server(policy) as guardrail:
@ -1407,7 +1414,7 @@ def test_H10_management_patch_null_scope_restores_both_directions(gateway: Gatew
assert tuple(
(entry["guardrail_name"], entry["guardrail_mode"], entry["guardrail_status"]) for entry in entries
) == (
(identity, "logging_only", "guardrail_intervened"),
(identity, "logging_only", "success"),
(identity, "logging_only", "guardrail_intervened"),
), entries
finally: