import unittest from unittest.mock import patch from lib._zyxel import normalize_mac from lib._arrival_detection import ArrivalDetector, _notify class ArrivalDetectorTests(unittest.TestCase): def test_starting_present_does_not_emit_arrival(self): detector = ArrivalDetector(absent_after=2, present_after=2) self.assertFalse(detector.sample(True)) self.assertFalse(detector.sample(True)) def test_emits_only_after_absence_and_debounced_presence(self): detector = ArrivalDetector(absent_after=2, present_after=2) for value in (False, False, True): self.assertFalse(detector.sample(value)) self.assertTrue(detector.sample(True)) self.assertFalse(detector.sample(True)) def test_single_missed_poll_does_not_mark_absent(self): detector = ArrivalDetector(absent_after=2, present_after=1) detector.sample(True) self.assertFalse(detector.sample(False)) self.assertFalse(detector.sample(True)) class MacTests(unittest.TestCase): def test_normalizes_common_formats(self): self.assertEqual(normalize_mac("AA-BB-CC-DD-EE-FF"), "aa:bb:cc:dd:ee:ff") def test_rejects_invalid_mac(self): with self.assertRaises(ValueError): normalize_mac("not-a-mac") class NotificationTests(unittest.TestCase): @patch("lib._arrival_detection.send_notification") def test_uses_notify_module(self, send_notification): config = { "notification": { "enabled": True, "message": "Welcome home, {name} ({mac})", "topic_url": "https://ntfy.example/home", "timeout": 4, } } _notify(config, {"name": "Ignace", "mac": "aa:bb:cc:dd:ee:ff"}) send_notification.assert_called_once_with( "Welcome home, Ignace (aa:bb:cc:dd:ee:ff)", topic_url="https://ntfy.example/home", timeout=4.0, ) if __name__ == "__main__": unittest.main()