refactor
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import time
|
||||
import queue
|
||||
import unittest
|
||||
from unittest.mock import patch, MagicMock
|
||||
|
||||
import hbd.dns as dns
|
||||
|
||||
|
||||
class TestDNS(unittest.TestCase):
|
||||
|
||||
def test_create_nsupdate_payload_ipv4(self):
|
||||
p = dns.create_nsupdate_payload("host", "1.2.3.4", "example")
|
||||
self.assertIn("update add host.dy.example 5 A 1.2.3.4", p)
|
||||
self.assertNotIn("AAAA", p)
|
||||
|
||||
def test_create_nsupdate_payload_ipv6(self):
|
||||
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")
|
||||
def test_nsupdate_success(self, mock_popen):
|
||||
proc = MagicMock()
|
||||
proc.communicate.return_value = (b"status: NOERROR", None)
|
||||
mock_popen.return_value = proc
|
||||
|
||||
err = dns.nsupdate(
|
||||
"host",
|
||||
"1.2.3.4",
|
||||
"example",
|
||||
nsupdate_bin="/usr/bin/nsupdate",
|
||||
rndc_key="/etc/rndc.key",
|
||||
)
|
||||
|
||||
self.assertIsNone(err)
|
||||
mock_popen.assert_called_once_with(
|
||||
["/usr/bin/nsupdate", "-k", "/etc/rndc.key", "-v"],
|
||||
shell=False,
|
||||
bufsize=0,
|
||||
stdin=dns.PIPE,
|
||||
stdout=dns.PIPE,
|
||||
stderr=dns.STDOUT,
|
||||
)
|
||||
|
||||
@patch("hbd.dns.Popen")
|
||||
def test_nsupdate_failure(self, mock_popen):
|
||||
proc = MagicMock()
|
||||
proc.communicate.return_value = (b"some error", None)
|
||||
mock_popen.return_value = proc
|
||||
|
||||
err = dns.nsupdate("host", "1.2.3.4", "example", nsupdate_bin="/usr/bin/nsupdate", rndc_key="/etc/rndc.key")
|
||||
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
|
||||
|
||||
t = 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")
|
||||
def test_nsupdate_raises_oserror(self, mock_popen):
|
||||
mock_popen.side_effect = OSError("noexec")
|
||||
err = dns.nsupdate("h", "1.2.3.4", "example", nsupdate_bin="/usr/bin/nsupdate", rndc_key="/etc/rndc.key")
|
||||
self.assertIsNotNone(err)
|
||||
self.assertIn("execution failed", err)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
@@ -0,0 +1,47 @@
|
||||
from hbd.udp import handle_datagram, parse_message
|
||||
from hbd.proto import dicttos
|
||||
|
||||
class FakeTransport:
|
||||
def __init__(self):
|
||||
self.sent = []
|
||||
def sendto(self, data, addr):
|
||||
self.sent.append((data, addr))
|
||||
|
||||
|
||||
def dummy_noop(*a, **k):
|
||||
pass
|
||||
|
||||
|
||||
def test_handle_cmd_sends_command():
|
||||
ftr = FakeTransport()
|
||||
# prepare ctx linking to hbdclass
|
||||
import hbdclass
|
||||
|
||||
ctx = {
|
||||
'config': {'watchhosts':[], 'dyndnshosts':[]},
|
||||
'hbdclass': hbdclass,
|
||||
'log': dummy_noop,
|
||||
'email': dummy_noop,
|
||||
'pushmsg': dummy_noop,
|
||||
'msg_to_websockets': dummy_noop,
|
||||
'msgs': [],
|
||||
'DEBUG': 0,
|
||||
'verbose': False,
|
||||
}
|
||||
|
||||
# create host by sending initial heartbeat
|
||||
msg = parse_message(dicttos('HTB', {'name':'cmdhost','interval':10}))
|
||||
handle_datagram(msg, ('127.0.0.1',50000), ftr, ctx)
|
||||
assert ftr.sent[0][0] == b'ACK'
|
||||
|
||||
# queue a CMD for the host and send another heartbeat; expect command sent
|
||||
h = hbdclass.Host.hosts['cmdhost']
|
||||
h.cmds.append(('CMD', {'cmd': 'doit'}))
|
||||
ftr.sent.clear()
|
||||
msg2 = parse_message(dicttos('HTB', {'name':'cmdhost','interval':10}))
|
||||
handle_datagram(msg2, ('127.0.0.1',50000), ftr, ctx)
|
||||
# should have sent ACK and the command; last send should be non-empty
|
||||
assert len(ftr.sent) >= 1
|
||||
# the command for cver 0 will be sent as raw cmd string
|
||||
# so at least one send contains b'doit' or similar
|
||||
assert any(b'doit' in s[0] for s in ftr.sent)
|
||||
@@ -0,0 +1,25 @@
|
||||
import pytest
|
||||
from hbd.proto import dicttos, stodict, oldmtodict
|
||||
|
||||
|
||||
def test_dicttos_and_stodict_uncompressed():
|
||||
msg = dicttos("HTB", {"name": "host.example", "interval": 10})
|
||||
d = stodict(msg)
|
||||
assert d["ID"].startswith("HTB")
|
||||
assert d["name"] == "host.example"
|
||||
assert d["interval"] == 10
|
||||
|
||||
|
||||
def test_dicttos_and_stodict_compressed():
|
||||
msg = dicttos("ACK", {"time": 12345}, compress=True)
|
||||
d = stodict(msg)
|
||||
# for compressed the original code included the colon in ID slice; ensure no crash
|
||||
assert "ID" in d
|
||||
|
||||
|
||||
def test_oldmtodict():
|
||||
msg = b"name=foo;interval=5"
|
||||
d = oldmtodict(msg)
|
||||
assert d["ID"].startswith("HTB")
|
||||
assert d["name"] == "foo"
|
||||
assert d["interval"] == 5
|
||||
@@ -0,0 +1,14 @@
|
||||
from hbd.udp import parse_message
|
||||
from hbd.proto import dicttos
|
||||
|
||||
|
||||
def test_parse_message_uncompressed():
|
||||
raw = dicttos('HTB', {'name': 'host', 'interval': 1})
|
||||
m = parse_message(raw)
|
||||
assert m['ID'].startswith('HTB')
|
||||
|
||||
|
||||
def test_parse_message_compressed():
|
||||
raw = dicttos('ACK', {'time': 1}, compress=True)
|
||||
m = parse_message(raw)
|
||||
assert 'ID' in m
|
||||
Reference in New Issue
Block a user