fbd6602814
---ci--- phase: 0 milestone: v0.1 status: complete ---/ci---
114 lines
3.0 KiB
Python
114 lines
3.0 KiB
Python
"""Unit tests for the CustomerServiceGuardrail (TASK-03-04)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
|
|
import pytest
|
|
|
|
from server.guardrails.customer_service import (
|
|
CustomerServiceGuardrail,
|
|
DISCLAIMER_TEXT,
|
|
)
|
|
from server.guardrails.noop import NoOpGuardrail
|
|
from server.services.base import Guardrail, GuardrailContext
|
|
|
|
|
|
def test_is_guardrail():
|
|
assert isinstance(CustomerServiceGuardrail(), Guardrail)
|
|
|
|
|
|
def test_disclaimer_text_defined():
|
|
g = CustomerServiceGuardrail()
|
|
assert "AI practice session" in g.session_start_disclaimer
|
|
assert "not a real conversation" in g.session_start_disclaimer
|
|
|
|
|
|
def test_blocks_legal_advice():
|
|
g = CustomerServiceGuardrail()
|
|
|
|
async def _run():
|
|
return await g.check("You should sue the company in small claims court.")
|
|
|
|
v = asyncio.run(_run())
|
|
assert not v.allowed
|
|
assert v.category == "blocked_legal"
|
|
|
|
|
|
def test_blocks_financial_advice():
|
|
g = CustomerServiceGuardrail()
|
|
|
|
async def _run():
|
|
return await g.check("You should invest in crypto for retirement.")
|
|
|
|
v = asyncio.run(_run())
|
|
assert not v.allowed
|
|
assert v.category == "blocked_financial"
|
|
|
|
|
|
def test_blocks_medical_advice():
|
|
g = CustomerServiceGuardrail()
|
|
|
|
async def _run():
|
|
return await g.check("That sounds like a diagnosis; see a doctor.")
|
|
|
|
v = asyncio.run(_run())
|
|
assert not v.allowed
|
|
assert v.category == "blocked_medical"
|
|
|
|
|
|
def test_allows_normal_coaching_line():
|
|
g = CustomerServiceGuardrail()
|
|
|
|
async def _run():
|
|
return await g.check("You acknowledged the customer's frustration well.")
|
|
|
|
v = asyncio.run(_run())
|
|
assert v.allowed
|
|
assert v.category == "ok"
|
|
|
|
|
|
def test_debrief_filter_blocks_sue_them():
|
|
"""PLAN.md must-have: guardrail flags a 'sue them' recommendation."""
|
|
g = CustomerServiceGuardrail()
|
|
ctx = GuardrailContext(role="debrief")
|
|
|
|
async def _run():
|
|
return await g.check("You should tell the customer to sue them.", ctx)
|
|
|
|
v = asyncio.run(_run())
|
|
assert not v.allowed
|
|
assert v.category == "blocked_legal"
|
|
assert v.filtered_text is not None
|
|
|
|
|
|
def test_debrief_allows_normal_coaching():
|
|
g = CustomerServiceGuardrail()
|
|
ctx = GuardrailContext(role="debrief")
|
|
|
|
async def _run():
|
|
return await g.check(
|
|
"What you did well: you acknowledged the customer's frustration "
|
|
"and offered a concrete resolution.",
|
|
ctx,
|
|
)
|
|
|
|
v = asyncio.run(_run())
|
|
assert v.allowed
|
|
assert v.category == "ok"
|
|
|
|
|
|
def test_guardrail_swappable_with_noop():
|
|
"""D-019: swapping CustomerServiceGuardrail ↔ NoOpGuardrail requires no
|
|
pipeline change (both implement the same interface)."""
|
|
cs = CustomerServiceGuardrail()
|
|
noop = NoOpGuardrail()
|
|
|
|
async def _run(g):
|
|
return await g.check("anything", GuardrailContext())
|
|
|
|
v_cs = asyncio.run(_run(cs))
|
|
v_noop = asyncio.run(_run(noop))
|
|
# Both return a GuardrailVerdict — interface-compatible.
|
|
assert hasattr(v_cs, "allowed")
|
|
assert hasattr(v_noop, "allowed") |