Files
praxis/tests/test_scenario_schema.py
T
Praxis CI fbd6602814 docs(milestone): complete v0.1 foundation
---ci---
phase: 0
milestone: v0.1
status: complete
---/ci---
2026-08-01 13:32:48 +00:00

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"