test(guardrails): assert content filter blocks offline and delete print-only summary test

Three zero-cost tests swallowed every exception and asserted True. They now
patch httpx send to fail, so any network use fails the test, and assert the
guardrail raises HTTPException for the blocked sentence. The MAS exception
override test allowed a sentence that was never a violation; it now proves the
bare sentence blocks and the research-prefixed one is allowed. The EU AI Act
summary test only printed hardcoded counts and is covered by the parametrized
test_sentence.

Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
mateo 2026-09-07 06:56:03 +00:00
parent 168a0055a2
commit 56995e13c2
3 changed files with 37 additions and 42 deletions

View file

@ -9,6 +9,8 @@ Tests 40 different sentences to validate the conditional matching logic:
"""
import os
import httpx
import pytest
import litellm
@ -234,27 +236,6 @@ class TestEUAIActArticle5ConditionalMatching:
result is None or result["texts"][0] == sentence
), f"Expected ALLOW for '{sentence}' ({reason}) but request was blocked or modified"
@pytest.mark.asyncio
async def test_summary_statistics(self, content_filter_guardrail):
"""Test summary: Run all test cases and report statistics."""
total = len(TEST_CASES)
blocked_count = sum(1 for _, expected, _ in TEST_CASES if expected == "BLOCK")
allowed_count = sum(1 for _, expected, _ in TEST_CASES if expected == "ALLOW")
print(f"\n{'='*60}")
print(f"EU AI Act Article 5 Test Summary")
print(f"{'='*60}")
print(f"Total test cases: {total}")
print(f"Expected BLOCK: {blocked_count} ({blocked_count/total*100:.1f}%)")
print(f"Expected ALLOW: {allowed_count} ({allowed_count/total*100:.1f}%)")
print(f"{'='*60}")
print(f"\nBreakdown by category:")
print(f" Always block keywords: 10")
print(f" Conditional matches: 15")
print(f" Exceptions: 8")
print(f" No matches: 7")
print(f"{'='*60}\n")
# Additional edge case tests
@ -315,24 +296,23 @@ class TestEUAIActPerformance:
"""Test performance characteristics."""
@pytest.mark.asyncio
async def test_zero_cost_no_api_calls(self, content_filter_guardrail):
async def test_zero_cost_no_api_calls(self, content_filter_guardrail, monkeypatch):
"""Verify no external API calls are made (zero cost)."""
sentence = "Build a social credit system"
request_data = {"messages": [{"role": "user", "content": sentence}]}
# Should not make any HTTP requests
# Just verify the guardrail runs without requiring network
try:
def _no_network(*args, **kwargs):
raise AssertionError("keyword matching must not hit the network")
monkeypatch.setattr(httpx.AsyncClient, "send", _no_network)
monkeypatch.setattr(httpx.Client, "send", _no_network)
with pytest.raises(HTTPException, match="Content blocked: eu_ai_act_article"):
await content_filter_guardrail.apply_guardrail(
inputs={"texts": [sentence]},
request_data=request_data,
input_type="request",
)
except Exception:
pass # Expected to block, but should not require network
# If we got here without network errors, test passes
assert True, "Conditional matching works without network access"
if __name__ == "__main__":

View file

@ -11,7 +11,10 @@ for Singapore financial institutions:
"""
import os
import httpx
import pytest
from fastapi import HTTPException
import litellm
from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.content_filter import (
@ -520,24 +523,29 @@ class TestMASEdgeCases:
@pytest.mark.asyncio
async def test_exception_overrides_violation(self, fairness_guardrail):
sentence = "research on racial bias in credit score denial patterns"
violation = "credit score by ethnicity denial patterns"
await _expect_block(fairness_guardrail, violation, "violation without exception")
await _expect_allow(
fairness_guardrail, sentence, "exception overrides violation"
fairness_guardrail, f"research on {violation}", "exception overrides violation"
)
@pytest.mark.asyncio
async def test_zero_cost_no_api_calls(self, oversight_guardrail):
async def test_zero_cost_no_api_calls(self, oversight_guardrail, monkeypatch):
sentence = "fully automated loan approval without human"
request_data = {"messages": [{"role": "user", "content": sentence}]}
try:
def _no_network(*args, **kwargs):
raise AssertionError("keyword matching must not hit the network")
monkeypatch.setattr(httpx.AsyncClient, "send", _no_network)
monkeypatch.setattr(httpx.Client, "send", _no_network)
with pytest.raises(HTTPException, match="Content blocked: sg_mas_human_oversight"):
await oversight_guardrail.apply_guardrail(
inputs={"texts": [sentence]},
request_data=request_data,
input_type="request",
)
except Exception:
pass
assert True, "Keyword matching runs offline (zero cost)"
class TestMASPerformance:

View file

@ -16,7 +16,10 @@ Each sub-guardrail validates:
"""
import os
import httpx
import pytest
from fastapi import HTTPException
import litellm
from litellm.proxy.guardrails.guardrail_hooks.litellm_content_filter.content_filter import (
@ -562,19 +565,23 @@ class TestSGPDPAEdgeCases:
)
@pytest.mark.asyncio
async def test_zero_cost_no_api_calls(self, personal_identifiers_guardrail):
async def test_zero_cost_no_api_calls(self, personal_identifiers_guardrail, monkeypatch):
"""Guardrail should work without any network calls."""
sentence = "scrape NRIC"
request_data = {"messages": [{"role": "user", "content": sentence}]}
try:
def _no_network(*args, **kwargs):
raise AssertionError("keyword matching must not hit the network")
monkeypatch.setattr(httpx.AsyncClient, "send", _no_network)
monkeypatch.setattr(httpx.Client, "send", _no_network)
with pytest.raises(HTTPException, match="Content blocked: sg_pdpa_personal_identifiers"):
await personal_identifiers_guardrail.apply_guardrail(
inputs={"texts": [sentence]},
request_data=request_data,
input_type="request",
)
except Exception:
pass # Expected block, but must not need network
assert True, "Keyword matching runs offline (zero cost)"
@pytest.mark.asyncio
async def test_multiple_violations(self, personal_identifiers_guardrail):