amogus/tests/test_triggers.py
Antigravity 071906df59 feat: Complete LLM agent framework with fog-of-war, meeting flow, and prompt assembly
- Core engine: simulator, game mechanics, triggers (138 tests)
- Fog-of-war per-player state tracking
- Meeting flow: interrupt, discussion, voting, consolidation
- Prompt assembler with strategy injection tiers
- LLM client with fallbacks for models without JSON/system support
- Prompt templates: action, discussion, voting, reflection
- Full integration in main.py orchestrator
- Verified working with free OpenRouter models (Gemma)
2026-02-01 00:00:34 -05:00

193 lines
6.4 KiB
Python

"""
Tests for the trigger system.
"""
import unittest
import sys
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.engine.triggers import (
TriggerRegistry, TriggerType, TriggerCondition, Trigger,
MANDATORY_TRIGGERS, STANDARD_TRIGGERS
)
class TestTriggerType(unittest.TestCase):
"""Tests for TriggerType enum."""
def test_mandatory_triggers_defined(self):
self.assertIn(TriggerType.DISCUSSION_START, MANDATORY_TRIGGERS)
self.assertIn(TriggerType.VOTE_START, MANDATORY_TRIGGERS)
self.assertIn(TriggerType.GAME_START, MANDATORY_TRIGGERS)
def test_standard_triggers_defined(self):
self.assertIn(TriggerType.BODY_IN_FOV, STANDARD_TRIGGERS)
self.assertIn(TriggerType.PLAYER_ENTERS_FOV, STANDARD_TRIGGERS)
class TestTriggerRegistry(unittest.TestCase):
"""Tests for TriggerRegistry class."""
def setUp(self):
self.registry = TriggerRegistry()
self.registry.register_agent("agent1")
self.registry.register_agent("agent2")
def test_register_agent(self):
# Agents should have standard triggers subscribed
self.assertIn(TriggerType.BODY_IN_FOV, self.registry._subscriptions["agent1"])
self.assertIn(TriggerType.PLAYER_ENTERS_FOV, self.registry._subscriptions["agent1"])
def test_subscribe(self):
self.registry.subscribe("agent1", TriggerType.INTERSECTION)
self.assertIn(TriggerType.INTERSECTION, self.registry._subscriptions["agent1"])
def test_unsubscribe(self):
self.registry.unsubscribe("agent1", TriggerType.BODY_IN_FOV)
self.assertNotIn(TriggerType.BODY_IN_FOV, self.registry._subscriptions["agent1"])
def test_cannot_unsubscribe_mandatory(self):
self.registry.unsubscribe("agent1", TriggerType.DISCUSSION_START)
# Should still fire because mandatory
self.assertTrue(
self.registry.should_fire("agent1", TriggerType.DISCUSSION_START, 0.0)
)
def test_should_fire_standard(self):
# Standard triggers should fire by default
self.assertTrue(
self.registry.should_fire("agent1", TriggerType.BODY_IN_FOV, 0.0)
)
def test_should_fire_optional_not_subscribed(self):
# Optional triggers don't fire unless subscribed
self.assertFalse(
self.registry.should_fire("agent1", TriggerType.INTERSECTION, 0.0)
)
def test_should_fire_mandatory_always(self):
# Mandatory triggers always fire
self.assertTrue(
self.registry.should_fire("agent1", TriggerType.GAME_START, 0.0)
)
def test_mute_trigger(self):
condition = TriggerCondition(
trigger_type=TriggerType.PLAYER_ENTERS_FOV,
until_time=10.0
)
self.registry.mute("agent1", condition)
# Should be muted before time expires
self.assertTrue(
self.registry.is_muted("agent1", TriggerType.PLAYER_ENTERS_FOV, 5.0)
)
# Should not fire while muted
self.assertFalse(
self.registry.should_fire("agent1", TriggerType.PLAYER_ENTERS_FOV, 5.0)
)
def test_mute_expires(self):
condition = TriggerCondition(
trigger_type=TriggerType.PLAYER_ENTERS_FOV,
until_time=10.0
)
self.registry.mute("agent1", condition)
# Should not be muted after time expires
self.assertFalse(
self.registry.is_muted("agent1", TriggerType.PLAYER_ENTERS_FOV, 15.0)
)
def test_cannot_mute_mandatory(self):
condition = TriggerCondition(
trigger_type=TriggerType.DISCUSSION_START,
until_time=100.0
)
self.registry.mute("agent1", condition)
# Mandatory should still fire
self.assertTrue(
self.registry.should_fire("agent1", TriggerType.DISCUSSION_START, 50.0)
)
def test_target_specific_mute(self):
condition = TriggerCondition(
trigger_type=TriggerType.PLAYER_ENTERS_FOV,
until_time=10.0,
target_id="player_blue"
)
self.registry.mute("agent1", condition)
# Should be muted for specific target
self.assertTrue(
self.registry.is_muted("agent1", TriggerType.PLAYER_ENTERS_FOV, 5.0, "player_blue")
)
# Should NOT be muted for different target
self.assertFalse(
self.registry.is_muted("agent1", TriggerType.PLAYER_ENTERS_FOV, 5.0, "player_red")
)
def test_clear_expired_mutes(self):
cond1 = TriggerCondition(trigger_type=TriggerType.PLAYER_ENTERS_FOV, until_time=5.0)
cond2 = TriggerCondition(trigger_type=TriggerType.PLAYER_EXITS_FOV, until_time=15.0)
self.registry.mute("agent1", cond1)
self.registry.mute("agent1", cond2)
self.assertEqual(len(self.registry._mutes["agent1"]), 2)
self.registry.clear_expired_mutes("agent1", 10.0)
self.assertEqual(len(self.registry._mutes["agent1"]), 1)
def test_get_agents_for_trigger(self):
# Add a third agent
self.registry.register_agent("agent3")
# Mute agent2
self.registry.mute("agent2", TriggerCondition(
trigger_type=TriggerType.BODY_IN_FOV,
until_time=10.0
))
agents = self.registry.get_agents_for_trigger(TriggerType.BODY_IN_FOV, 5.0)
self.assertIn("agent1", agents)
self.assertNotIn("agent2", agents) # muted
self.assertIn("agent3", agents)
def test_get_agents_with_exclude(self):
agents = self.registry.get_agents_for_trigger(
TriggerType.BODY_IN_FOV,
0.0,
exclude={"agent1"}
)
self.assertNotIn("agent1", agents)
self.assertIn("agent2", agents)
class TestTrigger(unittest.TestCase):
"""Tests for Trigger dataclass."""
def test_trigger_creation(self):
trigger = Trigger(
trigger_type=TriggerType.BODY_IN_FOV,
target_agent_id="agent1",
time=10.5,
data={"victim": "Blue"}
)
self.assertEqual(trigger.trigger_type, TriggerType.BODY_IN_FOV)
self.assertEqual(trigger.target_agent_id, "agent1")
self.assertEqual(trigger.time, 10.5)
if __name__ == "__main__":
unittest.main()