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