| """ |
| Sentinel β Aggressive Full Test Suite |
| Tests every module path including new: OpenRouter 30B-A3B model, Modal RAG local methods, |
| cost tracker prefix fix verification, trigger_history cap, and all gatekeeper trigger conditions. |
| """ |
| import sys |
| import os |
| import time |
| import math |
| import base64 |
| import asyncio |
| import unittest |
| import threading |
| from unittest.mock import MagicMock, AsyncMock, patch |
| import numpy as np |
| import cv2 |
|
|
| sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) |
|
|
| from cost_tracker import CostTracker, CallRecord |
| from gatekeeper import ( |
| GatekeeperDecision, |
| FrameChangeResult, |
| Detection, |
| AudioClass, |
| PoseData, |
| SensorSnapshot, |
| TriggerDecision, |
| FrameChangeDetector, |
| PoseAnalyzer, |
| AudioMonitor, |
| ) |
| from cohere_rag import SafetyKnowledgeBase |
| from fallback import FallbackRouter |
|
|
|
|
| |
| |
| |
| class TestCostTracker(unittest.TestCase): |
|
|
| def setUp(self): |
| self.tracker = CostTracker() |
|
|
| |
| def test_initialization_zero_state(self): |
| s = self.tracker.get_stats() |
| self.assertEqual(s["total_calls"], 0) |
| self.assertEqual(s["total_tokens"], 0) |
| self.assertEqual(s["total_cost_usd"], 0.0) |
| self.assertEqual(s["naive_cost_usd"], 0.0) |
| self.assertEqual(s["savings_pct"], 0.0) |
| self.assertEqual(s["frames_processed"], 0) |
|
|
| |
| def test_log_frame_increments(self): |
| for _ in range(7): |
| self.tracker.log_frame() |
| s = self.tracker.get_stats() |
| self.assertEqual(s["frames_processed"], 7) |
| self.assertAlmostEqual(s["naive_cost_usd"], 0.07, places=4) |
| self.assertEqual(s["gatekeeper_filtered"], 7) |
|
|
| |
| def test_nemotron_rate_exact(self): |
| |
| self.tracker.log("nemotron-4b", 1000, 300) |
| self.assertAlmostEqual(self.tracker.get_stats()["total_cost_usd"], 0.0003, places=7) |
|
|
| |
| def test_minicpm_rate_exact(self): |
| |
| self.tracker.log("minicpm-v", 500, 200) |
| self.assertAlmostEqual(self.tracker.get_stats()["total_cost_usd"], 0.0001, places=7) |
|
|
| |
| def test_kokoro_flat_rate(self): |
| self.tracker.log("kokoro-tts", 0, 100) |
| self.assertAlmostEqual(self.tracker.get_stats()["total_cost_usd"], 0.0001, places=7) |
|
|
| |
| def test_fallback_openrouter_prefix_nonzero_cost(self): |
| self.tracker.log("fallback-openrouter", 200, 500) |
| s = self.tracker.get_stats() |
| |
| self.assertGreater(s["total_cost_usd"], 0.0, "fallback-openrouter cost must be non-zero") |
|
|
| def test_fallback_openbmb_prefix_nonzero_cost(self): |
| self.tracker.log("fallback-openbmb", 200, 500) |
| s = self.tracker.get_stats() |
| self.assertGreater(s["total_cost_usd"], 0.0, "fallback-openbmb cost must be non-zero") |
|
|
| |
| def test_savings_99_percent(self): |
| for _ in range(10): |
| self.tracker.log_frame() |
| self.tracker.log("nemotron-4b", 100, 400) |
| self.tracker.log("nemotron-4b", 100, 400) |
| s = self.tracker.get_stats() |
| self.assertAlmostEqual(s["total_cost_usd"], 0.00006, places=7) |
| self.assertAlmostEqual(s["naive_cost_usd"], 0.10, places=4) |
| self.assertAlmostEqual(s["savings_pct"], 99.94, places=2) |
|
|
| |
| def test_reset_clears_all_state(self): |
| self.tracker.log_frame() |
| self.tracker.log("nemotron-4b", 100, 200) |
| self.tracker.reset() |
| s = self.tracker.get_stats() |
| self.assertEqual(s["total_calls"], 0) |
| self.assertEqual(s["frames_processed"], 0) |
| self.assertEqual(s["total_cost_usd"], 0.0) |
|
|
| |
| def test_gradio_rows_order_newest_first(self): |
| for i in range(5): |
| self.tracker.log(f"model-{i}", 10, 100) |
| time.sleep(0.01) |
| rows = self.tracker.get_gradio_rows() |
| |
| self.assertIn("model-4", rows[0][1]) |
|
|
| def test_gradio_rows_cap_at_20(self): |
| for i in range(30): |
| self.tracker.log("nemotron-4b", 10, 50) |
| rows = self.tracker.get_gradio_rows() |
| self.assertLessEqual(len(rows), 20) |
|
|
| |
| def test_thread_safety_250_workers(self): |
| def worker(): |
| for _ in range(50): |
| self.tracker.log_frame() |
| self.tracker.log("nemotron-4b", 10, 50) |
|
|
| threads = [threading.Thread(target=worker) for _ in range(5)] |
| for t in threads: t.start() |
| for t in threads: t.join() |
| s = self.tracker.get_stats() |
| self.assertEqual(s["frames_processed"], 250) |
| self.assertEqual(s["total_calls"], 250) |
|
|
| |
| def test_hourly_breakdown_non_empty(self): |
| self.tracker.log("nemotron-4b", 100, 300) |
| breakdown = self.tracker.get_hourly_breakdown() |
| self.assertIsInstance(breakdown, list) |
| self.assertGreater(len(breakdown), 0) |
| self.assertIn("hour", breakdown[0]) |
| self.assertIn("cost", breakdown[0]) |
|
|
|
|
| |
| |
| |
| class TestGatekeeperDecision(unittest.TestCase): |
|
|
| def setUp(self): |
| self.engine = GatekeeperDecision(cooldown_seconds=2.0) |
|
|
| def _no_signal(self): |
| return FrameChangeResult(change_pct=0.5, is_significant=False, threshold_used=5.0, motion_regions=0) |
|
|
| |
| def test_no_trigger_idle_scene(self): |
| d = self.engine.decide(self._no_signal(), [], [], None) |
| self.assertFalse(d.should_trigger) |
| self.assertEqual(d.urgency, "none") |
| self.assertEqual(d.confidence, 0.0) |
|
|
| |
| def test_manual_query_highest_priority(self): |
| d = self.engine.decide(self._no_signal(), [], [], None, user_query="What is ahead?") |
| self.assertTrue(d.should_trigger) |
| self.assertEqual(d.urgency, "info") |
| self.assertIn("Manual user query", d.reason) |
| self.assertEqual(d.confidence, 1.0) |
|
|
| def test_empty_query_string_does_not_trigger(self): |
| d = self.engine.decide(self._no_signal(), [], [], None, user_query=" ") |
| self.assertFalse(d.should_trigger) |
|
|
| |
| def test_critical_scream_triggers(self): |
| audio = [AudioClass(class_name="Scream", confidence=0.82, alert_level="critical")] |
| d = self.engine.decide(self._no_signal(), [], audio, None) |
| self.assertTrue(d.should_trigger) |
| self.assertEqual(d.urgency, "critical") |
| self.assertIn("Critical sound detected", d.reason) |
|
|
| def test_critical_explosion_triggers(self): |
| audio = [AudioClass(class_name="Explosion", confidence=0.72, alert_level="critical")] |
| d = self.engine.decide(self._no_signal(), [], audio, None) |
| self.assertTrue(d.should_trigger) |
| self.assertEqual(d.urgency, "critical") |
|
|
| def test_warning_audio_only_triggers_on_first_call(self): |
| |
| audio = [AudioClass(class_name="Siren", confidence=0.65, alert_level="warning")] |
| d1 = self.engine.decide(self._no_signal(), [], audio, None) |
| |
| if d1.should_trigger: |
| d2 = self.engine.decide(self._no_signal(), [], audio, None) |
| self.assertFalse(d2.should_trigger) |
|
|
| |
| def test_fall_high_confidence_triggers(self): |
| pose = PoseData(head_y=0.85, hip_y=0.55, shoulder_y=0.70, is_fall=True, is_sitting=False, confidence=0.92) |
| d = self.engine.decide(self._no_signal(), [], [], pose) |
| self.assertTrue(d.should_trigger) |
| self.assertEqual(d.urgency, "critical") |
| self.assertIn("Fall detected", d.reason) |
|
|
| def test_fall_low_confidence_no_trigger(self): |
| |
| pose = PoseData(head_y=0.85, hip_y=0.55, shoulder_y=0.70, is_fall=True, is_sitting=False, confidence=0.5) |
| d = self.engine.decide(self._no_signal(), [], [], pose) |
| self.assertFalse(d.should_trigger) |
|
|
| def test_sitting_does_not_trigger_fall(self): |
| pose = PoseData(head_y=0.3, hip_y=0.7, shoulder_y=0.5, is_fall=False, is_sitting=True, confidence=0.9) |
| d = self.engine.decide(self._no_signal(), [], [], pose) |
| self.assertFalse(d.should_trigger) |
|
|
| |
| def test_motion_and_person_triggers_warning(self): |
| motion = FrameChangeResult(change_pct=9.0, is_significant=True, threshold_used=5.0, motion_regions=2) |
| dets = [Detection(class_name="person", confidence=0.88, bbox=(0,0,100,100), area_pct=12.0)] |
| d = self.engine.decide(motion, dets, [], None) |
| self.assertTrue(d.should_trigger) |
| self.assertEqual(d.urgency, "warning") |
| self.assertIn("Motion + Person presence", d.reason) |
|
|
| def test_motion_without_person_no_trigger(self): |
| motion = FrameChangeResult(change_pct=9.0, is_significant=True, threshold_used=5.0, motion_regions=2) |
| d = self.engine.decide(motion, [], [], None) |
| self.assertFalse(d.should_trigger) |
|
|
| def test_person_without_motion_no_trigger(self): |
| dets = [Detection(class_name="person", confidence=0.88, bbox=(0,0,100,100), area_pct=12.0)] |
| d = self.engine.decide(self._no_signal(), dets, [], None) |
| self.assertFalse(d.should_trigger) |
|
|
| |
| def test_sensor_anomaly_accel_gyro_triggers(self): |
| sensors = SensorSnapshot( |
| accelerometer=(20.0, 20.0, 20.0), |
| gyroscope=(4.0, 4.0, 4.0), |
| gps=(37.7749, -122.4194), |
| light_level=100.0, battery_pct=80.0, heading=90.0 |
| ) |
| d = self.engine.decide(self._no_signal(), [], [], None, sensor_data=sensors) |
| self.assertTrue(d.should_trigger) |
| self.assertEqual(d.urgency, "critical") |
| self.assertIn("Sensor anomaly", d.reason) |
|
|
| def test_sensor_accel_spike_only_no_gyro_no_trigger(self): |
| sensors = SensorSnapshot( |
| accelerometer=(20.0, 20.0, 20.0), |
| gyroscope=(0.1, 0.1, 0.1), |
| gps=None, light_level=100.0, battery_pct=80.0, heading=0.0 |
| ) |
| d = self.engine.decide(self._no_signal(), [], [], None, sensor_data=sensors) |
| self.assertFalse(d.should_trigger) |
|
|
| |
| def test_environmental_darkness_movement_triggers(self): |
| s1 = SensorSnapshot(gps=(37.7749, -122.4194), light_level=2.0) |
| self.engine.decide(self._no_signal(), [], [], None, sensor_data=s1) |
| s2 = SensorSnapshot(gps=(37.7750, -122.4195), light_level=2.0) |
| d = self.engine.decide(self._no_signal(), [], [], None, sensor_data=s2) |
| self.assertTrue(d.should_trigger) |
| self.assertIn("Sudden darkness", d.reason) |
|
|
| def test_environmental_darkness_no_movement_no_trigger(self): |
| s1 = SensorSnapshot(gps=(37.7749, -122.4194), light_level=2.0) |
| self.engine.decide(self._no_signal(), [], [], None, sensor_data=s1) |
| s2 = SensorSnapshot(gps=(37.7749, -122.4194), light_level=2.0) |
| d = self.engine.decide(self._no_signal(), [], [], None, sensor_data=s2) |
| self.assertFalse(d.should_trigger) |
|
|
| |
| def test_warning_suppressed_within_cooldown(self): |
| motion = FrameChangeResult(change_pct=9.0, is_significant=True, threshold_used=5.0, motion_regions=1) |
| dets = [Detection(class_name="person", confidence=0.85, bbox=(0,0,50,50), area_pct=6.0)] |
| d1 = self.engine.decide(motion, dets, [], None) |
| self.assertTrue(d1.should_trigger) |
| d2 = self.engine.decide(motion, dets, [], None) |
| self.assertFalse(d2.should_trigger) |
| self.assertIn("suppressed due to cooldown", d2.reason) |
|
|
| def test_critical_bypasses_cooldown(self): |
| audio = [AudioClass(class_name="Scream", confidence=0.85, alert_level="critical")] |
| d1 = self.engine.decide(self._no_signal(), [], audio, None) |
| d2 = self.engine.decide(self._no_signal(), [], audio, None) |
| self.assertTrue(d1.should_trigger) |
| self.assertTrue(d2.should_trigger) |
|
|
| |
| def test_multi_signal_confidence_capped_at_1(self): |
| audio = [ |
| AudioClass(class_name="Scream", confidence=0.9, alert_level="critical"), |
| AudioClass(class_name="Crash", confidence=0.8, alert_level="critical"), |
| ] |
| d = self.engine.decide(self._no_signal(), [], audio, None) |
| self.assertLessEqual(d.confidence, 1.0) |
| self.assertGreater(d.confidence, 0.9) |
|
|
| |
| def test_signals_dict_keys_present(self): |
| d = self.engine.decide(self._no_signal(), [], [], None) |
| for key in ("frame_change_pct", "motion_significant", "motion_regions", |
| "detections", "audio_events", "pose", "sensor", "user_query"): |
| self.assertIn(key, d.signals) |
|
|
| |
| def test_trigger_history_grows(self): |
| for _ in range(50): |
| self.engine.decide(self._no_signal(), [], [], None) |
| self.assertGreater(len(self.engine.trigger_history), 0) |
| |
| self.assertEqual(len(self.engine.trigger_history), 50) |
|
|
|
|
| |
| |
| |
| class TestFrameChangeDetector(unittest.TestCase): |
|
|
| def setUp(self): |
| self.detector = FrameChangeDetector(threshold_pct=5.0, adaptive=False) |
|
|
| def test_first_frame_zero_change(self): |
| frame = np.zeros((480, 640, 3), dtype=np.uint8) |
| r = self.detector.detect(frame) |
| self.assertEqual(r.change_pct, 0.0) |
| self.assertFalse(r.is_significant) |
| self.assertEqual(r.motion_regions, 0) |
|
|
| def test_identical_frames_zero_change(self): |
| frame = np.ones((480, 640, 3), dtype=np.uint8) * 100 |
| self.detector.detect(frame) |
| r = self.detector.detect(frame) |
| self.assertEqual(r.change_pct, 0.0) |
| self.assertFalse(r.is_significant) |
|
|
| def test_half_frame_change_significant(self): |
| f1 = np.zeros((480, 640, 3), dtype=np.uint8) |
| f2 = np.zeros((480, 640, 3), dtype=np.uint8) |
| f2[240:, :, :] = 255 |
| self.detector.detect(f1) |
| r = self.detector.detect(f2) |
| self.assertGreater(r.change_pct, 40.0) |
| self.assertTrue(r.is_significant) |
|
|
| def test_small_change_below_threshold(self): |
| f1 = np.zeros((480, 640, 3), dtype=np.uint8) |
| f2 = np.zeros((480, 640, 3), dtype=np.uint8) |
| |
| f2[0:5, 0:8, :] = 200 |
| self.detector.detect(f1) |
| r = self.detector.detect(f2) |
| self.assertFalse(r.is_significant) |
|
|
| def test_reset_clears_history(self): |
| frame = np.zeros((480, 640, 3), dtype=np.uint8) |
| self.detector.detect(frame) |
| self.detector.reset() |
| self.assertIsNone(self.detector.previous_frame) |
| self.assertEqual(len(self.detector.change_history), 0) |
|
|
| def test_none_frame_returns_zeros(self): |
| r = self.detector.detect(None) |
| self.assertEqual(r.change_pct, 0.0) |
| self.assertFalse(r.is_significant) |
|
|
| def test_motion_regions_count(self): |
| f1 = np.zeros((480, 640, 3), dtype=np.uint8) |
| f2 = np.zeros((480, 640, 3), dtype=np.uint8) |
| |
| cv2.rectangle(f2, (50, 50), (100, 100), (255, 255, 255), -1) |
| cv2.rectangle(f2, (250, 250), (300, 300), (255, 255, 255), -1) |
| cv2.rectangle(f2, (450, 350), (500, 400), (255, 255, 255), -1) |
| self.detector.detect(f1) |
| r = self.detector.detect(f2) |
| self.assertGreaterEqual(r.motion_regions, 1) |
|
|
| def test_adaptive_threshold_adjusts(self): |
| detector = FrameChangeDetector(threshold_pct=5.0, adaptive=True) |
| frame = np.zeros((480, 640, 3), dtype=np.uint8) |
| |
| for _ in range(15): |
| detector.detect(frame) |
| |
| frame2 = frame.copy() |
| frame2[0:2, 0:2, :] = 50 |
| r = detector.detect(frame2) |
| self.assertFalse(r.is_significant) |
|
|
|
|
| |
| |
| |
| class TestPoseAnalyzerStub(unittest.TestCase): |
|
|
| def test_stub_returns_none_on_bad_frame(self): |
| analyzer = PoseAnalyzer() |
| result = analyzer.analyze(None) |
| self.assertIsNone(result) |
|
|
| def test_stub_returns_none_or_pose_on_blank_frame(self): |
| analyzer = PoseAnalyzer() |
| frame = np.zeros((480, 640, 3), dtype=np.uint8) |
| result = analyzer.analyze(frame) |
| |
| self.assertTrue(result is None or hasattr(result, "is_fall")) |
|
|
|
|
| |
| |
| |
| class TestAudioMonitorStub(unittest.TestCase): |
|
|
| def test_stub_returns_empty_without_tensorflow(self): |
| monitor = AudioMonitor() |
| |
| samples = np.zeros(16000, dtype=np.float32) |
| result = monitor.classify(samples, sample_rate=16000) |
| |
| self.assertIsInstance(result, list) |
|
|
|
|
| |
| |
| |
| class TestSafetyKnowledgeBase(unittest.TestCase): |
|
|
| def setUp(self): |
| self.kb = SafetyKnowledgeBase("dummy-key") |
|
|
| |
| def test_has_8_documents(self): |
| self.assertEqual(len(self.kb.documents), 8) |
|
|
| |
| def test_emergency_fire(self): |
| self.assertTrue(self.kb._check_is_emergency("fire in the kitchen")) |
|
|
| def test_emergency_fall(self): |
| self.assertTrue(self.kb._check_is_emergency("elderly person fall")) |
|
|
| def test_emergency_knife(self): |
| self.assertTrue(self.kb._check_is_emergency("knife detected")) |
|
|
| def test_emergency_gas(self): |
| self.assertTrue(self.kb._check_is_emergency("gas leak smell")) |
|
|
| def test_emergency_earthquake(self): |
| self.assertTrue(self.kb._check_is_emergency("earthquake shaking")) |
|
|
| def test_non_emergency_sunny_day(self): |
| self.assertFalse(self.kb._check_is_emergency("nice sunny day")) |
|
|
| def test_non_emergency_clear_path(self): |
| self.assertFalse(self.kb._check_is_emergency("clear path ahead")) |
|
|
| |
| def test_fallback_fire(self): |
| advice = self.kb.get_default_advice("fire") |
| self.assertIn("Fire detected", advice) |
|
|
| def test_fallback_fall(self): |
| advice = self.kb.get_default_advice("fall") |
| self.assertIn("Fall detected", advice) |
|
|
| def test_fallback_vehicle(self): |
| advice = self.kb.get_default_advice("car approaching") |
| self.assertIn("vehicle detected", advice) |
|
|
| def test_fallback_knife(self): |
| advice = self.kb.get_default_advice("knife visible") |
| self.assertIn("threat detected", advice) |
|
|
| def test_fallback_gas(self): |
| advice = self.kb.get_default_advice("gas smell") |
| self.assertIn("gas leak", advice) |
|
|
| def test_fallback_earthquake(self): |
| advice = self.kb.get_default_advice("quake") |
| self.assertIn("Earthquake", advice) |
|
|
| def test_fallback_choking(self): |
| advice = self.kb.get_default_advice("choking") |
| self.assertIn("Choking", advice) |
|
|
| def test_fallback_unknown(self): |
| advice = self.kb.get_default_advice("flying saucer detected") |
| self.assertIn("Anomaly detected", advice) |
|
|
| |
| def test_source_match_fire(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("evacuate via stairs away from smoke"), |
| "Fire Safety and Evacuation Procedures" |
| ) |
|
|
| def test_source_match_fall(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("do not move if fracture or unresponsive"), |
| "Fall Response and First Aid" |
| ) |
|
|
| def test_source_match_traffic(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("wait at the curb, listen for traffic"), |
| "Road Crossing and Traffic Safety" |
| ) |
|
|
| def test_source_match_intruder(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("move to locked room and call police"), |
| "Intruder Alert and Personal Security" |
| ) |
|
|
| def test_source_match_stroke(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("recognize signs of stroke quickly"), |
| "Medical Emergency and Stroke Recognition" |
| ) |
|
|
| def test_source_match_gas(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("rotten egg smell gas leak"), |
| "Gas Leak Protocols" |
| ) |
|
|
| def test_source_match_earthquake(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("earthquake shaking, under sturdy table"), |
| "Earthquake Safety (Drop, Cover, Hold On)" |
| ) |
|
|
| def test_source_match_choking(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("choking thrust above navel"), |
| "Choking First Aid (Heimlich Maneuver)" |
| ) |
|
|
| def test_source_match_unknown(self): |
| self.assertEqual( |
| self.kb._match_source_by_keyword("banana peel on floor"), |
| "General Safety Knowledge Base" |
| ) |
|
|
| |
| def test_cohere_exception_falls_back(self): |
| self.kb.client = MagicMock() |
| self.kb.client.chat.side_effect = Exception("Cohere API unavailable") |
| res = self.kb.query("fire", "There is smoke in the hallway") |
| self.assertIn("Fire detected", res["advice"]) |
| self.assertEqual(res["source"], "Fire Safety and Evacuation Procedures") |
| self.assertTrue(res["is_emergency"]) |
|
|
| def test_cohere_exception_fall_fallback(self): |
| self.kb.client = MagicMock() |
| self.kb.client.chat.side_effect = Exception("timeout") |
| res = self.kb.query("fall", "Person lying on floor") |
| self.assertIn("Fall detected", res["advice"]) |
|
|
| def test_cohere_exception_unknown_fallback(self): |
| self.kb.client = MagicMock() |
| self.kb.client.chat.side_effect = Exception("timeout") |
| res = self.kb.query("random event", "Something unusual") |
| self.assertIn("Anomaly detected", res["advice"]) |
| self.assertFalse(res["is_emergency"]) |
|
|
|
|
| |
| |
| |
| class TestFallbackRouter(unittest.IsolatedAsyncioTestCase): |
|
|
| async def asyncSetUp(self): |
| self.router = FallbackRouter( |
| openrouter_key="or-test-key", |
| openbmb_key="ob-test-key", |
| openai_key="oa-test-key" |
| ) |
| self.router.client = AsyncMock() |
|
|
| async def asyncTearDown(self): |
| await self.router.close() |
|
|
| |
| async def test_openrouter_uses_nemotron_30b_a3b_model(self): |
| """CRITICAL: Verifies the OpenRouter payload uses the correct new model slug.""" |
| mock_resp = MagicMock() |
| mock_resp.raise_for_status = MagicMock() |
| mock_resp.json.return_value = { |
| "choices": [{"message": {"content": "Safety response"}}], |
| "usage": {"total_tokens": 120} |
| } |
| self.router.client.post.return_value = mock_resp |
|
|
| res = await self.router.fallback_reason("Is there fire?", "You are Sentinel.") |
| |
| self.assertEqual(res["model"], "openrouter:nemotron-3-nano-30b-a3b:free") |
|
|
| |
| call_kwargs = self.router.client.post.call_args |
| payload = call_kwargs.kwargs.get("json") or call_kwargs.args[1] if len(call_kwargs.args) > 1 else call_kwargs.kwargs["json"] |
| self.assertEqual(payload["model"], "nvidia/nemotron-3-nano-30b-a3b:free") |
|
|
| async def test_openrouter_headers_include_referer(self): |
| """Verifies that HTTP-Referer and X-Title headers are present for OpenRouter compliance.""" |
| mock_resp = MagicMock() |
| mock_resp.raise_for_status = MagicMock() |
| mock_resp.json.return_value = { |
| "choices": [{"message": {"content": "ok"}}], |
| "usage": {"total_tokens": 10} |
| } |
| self.router.client.post.return_value = mock_resp |
| await self.router.fallback_reason("test", "") |
| call_kwargs = self.router.client.post.call_args |
| headers = call_kwargs.kwargs.get("headers", {}) |
| self.assertIn("HTTP-Referer", headers) |
| self.assertIn("X-Title", headers) |
|
|
| async def test_openrouter_success_returns_text(self): |
| mock_resp = MagicMock() |
| mock_resp.raise_for_status = MagicMock() |
| mock_resp.json.return_value = { |
| "choices": [{"message": {"content": "Stairs ahead, stop immediately."}}], |
| "usage": {"total_tokens": 90} |
| } |
| self.router.client.post.return_value = mock_resp |
| res = await self.router.fallback_reason("Describe scene", "You are Sentinel.") |
| self.assertEqual(res["text"], "Stairs ahead, stop immediately.") |
| self.assertEqual(res["tokens"], 90) |
|
|
| async def test_openrouter_fail_openai_succeeds(self): |
| fail_resp = MagicMock() |
| fail_resp.raise_for_status.side_effect = Exception("OpenRouter 500") |
| ok_resp = MagicMock() |
| ok_resp.raise_for_status = MagicMock() |
| ok_resp.json.return_value = { |
| "choices": [{"message": {"content": "OpenAI fallback response"}}], |
| "usage": {"total_tokens": 75} |
| } |
| self.router.client.post.side_effect = [fail_resp, ok_resp] |
| res = await self.router.fallback_reason("What is ahead?", "") |
| self.assertEqual(res["text"], "OpenAI fallback response") |
| self.assertEqual(res["model"], "openai:gpt-4o-mini") |
|
|
| async def test_all_reasoning_providers_fail_returns_error(self): |
| fail_resp = MagicMock() |
| fail_resp.raise_for_status.side_effect = Exception("all fail") |
| self.router.client.post.side_effect = [fail_resp, fail_resp] |
| res = await self.router.fallback_reason("test", "") |
| self.assertIn("error", res) |
| self.assertIn("having trouble", res["text"]) |
|
|
| async def test_fallback_see_openbmb_success(self): |
| mock_resp = MagicMock() |
| mock_resp.raise_for_status = MagicMock() |
| mock_resp.json.return_value = { |
| "choices": [{"message": {"content": "A person approaching from the left."}}], |
| "usage": {"total_tokens": 200} |
| } |
| self.router.client.post.return_value = mock_resp |
| res = await self.router.fallback_see("base64imagedata", "Describe the scene.") |
| self.assertEqual(res["text"], "A person approaching from the left.") |
| self.assertEqual(res["model"], "openbmb:minicpm-v-4.6") |
|
|
| async def test_fallback_see_openbmb_fail_openai_succeeds(self): |
| fail = MagicMock() |
| fail.raise_for_status.side_effect = Exception("OpenBMB down") |
| ok = MagicMock() |
| ok.raise_for_status = MagicMock() |
| ok.json.return_value = { |
| "choices": [{"message": {"content": "Fire visible in scene."}}], |
| "usage": {"total_tokens": 160} |
| } |
| self.router.client.post.side_effect = [fail, ok] |
| res = await self.router.fallback_see("img", "What is in the scene?") |
| self.assertEqual(res["text"], "Fire visible in scene.") |
| self.assertEqual(res["model"], "openai:gpt-4o-mini-vision") |
|
|
| async def test_fallback_see_all_fail_returns_error(self): |
| fail = MagicMock() |
| fail.raise_for_status.side_effect = Exception("all fail") |
| self.router.client.post.side_effect = [fail, fail] |
| res = await self.router.fallback_see("img", "Describe.") |
| self.assertIn("error", res) |
|
|
| async def test_fallback_tts_success(self): |
| mock_resp = MagicMock() |
| mock_resp.raise_for_status = MagicMock() |
| mock_resp.content = b"fake_wav_bytes" |
| self.router.client.post.return_value = mock_resp |
| res = await self.router.fallback_tts("Warning: stairs ahead") |
| self.assertIn("audio_base64", res) |
| decoded = base64.b64decode(res["audio_base64"]) |
| self.assertEqual(decoded, b"fake_wav_bytes") |
| self.assertEqual(res["model"], "openai-tts-1") |
|
|
| async def test_fallback_tts_no_key_returns_error(self): |
| router_no_key = FallbackRouter(openrouter_key="", openbmb_key="", openai_key="") |
| res = await router_no_key.fallback_tts("test") |
| self.assertIn("error", res) |
| await router_no_key.close() |
|
|
| async def test_no_keys_returns_error_chain(self): |
| router_empty = FallbackRouter(openrouter_key="", openbmb_key="", openai_key="") |
| res = await router_empty.fallback_reason("test", "") |
| self.assertIn("error", res) |
| await router_empty.close() |
|
|
| async def test_rate_limit_429_triggers_backoff(self): |
| """Verify 429 responses result in asyncio.sleep (backoff) before next attempt.""" |
| import httpx |
| fail_resp = MagicMock(spec=httpx.Response) |
| fail_resp.status_code = 429 |
| fail_resp.raise_for_status.side_effect = httpx.HTTPStatusError( |
| "Rate limited", request=MagicMock(), response=fail_resp |
| ) |
| ok_resp = MagicMock() |
| ok_resp.raise_for_status = MagicMock() |
| ok_resp.json.return_value = { |
| "choices": [{"message": {"content": "ok"}}], |
| "usage": {"total_tokens": 5} |
| } |
| self.router.client.post.side_effect = [fail_resp, ok_resp] |
| |
| res = await self.router.fallback_reason("test", "") |
| |
| self.assertIn("text", res) |
|
|
|
|
| |
| |
| |
| class TestModalRAGLocalMethods(unittest.TestCase): |
| """ |
| Tests the _rag_local_fallback and _rag_match_source helper methods |
| that are defined inside SentinelEngine in modal_backend.py. |
| We replicate them here to test the logic without invoking Modal. |
| """ |
|
|
| def _local_fallback(self, situation: str) -> str: |
| s = situation.lower() |
| if any(k in s for k in ("fire", "smoke")): |
| return "Fire detected. Evacuate immediately via stairs, stay low to avoid smoke, and do not use elevators." |
| if any(k in s for k in ("fall", "collapse")): |
| return "Fall detected. Do not move if you feel neck or back pain. Call for medical assistance." |
| if any(k in s for k in ("car", "vehicle", "road", "traffic")): |
| return "Approaching vehicle detected. Step away from the curb and wait for traffic noise to pass completely." |
| if any(k in s for k in ("knife", "intruder", "weapon")): |
| return "Potential threat detected. Move to a secure, locked room, silence your phone, and call police." |
| if any(k in s for k in ("gas", "leak")): |
| return "Potential gas leak. Do not turn on light switches, leave the building immediately, and call emergency services." |
| if any(k in s for k in ("quake", "shaking", "earthquake")): |
| return "Earthquake active. Drop, cover, and hold on under a sturdy table. Stay away from windows." |
| if any(k in s for k in ("chok", "cough")): |
| return "Choking hazard. If you cannot breathe, perform abdominal thrusts or call emergency services immediately." |
| return "Anomaly detected. Move slowly to a stable position and evaluate your surroundings carefully." |
|
|
| def _match_source(self, text: str) -> str: |
| t = text.lower() |
| if any(k in t for k in ("fire", "evacuate", "smoke")): |
| return "Fire Safety and Evacuation Procedures" |
| if any(k in t for k in ("fall", "fracture", "unresponsive")): |
| return "Fall Response and First Aid" |
| if any(k in t for k in ("curb", "traffic", "crosswalk")): |
| return "Road Crossing and Traffic Safety" |
| if any(k in t for k in ("intruder", "locked room", "police")): |
| return "Intruder Alert and Personal Security" |
| if any(k in t for k in ("stroke", "fast")): |
| return "Medical Emergency and Stroke Recognition" |
| if any(k in t for k in ("gas leak", "rotten egg")): |
| return "Gas Leak Protocols" |
| if any(k in t for k in ("earthquake", "shaking", "sturdy table")): |
| return "Earthquake Safety (Drop, Cover, Hold On)" |
| if any(k in t for k in ("choking", "navel", "thrust")): |
| return "Choking First Aid (Heimlich Maneuver)" |
| return "General Safety Knowledge Base" |
|
|
| def test_modal_rag_fire_fallback(self): |
| self.assertIn("Fire detected", self._local_fallback("fire in room")) |
|
|
| def test_modal_rag_smoke_fallback(self): |
| self.assertIn("Fire detected", self._local_fallback("smoke everywhere")) |
|
|
| def test_modal_rag_fall_fallback(self): |
| self.assertIn("Fall detected", self._local_fallback("person fall")) |
|
|
| def test_modal_rag_collapse_fallback(self): |
| self.assertIn("Fall detected", self._local_fallback("collapse")) |
|
|
| def test_modal_rag_vehicle_fallback(self): |
| self.assertIn("vehicle detected", self._local_fallback("car approaching")) |
|
|
| def test_modal_rag_traffic_fallback(self): |
| self.assertIn("vehicle detected", self._local_fallback("heavy traffic road")) |
|
|
| def test_modal_rag_knife_fallback(self): |
| self.assertIn("threat detected", self._local_fallback("knife on table")) |
|
|
| def test_modal_rag_intruder_fallback(self): |
| self.assertIn("threat detected", self._local_fallback("intruder in house")) |
|
|
| def test_modal_rag_weapon_fallback(self): |
| self.assertIn("threat detected", self._local_fallback("weapon visible")) |
|
|
| def test_modal_rag_gas_fallback(self): |
| self.assertIn("gas leak", self._local_fallback("gas smell")) |
|
|
| def test_modal_rag_leak_fallback(self): |
| self.assertIn("gas leak", self._local_fallback("pipe leak")) |
|
|
| def test_modal_rag_earthquake_fallback(self): |
| self.assertIn("Earthquake", self._local_fallback("earthquake shaking")) |
|
|
| def test_modal_rag_quake_fallback(self): |
| self.assertIn("Earthquake", self._local_fallback("quake event")) |
|
|
| def test_modal_rag_choking_fallback(self): |
| self.assertIn("Choking", self._local_fallback("person choking")) |
|
|
| def test_modal_rag_cough_fallback(self): |
| self.assertIn("Choking", self._local_fallback("persistent cough")) |
|
|
| def test_modal_rag_unknown_fallback(self): |
| self.assertIn("Anomaly detected", self._local_fallback("unknown situation")) |
|
|
| def test_modal_source_fire(self): |
| self.assertEqual(self._match_source("evacuate via stairs avoid smoke"), |
| "Fire Safety and Evacuation Procedures") |
|
|
| def test_modal_source_fall(self): |
| self.assertEqual(self._match_source("do not move if fracture present"), |
| "Fall Response and First Aid") |
|
|
| def test_modal_source_traffic(self): |
| self.assertEqual(self._match_source("wait at the curb for traffic"), |
| "Road Crossing and Traffic Safety") |
|
|
| def test_modal_source_intruder(self): |
| self.assertEqual(self._match_source("move to locked room call police"), |
| "Intruder Alert and Personal Security") |
|
|
| def test_modal_source_stroke(self): |
| self.assertEqual(self._match_source("recognize stroke signs fast"), |
| "Medical Emergency and Stroke Recognition") |
|
|
| def test_modal_source_gas_leak(self): |
| self.assertEqual(self._match_source("gas leak rotten egg smell"), |
| "Gas Leak Protocols") |
|
|
| def test_modal_source_earthquake(self): |
| self.assertEqual(self._match_source("earthquake sturdy table shaking"), |
| "Earthquake Safety (Drop, Cover, Hold On)") |
|
|
| def test_modal_source_choking(self): |
| self.assertEqual(self._match_source("choking thrust above navel"), |
| "Choking First Aid (Heimlich Maneuver)") |
|
|
| def test_modal_source_unknown(self): |
| self.assertEqual(self._match_source("random text"), |
| "General Safety Knowledge Base") |
|
|
|
|
| |
| |
| |
| class TestStressIntegration(unittest.TestCase): |
| """ |
| Rapid-fire decision engine calls simulating a real monitoring session. |
| Verifies: no crashes, correct cooldown enforcement, signal variety handling. |
| """ |
|
|
| def setUp(self): |
| self.engine = GatekeeperDecision(cooldown_seconds=0.05) |
| self.tracker = CostTracker() |
|
|
| def test_1000_frames_no_crash(self): |
| for i in range(1000): |
| self.tracker.log_frame() |
| change_pct = 5.0 if (i % 10 == 0) else 0.5 |
| is_sig = change_pct > 5.0 |
| frame_change = FrameChangeResult(change_pct=change_pct, is_significant=is_sig, |
| threshold_used=5.0, motion_regions=int(is_sig)) |
| detections = [Detection("person", 0.85, (0,0,50,50), 6.0)] if (i % 10 == 0) else [] |
| audio = [AudioClass("Scream", 0.9, "critical")] if (i % 50 == 0) else [] |
| pose = None |
|
|
| d = self.engine.decide(frame_change, detections, audio, pose) |
| self.assertIsInstance(d, TriggerDecision) |
| self.assertLessEqual(d.confidence, 1.0) |
| self.assertGreaterEqual(d.confidence, 0.0) |
|
|
| |
| s = self.tracker.get_stats() |
| self.assertEqual(s["frames_processed"], 1000) |
|
|
| def test_all_scenarios_produce_valid_decisions(self): |
| fc_no_motion = FrameChangeResult(1.0, False, 5.0, 0) |
| fc_motion = FrameChangeResult(9.0, True, 5.0, 2) |
| scenarios = [ |
| dict(frame_change=fc_no_motion, detections=[], audio_classes=[], pose_data=None), |
| dict(frame_change=fc_motion, |
| detections=[Detection("person", 0.9, (0, 0, 50, 50), 8.0)], |
| audio_classes=[], pose_data=None), |
| dict(frame_change=fc_no_motion, detections=[], |
| audio_classes=[AudioClass("Scream", 0.95, "critical")], pose_data=None), |
| dict(frame_change=fc_no_motion, detections=[], audio_classes=[], |
| pose_data=PoseData(0.85, 0.55, 0.7, True, False, 0.9)), |
| dict(frame_change=fc_no_motion, detections=[], audio_classes=[], pose_data=None, |
| sensor_data=SensorSnapshot( |
| accelerometer=(20.0, 20.0, 20.0), |
| gyroscope=(4.0, 4.0, 4.0), |
| gps=(37.0, -122.0), |
| light_level=2.0, |
| battery_pct=80.0, |
| heading=90.0 |
| )), |
| ] |
| engine = GatekeeperDecision(cooldown_seconds=0.0) |
| for kwargs in scenarios: |
| d = engine.decide(**kwargs) |
| self.assertIsInstance(d, TriggerDecision) |
| self.assertIn(d.urgency, ("none", "info", "warning", "critical")) |
|
|
|
|
| if __name__ == "__main__": |
| unittest.main(verbosity=2) |
|
|