Files
praxis/tests/test_guardrail.py
T
Praxis CI b77536aa5e docs(P01): complete minimal-voice-loop phase
---ci---
phase: 1
milestone: v0.1
status: complete
requirements:
  covered: [REQ-VOICE-01, REQ-VOICE-02, REQ-VOICE-03, REQ-VOICE-04, REQ-SCEN-01, REQ-STATE-01, REQ-LLM-01, REQ-LLM-02, REQ-DEBRIEF-01, REQ-ORCH-01, REQ-ORCH-02, REQ-SCEN-FMT-01, REQ-NFR-LAT-01, REQ-NFR-SAFE-01, REQ-NFR-COST-01]
  partial: []
---/ci---
2026-08-01 13:28:42 +00:00

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")