"""Tests for intelligence/loop.py — Darkplex Loop state machine and helpers.""" import json import sys import time from datetime import datetime, timezone, timedelta from pathlib import Path from unittest.mock import patch, MagicMock sys.path.insert(0, str(Path.home() / "repos" / "darkplex-core" / "intelligence")) import loop as darkplex_loop class TestImportance: def test_empty(self): assert darkplex_loop._importance("") == 0.0 def test_heartbeat_low(self): assert darkplex_loop._importance("HEARTBEAT_OK all systems nominal") < 0.2 def test_business_content_high(self): score = darkplex_loop._importance("Meeting about the project deadline and budget milestone") assert score > 0.4 def test_clamped(self): for text in ["", "x" * 1000, "meeting project company contract decision strategy"]: s = darkplex_loop._importance(text) assert 0.0 <= s <= 1.0 class TestLoopState: def test_init(self, tmp_path): with patch.object(darkplex_loop, 'STATE_FILE', tmp_path / "state.json"): state = darkplex_loop.LoopState() assert state.status == "INIT" assert state.cycle_count == 0 def test_save_and_load(self, tmp_path): sf = tmp_path / "state.json" with patch.object(darkplex_loop, 'STATE_FILE', sf): state = darkplex_loop.LoopState() state.status = "RUNNING" state.cycle_count = 5 state.save() state2 = darkplex_loop.LoopState() assert state2.status == "RUNNING" assert state2.cycle_count == 5 def test_record_success(self, tmp_path): with patch.object(darkplex_loop, 'STATE_FILE', tmp_path / "state.json"): state = darkplex_loop.LoopState() state.record_success({"test": "ok"}) assert state.status == "RUNNING" assert state.consecutive_failures == 0 assert state.cycle_count == 1 def test_record_failure_degraded(self, tmp_path): with patch.object(darkplex_loop, 'STATE_FILE', tmp_path / "state.json"): state = darkplex_loop.LoopState() state.record_failure("ingest", "timeout") assert state.status == "DEGRADED" assert state.consecutive_failures == 1 def test_record_failure_emergency(self, tmp_path): with patch.object(darkplex_loop, 'STATE_FILE', tmp_path / "state.json"): state = darkplex_loop.LoopState() for i in range(3): state.record_failure("ingest", "timeout") assert state.status == "EMERGENCY" def test_can_alert(self, tmp_path): with patch.object(darkplex_loop, 'STATE_FILE', tmp_path / "state.json"): state = darkplex_loop.LoopState() assert state.can_alert() state.mark_alerted() assert not state.can_alert() def test_record_perf(self, tmp_path): with patch.object(darkplex_loop, 'STATE_FILE', tmp_path / "state.json"): state = darkplex_loop.LoopState() state.record_perf({"total_ms": 1000, "ingest_ms": 200}) assert state.perf["total_ms"] == 1000 assert len(state.perf_history) == 1 def test_perf_averages(self, tmp_path): with patch.object(darkplex_loop, 'STATE_FILE', tmp_path / "state.json"): state = darkplex_loop.LoopState() state.record_perf({"total_ms": 1000}) state.record_perf({"total_ms": 2000}) avgs = state.perf_averages() assert avgs["total_ms"] == 1500 def test_perf_history_capped(self, tmp_path): with patch.object(darkplex_loop, 'STATE_FILE', tmp_path / "state.json"): state = darkplex_loop.LoopState() for i in range(15): state.record_perf({"total_ms": i * 100}) assert len(state.perf_history) == 10 class TestCheckNewEvents: @patch("loop.subprocess.run") def test_returns_pending(self, mock_run): mock_run.return_value = MagicMock( returncode=0, stdout=json.dumps({"num_pending": 42}) ) assert darkplex_loop.check_new_events() == 42 @patch("loop.subprocess.run") def test_returns_negative_on_failure(self, mock_run): mock_run.return_value = MagicMock(returncode=1, stdout="") assert darkplex_loop.check_new_events() == -1 @patch("loop.subprocess.run") def test_handles_exception(self, mock_run): mock_run.side_effect = Exception("nats not found") assert darkplex_loop.check_new_events() == -1