fbd6602814
---ci--- phase: 0 milestone: v0.1 status: complete ---/ci---
102 lines
3.3 KiB
Python
102 lines
3.3 KiB
Python
"""Unit tests for the scenario schema + loader (TASK-03-01, TASK-03-02)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
import yaml
|
|
|
|
from server.scenarios.schema import Scenario, ValidationError
|
|
from server.scenarios.loader import load
|
|
|
|
|
|
VALID_SCENARIO_DICT = {
|
|
"id": "cs_refund_ca_v01",
|
|
"path": "customer_service",
|
|
"market": "CA",
|
|
"language": "en-CA",
|
|
"title": "Angry customer requesting refund on a damaged product",
|
|
"difficulty": 1,
|
|
"failure_mode": "escalates_unresolved",
|
|
"persona": {
|
|
"voice_id": "cartesia:some-voice-id",
|
|
"character": "Customer (Jordan)",
|
|
},
|
|
"setup": {
|
|
"system_prompt": "You are Jordan, a customer who received a damaged product.",
|
|
"opening_line": "Hi, I received my order yesterday and the item is cracked.",
|
|
},
|
|
"success_criteria": ["Acknowledged the customer's frustration empathetically"],
|
|
"common_mistakes": ["Jumping to policy before acknowledging emotion"],
|
|
"branches": [
|
|
{
|
|
"id": "accept_resolution",
|
|
"trigger": {"learner_signals": ["empathy", "concrete_resolution"]},
|
|
"outcome": "success",
|
|
"debrief_focus": "What you did well",
|
|
},
|
|
{
|
|
"id": "escalate",
|
|
"trigger": {"learner_signals": ["defensive", "policy_first"]},
|
|
"outcome": "failure",
|
|
"failure_mode": "escalates_unresolved",
|
|
"debrief_focus": "The customer escalated because they felt unheard",
|
|
},
|
|
],
|
|
"debrief": {
|
|
"model": "deepseek-v4-flash:cloud",
|
|
"mode": "no_think",
|
|
"prompt_template": "debrief/default",
|
|
},
|
|
}
|
|
|
|
|
|
def test_valid_scenario_parses():
|
|
s = Scenario.model_validate(VALID_SCENARIO_DICT)
|
|
assert s.id == "cs_refund_ca_v01"
|
|
assert s.failure_mode == "escalates_unresolved"
|
|
assert len(s.branches) == 2
|
|
assert s.branch_ids() == ["accept_resolution", "escalate"]
|
|
|
|
|
|
def test_invalid_scenario_raises_typed_error():
|
|
bad = dict(VALID_SCENARIO_DICT)
|
|
bad["failure_mode"] = None # required field → ValidationError
|
|
with pytest.raises(ValidationError):
|
|
Scenario.model_validate(bad)
|
|
|
|
|
|
def test_invalid_branch_outcome_raises():
|
|
bad = dict(VALID_SCENARIO_DICT)
|
|
bad["branches"] = [
|
|
{
|
|
"id": "x",
|
|
"trigger": {"learner_signals": ["a"]},
|
|
"outcome": "not_a_real_outcome", # Literal mismatch
|
|
"debrief_focus": "f",
|
|
}
|
|
]
|
|
with pytest.raises(ValidationError):
|
|
Scenario.model_validate(bad)
|
|
|
|
|
|
def test_scenario_branch_by_id():
|
|
s = Scenario.model_validate(VALID_SCENARIO_DICT)
|
|
b = s.branch_by_id("escalate")
|
|
assert b is not None
|
|
assert b.outcome == "failure"
|
|
assert b.failure_mode == "escalates_unresolved"
|
|
assert s.branch_by_id("nonexistent") is None
|
|
|
|
|
|
def test_load_customer_service_refund_scenario():
|
|
"""TASK-03-02 verification: the real YAML loads and validates."""
|
|
s = load("customer_service_refund_ca_v01")
|
|
assert s.id == "cs_refund_ca_v01"
|
|
assert s.failure_mode == "escalates_unresolved"
|
|
assert len(s.branches) == 2
|
|
assert s.branch_by_id("accept_resolution") is not None
|
|
assert s.branch_by_id("escalate") is not None
|
|
assert s.debrief.model == "deepseek-v4-flash:cloud"
|
|
assert s.debrief.mode == "no_think" |