Files
Atomizer/tests/interview/test_interview_state.py

296 lines
9.7 KiB
Python
Raw Permalink Normal View History

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