"""Integration tests for StudyInterviewEngine.""" import pytest import tempfile from pathlib import Path from optimization_engine.interview.study_interview import ( StudyInterviewEngine, InterviewSession, NextAction, run_interview, ) from optimization_engine.interview.interview_state import InterviewState, InterviewPhase class TestInterviewSession: """Tests for InterviewSession dataclass.""" def test_creation(self): """Test creating interview session.""" from datetime import datetime session = InterviewSession( session_id="abc123", study_name="test_study", study_path=Path("/tmp/test"), started_at=datetime.now(), current_phase=InterviewPhase.INTROSPECTION, introspection={} ) assert session.session_id == "abc123" assert session.study_name == "test_study" assert not session.is_complete class TestNextAction: """Tests for NextAction dataclass.""" def test_ask_question_action(self): """Test ask_question action type.""" from optimization_engine.interview.question_engine import Question question = Question( id="test", category="test", text="Test?", question_type="text", maps_to="test_field" ) action = NextAction( action_type="ask_question", question=question, message="Test question" ) assert action.action_type == "ask_question" assert action.question is not None def test_show_summary_action(self): """Test show_summary action type.""" action = NextAction( action_type="show_summary", message="Summary here" ) assert action.action_type == "show_summary" def test_error_action(self): """Test error action type.""" action = NextAction( action_type="error", message="Something went wrong" ) assert action.action_type == "error" assert "wrong" in action.message class TestStudyInterviewEngine: """Tests for StudyInterviewEngine.""" def test_init(self): """Test engine initialization.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) assert engine.study_path == study_path assert engine.state is None assert engine.presenter is not None def test_start_interview_new(self): """Test starting a new interview.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) session = engine.start_interview("test_study") assert session is not None assert session.study_name == "test_study" assert not session.is_resumed assert engine.state is not None def test_start_interview_with_introspection(self): """Test starting interview with introspection data.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) introspection = { "expressions": ["thickness", "width"], "model_path": "/path/model.prt", "sim_path": "/path/sim.sim" } session = engine.start_interview( "test_study", introspection=introspection ) assert session.introspection == introspection # Should skip introspection phase assert engine.state.get_phase() == InterviewPhase.PROBLEM_DEFINITION def test_start_interview_resume(self): """Test resuming an existing interview.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() # Start first interview engine1 = StudyInterviewEngine(study_path) session1 = engine1.start_interview("test_study") # Make some progress engine1.state.answers["study_description"] = "Test" engine1.state.set_phase(InterviewPhase.OBJECTIVES) engine1.state_manager.save_state(engine1.state) # Create new engine and resume engine2 = StudyInterviewEngine(study_path) session2 = engine2.start_interview("test_study") assert session2.is_resumed assert engine2.state.get_phase() == InterviewPhase.OBJECTIVES def test_get_first_question(self): """Test getting first question.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={"expressions": []}) action = engine.get_first_question() assert action.action_type == "ask_question" assert action.question is not None assert action.message is not None def test_get_first_question_without_start(self): """Test error when getting question without starting.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) action = engine.get_first_question() assert action.action_type == "error" def test_process_answer(self): """Test processing an answer.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={}) # Get first question action = engine.get_first_question() assert action.question is not None # Answer it next_action = engine.process_answer("This is my test study description") # May get next question, show summary, error, or confirm_warning assert next_action.action_type in ["ask_question", "show_summary", "error", "confirm_warning"] def test_process_answer_invalid(self): """Test processing an invalid answer.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={}) engine.get_first_question() # For a required question, empty answer should fail # This depends on question validation rules # Just verify we don't crash action = engine.process_answer("") assert action.action_type in ["error", "ask_question"] def test_full_simple_interview_flow(self): """Test complete simple interview flow.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={ "expressions": [ {"name": "thickness", "value": 5.0}, {"name": "width", "value": 10.0} ], "model_path": "/model.prt", "sim_path": "/sim.sim" }) # Simulate answering questions answers = [ "Bracket mass optimization", # study description "minimize mass", # objective "1", # single objective confirm "stress, 200 MPa", # constraint "thickness, width", # design variables "yes", # confirm settings ] action = engine.get_first_question() max_iterations = 20 for i, answer in enumerate(answers): if action.action_type == "show_summary": break if action.action_type == "error": # Try to recover continue action = engine.process_answer(answer) if i > max_iterations: break # Should eventually complete or show summary # (exact behavior depends on question flow) def test_acknowledge_warnings(self): """Test acknowledging warnings.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={}) # Add some warnings engine.state.add_warning("Test warning 1") engine.state.add_warning("Test warning 2") action = engine.acknowledge_warnings(acknowledged=True) # Warnings should be acknowledged assert "Test warning 1" in engine.state.warnings_acknowledged assert "Test warning 2" in engine.state.warnings_acknowledged def test_acknowledge_warnings_rejected(self): """Test rejecting warnings pauses interview.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={}) engine.state.add_warning("Test warning") action = engine.acknowledge_warnings(acknowledged=False) assert action.action_type == "error" def test_generate_blueprint(self): """Test blueprint generation.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={ "model_path": "/model.prt", "sim_path": "/sim.sim" }) # Set up minimal answers for blueprint engine.state.answers = { "study_description": "Test", "objectives": [{"goal": "minimize_mass"}], "constraints": [{"type": "stress", "threshold": 200}], "design_variables": [{"name": "t", "min": 1, "max": 10}], } blueprint = engine.generate_blueprint() assert blueprint is not None assert blueprint.study_name == "test_study" assert len(blueprint.objectives) == 1 def test_modify_blueprint(self): """Test blueprint modification.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={}) # Set up and generate blueprint engine.state.answers = { "study_description": "Test", "objectives": [{"goal": "minimize_mass"}], "constraints": [], "design_variables": [{"name": "t", "min": 1, "max": 10}], } engine.generate_blueprint() # Modify n_trials modified = engine.modify_blueprint({"n_trials": 200}) assert modified.n_trials == 200 def test_confirm_blueprint(self): """Test confirming blueprint.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={}) engine.state.answers = { "study_description": "Test", "objectives": [{"goal": "minimize_mass"}], "constraints": [], "design_variables": [{"name": "t", "min": 1, "max": 10}], } engine.generate_blueprint() result = engine.confirm_blueprint() assert result is True assert engine.state.get_phase() == InterviewPhase.COMPLETE def test_get_progress(self): """Test getting progress string.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={}) progress = engine.get_progress() assert isinstance(progress, str) assert len(progress) > 0 def test_reset_interview(self): """Test resetting interview.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) engine.start_interview("test_study", introspection={}) # Make some progress engine.state.answers["test"] = "value" engine.reset_interview() assert engine.state is None assert engine.session is None def test_get_current_state(self): """Test getting current state.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = StudyInterviewEngine(study_path) assert engine.get_current_state() is None engine.start_interview("test_study", introspection={}) state = engine.get_current_state() assert state is not None assert state.study_name == "test_study" class TestRunInterview: """Tests for run_interview convenience function.""" def test_run_interview(self): """Test run_interview function.""" with tempfile.TemporaryDirectory() as tmpdir: study_path = Path(tmpdir) / "test_study" study_path.mkdir() engine = run_interview( study_path, "test_study", introspection={"expressions": []} ) assert engine is not None assert engine.state is not None assert engine.session is not None