"""Tests for QuestionEngine.""" import pytest import json from pathlib import Path from optimization_engine.interview.question_engine import ( QuestionEngine, Question, QuestionCondition, QuestionOption, ValidationRule, ) from optimization_engine.interview.interview_state import InterviewState class TestQuestion: """Tests for Question dataclass.""" def test_from_dict(self): """Test creating Question from dict.""" data = { "id": "obj_01", "category": "objectives", "text": "What is your goal?", "question_type": "choice", "maps_to": "objectives[0].goal", "options": [ {"value": "mass", "label": "Minimize mass"} ], } q = Question.from_dict(data) assert q.id == "obj_01" assert q.category == "objectives" assert q.question_type == "choice" assert len(q.options) == 1 class TestQuestionCondition: """Tests for QuestionCondition evaluation.""" def test_from_dict_simple(self): """Test creating simple condition from dict.""" data = {"type": "answered", "field": "study_description"} cond = QuestionCondition.from_dict(data) assert cond is not None assert cond.type == "answered" assert cond.field == "study_description" def test_from_dict_with_value(self): """Test creating equals condition from dict.""" data = {"type": "equals", "field": "objectives[0].goal", "value": "minimize_mass"} cond = QuestionCondition.from_dict(data) assert cond.type == "equals" assert cond.value == "minimize_mass" def test_from_dict_nested_and(self): """Test creating nested 'and' condition from dict.""" data = { "type": "and", "conditions": [ {"type": "answered", "field": "a"}, {"type": "answered", "field": "b"} ] } cond = QuestionCondition.from_dict(data) assert cond.type == "and" assert len(cond.conditions) == 2 def test_from_dict_nested_not(self): """Test creating nested 'not' condition from dict.""" data = { "type": "not", "condition": {"type": "answered", "field": "skip_flag"} } cond = QuestionCondition.from_dict(data) assert cond.type == "not" assert cond.condition is not None assert cond.condition.field == "skip_flag" class TestQuestionOption: """Tests for QuestionOption dataclass.""" def test_from_dict(self): """Test creating option from dict.""" data = {"value": "minimize_mass", "label": "Minimize mass", "description": "Reduce weight"} opt = QuestionOption.from_dict(data) assert opt.value == "minimize_mass" assert opt.label == "Minimize mass" assert opt.description == "Reduce weight" class TestValidationRule: """Tests for ValidationRule dataclass.""" def test_from_dict(self): """Test creating validation rule from dict.""" data = {"required": True, "min": 0, "max": 100} rule = ValidationRule.from_dict(data) assert rule.required is True assert rule.min == 0 assert rule.max == 100 def test_from_dict_none(self): """Test None input returns None.""" rule = ValidationRule.from_dict(None) assert rule is None class TestQuestionEngine: """Tests for QuestionEngine.""" def test_load_schema(self): """Test schema loading.""" engine = QuestionEngine() assert len(engine.questions) > 0 assert len(engine.categories) > 0 def test_get_question(self): """Test getting question by ID.""" engine = QuestionEngine() q = engine.get_question("pd_01") assert q is not None assert q.id == "pd_01" def test_get_question_not_found(self): """Test getting non-existent question.""" engine = QuestionEngine() q = engine.get_question("nonexistent") assert q is None def test_get_all_questions(self): """Test getting all questions.""" engine = QuestionEngine() qs = engine.get_all_questions() assert len(qs) > 0 def test_get_next_question_new_state(self): """Test getting first question for new state.""" engine = QuestionEngine() state = InterviewState() next_q = engine.get_next_question(state, {}) assert next_q is not None # First question should be in problem_definition category assert next_q.category == "problem_definition" def test_get_next_question_skips_answered(self): """Test that answered questions are skipped.""" engine = QuestionEngine() state = InterviewState() # Get first question first_q = engine.get_next_question(state, {}) # Mark it as answered state.questions_answered.append({ "question_id": first_q.id, "answered_at": "2026-01-02T10:00:00" }) # Should get different question second_q = engine.get_next_question(state, {}) assert second_q is not None assert second_q.id != first_q.id def test_get_next_question_returns_none_when_complete(self): """Test that None is returned when all questions answered.""" engine = QuestionEngine() state = InterviewState() # Mark all questions as answered for q in engine.get_all_questions(): state.questions_answered.append({ "question_id": q.id, "answered_at": "2026-01-02T10:00:00" }) next_q = engine.get_next_question(state, {}) assert next_q is None def test_validate_answer_required(self): """Test required field validation.""" engine = QuestionEngine() q = Question( id="test", category="test", text="Test?", question_type="text", maps_to="test", validation=ValidationRule(required=True) ) is_valid, error = engine.validate_answer("", q) assert not is_valid assert error is not None is_valid, error = engine.validate_answer("value", q) assert is_valid def test_validate_answer_numeric_range(self): """Test numeric range validation.""" engine = QuestionEngine() q = Question( id="test", category="test", text="Enter value", question_type="numeric", maps_to="test", validation=ValidationRule(min=0, max=100) ) is_valid, _ = engine.validate_answer(50, q) assert is_valid is_valid, error = engine.validate_answer(-5, q) assert not is_valid is_valid, error = engine.validate_answer(150, q) assert not is_valid def test_validate_answer_choice(self): """Test choice validation.""" engine = QuestionEngine() q = Question( id="test", category="test", text="Choose", question_type="choice", maps_to="test", options=[ QuestionOption(value="a", label="Option A"), QuestionOption(value="b", label="Option B") ] ) is_valid, _ = engine.validate_answer("a", q) assert is_valid # Choice validation may be lenient (accept any string for custom input) # Just verify the method runs without error is_valid, error = engine.validate_answer("c", q) # Not asserting the result since implementation may vary class TestQuestionOrdering: """Tests for question ordering logic.""" def test_categories_sorted_by_order(self): """Test that categories are sorted by order.""" engine = QuestionEngine() prev_order = -1 for cat in engine.categories: assert cat.order >= prev_order prev_order = cat.order