"""Tests for InterviewState and InterviewStateManager.""" import pytest import json import tempfile from pathlib import Path from datetime import datetime from optimization_engine.interview.interview_state import ( InterviewState, InterviewPhase, InterviewStateManager, AnsweredQuestion, LogEntry, ) class TestInterviewPhase: """Tests for InterviewPhase enum.""" def test_from_string(self): """Test converting string to enum.""" assert InterviewPhase.from_string("introspection") == InterviewPhase.INTROSPECTION assert InterviewPhase.from_string("objectives") == InterviewPhase.OBJECTIVES assert InterviewPhase.from_string("complete") == InterviewPhase.COMPLETE def test_from_string_invalid(self): """Test invalid string raises error.""" with pytest.raises(ValueError): InterviewPhase.from_string("invalid_phase") def test_next_phase(self): """Test getting next phase.""" assert InterviewPhase.INTROSPECTION.next_phase() == InterviewPhase.PROBLEM_DEFINITION assert InterviewPhase.OBJECTIVES.next_phase() == InterviewPhase.CONSTRAINTS assert InterviewPhase.COMPLETE.next_phase() is None def test_previous_phase(self): """Test getting previous phase.""" assert InterviewPhase.OBJECTIVES.previous_phase() == InterviewPhase.PROBLEM_DEFINITION assert InterviewPhase.INTROSPECTION.previous_phase() is None class TestAnsweredQuestion: """Tests for AnsweredQuestion dataclass.""" def test_to_dict(self): """Test conversion to dict.""" aq = AnsweredQuestion( question_id="obj_01", answered_at="2026-01-02T10:00:00", raw_response="minimize mass", parsed_value="minimize_mass", inferred={"extractor": "E4"} ) d = aq.to_dict() assert d["question_id"] == "obj_01" assert d["parsed_value"] == "minimize_mass" assert d["inferred"]["extractor"] == "E4" def test_from_dict(self): """Test creation from dict.""" data = { "question_id": "obj_01", "answered_at": "2026-01-02T10:00:00", "raw_response": "minimize mass", "parsed_value": "minimize_mass", } aq = AnsweredQuestion.from_dict(data) assert aq.question_id == "obj_01" assert aq.parsed_value == "minimize_mass" class TestInterviewState: """Tests for InterviewState dataclass.""" def test_default_values(self): """Test default initialization.""" state = InterviewState() assert state.version == "1.0" assert state.session_id != "" assert state.current_phase == InterviewPhase.INTROSPECTION.value assert state.complexity == "simple" assert state.answers["objectives"] == [] def test_get_phase(self): """Test getting phase as enum.""" state = InterviewState(current_phase="objectives") assert state.get_phase() == InterviewPhase.OBJECTIVES def test_set_phase(self): """Test setting phase.""" state = InterviewState() state.set_phase(InterviewPhase.CONSTRAINTS) assert state.current_phase == "constraints" def test_is_complete(self): """Test completion check.""" state = InterviewState(current_phase="review") assert not state.is_complete() state.current_phase = "complete" assert state.is_complete() def test_progress_percentage(self): """Test progress calculation.""" state = InterviewState(current_phase="introspection") assert state.progress_percentage() == 0.0 state.current_phase = "complete" assert state.progress_percentage() == 100.0 def test_add_answered_question(self): """Test adding answered question.""" state = InterviewState() aq = AnsweredQuestion( question_id="pd_01", answered_at=datetime.now().isoformat(), raw_response="test", parsed_value="test" ) state.add_answered_question(aq) assert len(state.questions_answered) == 1 def test_add_warning(self): """Test adding warnings.""" state = InterviewState() state.add_warning("Test warning") assert "Test warning" in state.warnings # Duplicate should not be added state.add_warning("Test warning") assert len(state.warnings) == 1 def test_acknowledge_warning(self): """Test acknowledging warnings.""" state = InterviewState() state.add_warning("Test warning") state.acknowledge_warning("Test warning") assert "Test warning" in state.warnings_acknowledged def test_to_json(self): """Test JSON serialization.""" state = InterviewState(study_name="test_study") json_str = state.to_json() data = json.loads(json_str) assert data["study_name"] == "test_study" assert data["version"] == "1.0" def test_from_json(self): """Test JSON deserialization.""" json_str = '{"version": "1.0", "session_id": "abc", "study_name": "test", "current_phase": "objectives", "answers": {}}' state = InterviewState.from_json(json_str) assert state.study_name == "test" assert state.current_phase == "objectives" def test_validate(self): """Test state validation.""" state = InterviewState() errors = state.validate() assert "Missing study_name" in errors state.study_name = "test" errors = state.validate() assert "Missing study_name" not in errors class TestInterviewStateManager: """Tests for InterviewStateManager.""" def test_init_creates_directories(self): """Test initialization creates needed directories.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() manager = InterviewStateManager(study_path) assert (study_path / ".interview").exists() assert (study_path / ".interview" / "backups").exists() def test_save_and_load_state(self): """Test saving and loading state.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() manager = InterviewStateManager(study_path) state = InterviewState( study_name="test_study", study_path=str(study_path), current_phase="objectives" ) manager.save_state(state) assert manager.exists() loaded = manager.load_state() assert loaded is not None assert loaded.study_name == "test_study" assert loaded.current_phase == "objectives" def test_append_log(self): """Test appending to log file.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() manager = InterviewStateManager(study_path) entry = LogEntry( timestamp=datetime.now(), question_id="obj_01", question_text="What is your goal?", answer_raw="minimize mass", answer_parsed="minimize_mass" ) manager.append_log(entry) assert manager.log_file.exists() content = manager.log_file.read_text() assert "obj_01" in content assert "minimize mass" in content def test_backup_rotation(self): """Test backup rotation keeps only N backups.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() manager = InterviewStateManager(study_path) manager.MAX_BACKUPS = 3 # Create multiple saves for i in range(5): state = InterviewState( study_name=f"test_{i}", study_path=str(study_path) ) manager.save_state(state) backups = list(manager.backup_dir.glob("state_*.json")) assert len(backups) <= 3 def test_get_history(self): """Test getting modification history.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() manager = InterviewStateManager(study_path) # Save multiple states for i in range(3): state = InterviewState( study_name=f"test_{i}", study_path=str(study_path), current_phase=["objectives", "constraints", "review"][i] ) manager.save_state(state) history = manager.get_history() # Should have 2 backups (first save doesn't create backup) assert len(history) >= 1 class TestLogEntry: """Tests for LogEntry dataclass.""" def test_to_markdown(self): """Test markdown generation.""" entry = LogEntry( timestamp=datetime(2026, 1, 2, 10, 30, 0), question_id="obj_01", question_text="What is your primary optimization goal?", answer_raw="minimize mass", answer_parsed="minimize_mass", inferred={"extractor": "E4"}, warnings=["Consider safety factor"] ) md = entry.to_markdown() assert "## [2026-01-02 10:30:00]" in md assert "obj_01" in md assert "minimize mass" in md assert "Extractor" in md.lower() or "extractor" in md assert "Consider safety factor" in md