legco_ai_assistant/backend/app/test/test_phase5_subquestions_mo...

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)