import unittest from pathlib import Path from tempfile import TemporaryDirectory from unittest.mock import patch from lib._zyxel import ZyxelRouter, 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") def test_parses_ex5601_nested_lanhosts_response(self): router = ZyxelRouter("192.0.2.1", "user", "password") router.dal_get = lambda _oid: { "result": "ZCFG_SUCCESS", "Object": [ { "lanhosts": [ { "PhysAddress": "2C:DB:07:50:2B:5C", "Active": True, "HostName": "workstation", "IPAddress": "192.168.178.10", } ] } ], } hosts = router.get_lan_hosts() self.assertEqual(len(hosts), 1) self.assertEqual(hosts[0].mac, "2c:db:07:50:2b:5c") self.assertTrue(router.is_connected("2c-db-07-50-2b-5c")) 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, ) @patch("lib._arrival_detection.send_notification") def test_appends_notification_to_event_log(self, send_notification): with TemporaryDirectory() as directory: event_log = Path(directory) / "arrivals.txt" config = { "notification": { "enabled": True, "message": "{name} arrived home", "event_log": str(event_log), } } _notify(config, {"name": "Ignace", "mac": "aa:bb:cc:dd:ee:ff"}) self.assertTrue(event_log.read_text().endswith(" Ignace arrived home\n")) if __name__ == "__main__": unittest.main()