100 lines
3.6 KiB
Python
100 lines
3.6 KiB
Python
"""Tests for Phase 5.1 SubQuestions Pydantic model.
|
|
|
|
Validates the SubQuestions model used with LangChain with_structured_output().
|
|
Ensures proper validation of structured LLM responses.
|
|
"""
|
|
|
|
import pytest
|
|
from pydantic import ValidationError
|
|
|
|
|
|
class TestSubQuestionsModel:
|
|
"""Pydantic model tests for SubQuestions — no LLM calls needed."""
|
|
|
|
def test_valid_subquestions(self):
|
|
"""SubQuestions should accept a valid list of 2-5 strings."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
sq = SubQuestions(questions=["What is A?", "What is B?"])
|
|
assert sq.questions == ["What is A?", "What is B?"]
|
|
assert len(sq.questions) == 2
|
|
|
|
def test_min_length_one(self):
|
|
"""A single sub-question should be valid (min_length=1)."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
sq = SubQuestions(questions=["Single question"])
|
|
assert len(sq.questions) == 1
|
|
|
|
def test_max_length_five(self):
|
|
"""Up to 5 sub-questions should be valid (max_length=5)."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
sq = SubQuestions(questions=[f"Q{i}" for i in range(5)])
|
|
assert len(sq.questions) == 5
|
|
|
|
def test_empty_list_rejected(self):
|
|
"""Empty list should be rejected by min_length=1 constraint."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
with pytest.raises(ValidationError, match="questions"):
|
|
SubQuestions(questions=[])
|
|
|
|
def test_zero_questions_rejected(self):
|
|
"""Empty list should be rejected (same as above, explicit)."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
with pytest.raises(ValidationError, match="questions"):
|
|
SubQuestions(questions=[])
|
|
|
|
def test_too_many_questions_rejected(self):
|
|
"""More than 5 questions should be rejected by max_length=5."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
with pytest.raises(ValidationError, match="questions"):
|
|
SubQuestions(questions=[f"Q{i}" for i in range(10)])
|
|
|
|
def test_non_string_items_rejected(self):
|
|
"""Non-string items in the list should be rejected."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
with pytest.raises(ValidationError):
|
|
SubQuestions(questions=[1, 2, 3])
|
|
|
|
def test_missing_field_rejected(self):
|
|
"""Missing 'questions' field should be rejected."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
with pytest.raises(ValidationError):
|
|
SubQuestions() # type: ignore
|
|
|
|
def test_wrong_type_rejected(self):
|
|
"""Passing a string instead of a list should be rejected."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
with pytest.raises(ValidationError):
|
|
SubQuestions(questions="not a list") # type: ignore
|
|
|
|
def test_json_schema_generation(self):
|
|
"""JSON schema should be valid for structured output."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
schema = SubQuestions.model_json_schema()
|
|
assert schema["type"] == "object"
|
|
assert "questions" in schema["properties"]
|
|
assert schema["properties"]["questions"]["type"] == "array"
|
|
assert schema["properties"]["questions"]["items"]["type"] == "string"
|
|
|
|
def test_cantonese_questions_accepted(self):
|
|
"""Cantonese/Chinese text should be accepted as valid strings."""
|
|
from app.models.decompose import SubQuestions
|
|
|
|
sq = SubQuestions(
|
|
questions=[
|
|
"立法會今日討論什麼議題?",
|
|
"有咩重要決定?",
|
|
]
|
|
)
|
|
assert len(sq.questions) == 2
|
|
assert all(isinstance(q, str) for q in sq.questions)
|