fix(server): honour the legacy thresholds: config; repair the stale test suite

Since the built-in defaults were seeded into threshold_configs, the legacy
top-level thresholds: format was silently ignored. get_thresholds_for_host
now uses it whenever it was configured.

Tests: point imports at hbd.server/hbd.common, rewrite the DNS worker,
proto, datagram and threshold tests for the current API, add pytest-asyncio
to the dev extras and set testpaths so a bare pytest runs cleanly.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-28 04:53:43 -04:00
co-authored by Claude Sonnet 5.5
parent 952adb68f8
commit 85c6a4eebe
8 changed files with 78 additions and 120 deletions
+37 -82
View File
@@ -1,9 +1,8 @@
import time
import queue
import asyncio
import unittest
from unittest.mock import patch, MagicMock
import hbd.dns as dns
import hbd.server.dns as dns
class TestDNS(unittest.TestCase):
@@ -17,7 +16,7 @@ class TestDNS(unittest.TestCase):
p = dns.create_nsupdate_payload("host", "2001:db8::1", "example")
self.assertIn("update add host.dy.example 5 AAAA 2001:db8::1", p)
@patch("hbd.dns.Popen")
@patch("hbd.server.dns.Popen")
def test_nsupdate_success(self, mock_popen):
proc = MagicMock()
proc.communicate.return_value = (b"status: NOERROR", None)
@@ -41,7 +40,7 @@ class TestDNS(unittest.TestCase):
stderr=dns.STDOUT,
)
@patch("hbd.dns.Popen")
@patch("hbd.server.dns.Popen")
def test_nsupdate_failure(self, mock_popen):
proc = MagicMock()
proc.communicate.return_value = (b"some error", None)
@@ -57,83 +56,7 @@ class TestDNS(unittest.TestCase):
self.assertIsNotNone(err)
self.assertIn("some error", err)
def test_dnsupdatethread_processes_queue(self):
# patch nsupdate to succeed
with patch("hbd.dns.nsupdate", return_value=None):
logs = []
def log(h, m):
logs.append((h, m))
emails = []
def email(s, m):
emails.append((s, m))
class FakeHost:
dnsQ = queue.Queue()
class FakeHbd:
Host = FakeHost
# start the thread (daemon) that processes the queue
t = dns.start_dns_thread(
FakeHbd, {"dyndomains": ["example"]}, log=log, email=email
)
self.assertTrue(t.is_alive())
# enqueue one item and wait for it to be processed (polling with timeout)
FakeHbd.Host.dnsQ.put(("testhost", "1.2.3.4"))
for _ in range(30):
if logs:
break
time.sleep(0.1)
self.assertTrue(logs, "dnsupdatethread did not call log")
self.assertTrue(
any("changed address" in m or "DNS updated" in m for (_h, m) in logs)
)
def test_dnsupdatethread_calls_email_on_failure(self):
# patch nsupdate to fail with an error message
with patch("hbd.dns.nsupdate", return_value="error: failed"):
logs = []
def log(h, m):
logs.append((h, m))
emails = []
def email(s, m):
emails.append((s, m))
class FakeHost:
dnsQ = queue.Queue()
class FakeHbd:
Host = FakeHost
dns.start_dns_thread(
FakeHbd, {"dyndomains": ["example"]}, log=log, email=email
)
# enqueue and wait for the email to be sent
FakeHbd.Host.dnsQ.put(("testhost", "1.2.3.4"))
for _ in range(30):
if emails:
break
time.sleep(0.1)
self.assertTrue(emails, "dnsupdatethread did not call email on failure")
self.assertTrue(
any(
"nsupdate failed" in s or "nsupdate failed" in m or "error" in m
for (s, m) in emails
)
)
@patch("hbd.dns.Popen")
@patch("hbd.server.dns.Popen")
def test_nsupdate_raises_oserror(self, mock_popen):
mock_popen.side_effect = OSError("noexec")
err = dns.nsupdate(
@@ -147,5 +70,37 @@ class TestDNS(unittest.TestCase):
self.assertIn("execution failed", err)
class TestDNSWorker(unittest.IsolatedAsyncioTestCase):
async def _run_worker(self, nsupdate_result):
"""Feed one update through dns_update_worker; returns the (name, level, message) log calls."""
logs = []
queue = asyncio.Queue()
await queue.put(("testhost", "1.2.3.4"))
await queue.put(None)
with patch("hbd.server.dns.nsupdate", return_value=nsupdate_result):
await dns.dns_update_worker(
None, {"dyndomains": ["example"]}, async_queue=queue,
log=lambda name, level, msg: logs.append((name, level, msg)),
)
return logs
async def test_worker_logs_successful_update(self):
logs = await self._run_worker(None)
self.assertEqual(len(logs), 1)
name, level, msg = logs[0]
self.assertEqual((name, level), ("testhost", "INFO"))
self.assertIn("DNS updated testhost.dy.example", msg)
self.assertIn("1.2.3.4", msg)
async def test_worker_logs_error_on_failed_update(self):
logs = await self._run_worker("error: failed")
self.assertEqual(len(logs), 1)
name, level, msg = logs[0]
self.assertEqual((name, level), ("testhost", "ERROR"))
self.assertIn("DNS update failed", msg)
self.assertIn("error: failed", msg)
if __name__ == "__main__":
unittest.main()