196 lines
7.0 KiB
Python
196 lines
7.0 KiB
Python
"""Tests for agent emotional state simulation (src/timmy/agents/emotional_state.py)."""
|
|
|
|
import time
|
|
|
|
from timmy.agents.emotional_state import (
|
|
EMOTION_PROMPT_MODIFIERS,
|
|
EMOTIONAL_STATES,
|
|
EVENT_TRANSITIONS,
|
|
EmotionalState,
|
|
EmotionalStateTracker,
|
|
_intensity_label,
|
|
)
|
|
|
|
|
|
class TestEmotionalState:
|
|
"""Test the EmotionalState dataclass."""
|
|
|
|
def test_defaults(self):
|
|
state = EmotionalState()
|
|
assert state.current_emotion == "calm"
|
|
assert state.intensity == 0.5
|
|
assert state.previous_emotion == "calm"
|
|
assert state.trigger_event == ""
|
|
|
|
def test_to_dict_includes_label(self):
|
|
state = EmotionalState(current_emotion="analytical")
|
|
d = state.to_dict()
|
|
assert d["emotion_label"] == "Analytical"
|
|
assert d["current_emotion"] == "analytical"
|
|
|
|
def test_to_dict_all_fields(self):
|
|
state = EmotionalState(
|
|
current_emotion="frustrated",
|
|
intensity=0.8,
|
|
previous_emotion="calm",
|
|
trigger_event="task_failure",
|
|
)
|
|
d = state.to_dict()
|
|
assert d["current_emotion"] == "frustrated"
|
|
assert d["intensity"] == 0.8
|
|
assert d["previous_emotion"] == "calm"
|
|
assert d["trigger_event"] == "task_failure"
|
|
|
|
|
|
class TestEmotionalStates:
|
|
"""Validate the emotional states and transitions are well-defined."""
|
|
|
|
def test_all_states_are_strings(self):
|
|
for state in EMOTIONAL_STATES:
|
|
assert isinstance(state, str)
|
|
|
|
def test_all_states_have_prompt_modifiers(self):
|
|
for state in EMOTIONAL_STATES:
|
|
assert state in EMOTION_PROMPT_MODIFIERS
|
|
|
|
def test_all_transitions_target_valid_states(self):
|
|
for event_type, (emotion, intensity) in EVENT_TRANSITIONS.items():
|
|
assert emotion in EMOTIONAL_STATES, f"{event_type} targets unknown state: {emotion}"
|
|
assert 0.0 <= intensity <= 1.0, f"{event_type} has invalid intensity: {intensity}"
|
|
|
|
|
|
class TestEmotionalStateTracker:
|
|
"""Test the EmotionalStateTracker."""
|
|
|
|
def test_initial_emotion_default(self):
|
|
tracker = EmotionalStateTracker()
|
|
assert tracker.state.current_emotion == "calm"
|
|
|
|
def test_initial_emotion_custom(self):
|
|
tracker = EmotionalStateTracker(initial_emotion="analytical")
|
|
assert tracker.state.current_emotion == "analytical"
|
|
|
|
def test_initial_emotion_invalid_falls_back(self):
|
|
tracker = EmotionalStateTracker(initial_emotion="invalid_state")
|
|
assert tracker.state.current_emotion == "calm"
|
|
|
|
def test_process_known_event(self):
|
|
tracker = EmotionalStateTracker()
|
|
state = tracker.process_event("task_success")
|
|
assert state.current_emotion == "confident"
|
|
assert state.trigger_event == "task_success"
|
|
assert state.previous_emotion == "calm"
|
|
|
|
def test_process_unknown_event_ignored(self):
|
|
tracker = EmotionalStateTracker()
|
|
state = tracker.process_event("unknown_event_xyz")
|
|
assert state.current_emotion == "calm" # unchanged
|
|
|
|
def test_repeated_same_emotion_amplifies(self):
|
|
tracker = EmotionalStateTracker()
|
|
tracker.process_event("task_success")
|
|
initial_intensity = tracker.state.intensity
|
|
tracker.process_event("user_praise") # also targets confident
|
|
assert tracker.state.intensity >= initial_intensity
|
|
|
|
def test_different_emotion_replaces(self):
|
|
tracker = EmotionalStateTracker()
|
|
tracker.process_event("task_success")
|
|
assert tracker.state.current_emotion == "confident"
|
|
tracker.process_event("task_failure")
|
|
assert tracker.state.current_emotion == "frustrated"
|
|
assert tracker.state.previous_emotion == "confident"
|
|
|
|
def test_decay_no_effect_when_recent(self):
|
|
tracker = EmotionalStateTracker()
|
|
tracker.process_event("task_failure")
|
|
emotion_before = tracker.state.current_emotion
|
|
tracker.decay()
|
|
assert tracker.state.current_emotion == emotion_before
|
|
|
|
def test_decay_resets_to_calm_after_long_time(self):
|
|
tracker = EmotionalStateTracker()
|
|
tracker.process_event("task_failure")
|
|
assert tracker.state.current_emotion == "frustrated"
|
|
|
|
# Simulate passage of time (30+ minutes)
|
|
tracker.state.updated_at = time.time() - 2000
|
|
tracker.decay()
|
|
assert tracker.state.current_emotion == "calm"
|
|
|
|
def test_get_profile_returns_expected_keys(self):
|
|
tracker = EmotionalStateTracker()
|
|
profile = tracker.get_profile()
|
|
assert "current_emotion" in profile
|
|
assert "emotion_label" in profile
|
|
assert "intensity" in profile
|
|
assert "intensity_label" in profile
|
|
assert "previous_emotion" in profile
|
|
assert "trigger_event" in profile
|
|
assert "prompt_modifier" in profile
|
|
|
|
def test_get_prompt_modifier_returns_string(self):
|
|
tracker = EmotionalStateTracker(initial_emotion="cautious")
|
|
modifier = tracker.get_prompt_modifier()
|
|
assert isinstance(modifier, str)
|
|
assert "cautious" in modifier.lower()
|
|
|
|
def test_reset(self):
|
|
tracker = EmotionalStateTracker()
|
|
tracker.process_event("task_failure")
|
|
tracker.reset()
|
|
assert tracker.state.current_emotion == "calm"
|
|
assert tracker.state.intensity == 0.5
|
|
|
|
def test_process_event_with_context(self):
|
|
"""Context dict is accepted without error."""
|
|
tracker = EmotionalStateTracker()
|
|
state = tracker.process_event("error", {"details": "connection timeout"})
|
|
assert state.current_emotion == "cautious"
|
|
|
|
def test_event_chain_scenario(self):
|
|
"""Simulate: task assigned → success → new discovery → idle."""
|
|
tracker = EmotionalStateTracker()
|
|
|
|
tracker.process_event("task_assigned")
|
|
assert tracker.state.current_emotion == "analytical"
|
|
|
|
tracker.process_event("task_success")
|
|
assert tracker.state.current_emotion == "confident"
|
|
|
|
tracker.process_event("new_discovery")
|
|
assert tracker.state.current_emotion == "curious"
|
|
|
|
tracker.process_event("idle")
|
|
assert tracker.state.current_emotion == "calm"
|
|
|
|
def test_health_events(self):
|
|
tracker = EmotionalStateTracker()
|
|
tracker.process_event("health_low")
|
|
assert tracker.state.current_emotion == "cautious"
|
|
|
|
tracker.process_event("health_recovered")
|
|
assert tracker.state.current_emotion == "calm"
|
|
|
|
def test_quest_completed_triggers_adventurous(self):
|
|
tracker = EmotionalStateTracker()
|
|
tracker.process_event("quest_completed")
|
|
assert tracker.state.current_emotion == "adventurous"
|
|
|
|
|
|
class TestIntensityLabel:
|
|
def test_overwhelming(self):
|
|
assert _intensity_label(0.9) == "overwhelming"
|
|
|
|
def test_strong(self):
|
|
assert _intensity_label(0.7) == "strong"
|
|
|
|
def test_moderate(self):
|
|
assert _intensity_label(0.5) == "moderate"
|
|
|
|
def test_mild(self):
|
|
assert _intensity_label(0.3) == "mild"
|
|
|
|
def test_faint(self):
|
|
assert _intensity_label(0.1) == "faint"
|