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