Revamp dhcp sync and add tests
This commit is contained in:
1 parent
0bc8daeabc
commit
a5c06bdce5
5 files changed
+385
-101
No files matched your search
@@ -0,0 +1,113 @@
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mdb.services.dhcp_sync import DhcpSyncService
|
||||
|
||||
|
||||
def make_config(serial=2, active_serial=1):
|
||||
config = MagicMock()
|
||||
config.serial = serial
|
||||
config.active_serial = active_serial
|
||||
config.dhcpd_configuration.return_value = "# dhcpd config\nsubnet 10.0.0.0 netmask 255.255.255.0 {}\n"
|
||||
return config
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def service():
|
||||
return DhcpSyncService()
|
||||
|
||||
|
||||
# --- needs_update ---
|
||||
|
||||
def test_needs_update_true_when_serials_differ(service):
|
||||
config = make_config(serial=2, active_serial=1)
|
||||
assert service.needs_update(config) is True
|
||||
|
||||
|
||||
def test_needs_update_false_when_serials_match(service):
|
||||
config = make_config(serial=1, active_serial=1)
|
||||
assert service.needs_update(config) is False
|
||||
|
||||
|
||||
def test_needs_update_true_when_force_even_if_serials_match():
|
||||
svc = DhcpSyncService(force=True)
|
||||
config = make_config(serial=1, active_serial=1)
|
||||
assert svc.needs_update(config) is True
|
||||
|
||||
|
||||
# --- calculate_diff ---
|
||||
|
||||
def test_calculate_diff_identical_returns_empty(service):
|
||||
config = make_config()
|
||||
assert service.calculate_diff(config, "line\n", "line\n") == ""
|
||||
|
||||
|
||||
def test_calculate_diff_format(service):
|
||||
config = make_config(serial=2, active_serial=1)
|
||||
diff = service.calculate_diff(config, "new line\n", "old line\n")
|
||||
assert "*** 1" in diff
|
||||
assert "--- 2" in diff
|
||||
assert "! old line" in diff
|
||||
assert "! new line" in diff
|
||||
|
||||
|
||||
# --- write_config ---
|
||||
|
||||
def test_write_config_creates_file_with_config_contents(service, tmp_path):
|
||||
config = make_config()
|
||||
output_path = str(tmp_path / "dhcpd.conf")
|
||||
|
||||
service.write_config(config, output_path)
|
||||
|
||||
with open(output_path) as f:
|
||||
assert f.read() == config.dhcpd_configuration.return_value
|
||||
|
||||
|
||||
def test_write_config_updates_active_serial(service, tmp_path):
|
||||
config = make_config(serial=5, active_serial=3)
|
||||
output_path = str(tmp_path / "dhcpd.conf")
|
||||
|
||||
service.write_config(config, output_path)
|
||||
|
||||
assert config.active_serial == 5
|
||||
config.save.assert_called_once()
|
||||
|
||||
|
||||
def test_write_config_returns_old_serial(service, tmp_path):
|
||||
config = make_config(serial=2, active_serial=1)
|
||||
output_path = str(tmp_path / "dhcpd.conf")
|
||||
|
||||
old_serial, diff = service.write_config(config, output_path)
|
||||
|
||||
assert old_serial == 1
|
||||
|
||||
|
||||
def test_write_config_diff_reflects_changes(service, tmp_path):
|
||||
output_path = str(tmp_path / "dhcpd.conf")
|
||||
with open(output_path, "w") as f:
|
||||
f.write("old content\n")
|
||||
|
||||
config = make_config(serial=2, active_serial=1)
|
||||
config.dhcpd_configuration.return_value = "new content\n"
|
||||
|
||||
old_serial, diff = service.write_config(config, output_path)
|
||||
|
||||
assert "old content" in diff
|
||||
assert "new content" in diff
|
||||
|
||||
|
||||
# --- restart_service ---
|
||||
|
||||
def test_restart_service_returns_true_on_success(service):
|
||||
with patch("mdb.services.dhcp_sync.getstatusoutput", return_value=(0, "")):
|
||||
success, output = service.restart_service()
|
||||
assert success is True
|
||||
assert output == ""
|
||||
|
||||
|
||||
def test_restart_service_returns_false_on_failure(service):
|
||||
with patch("mdb.services.dhcp_sync.getstatusoutput", return_value=(1, "Job failed")):
|
||||
success, output = service.restart_service()
|
||||
assert success is False
|
||||
assert output == "Job failed"
|
||||
@@ -0,0 +1,131 @@
|
||||
from io import StringIO
|
||||
from unittest.mock import MagicMock, patch
|
||||
|
||||
import pytest
|
||||
|
||||
from mdb.management.commands.dhcp_sync import Command as CommandClass
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def cmd():
|
||||
c = CommandClass()
|
||||
c.stdout = StringIO()
|
||||
c.stderr = StringIO()
|
||||
return c
|
||||
|
||||
|
||||
def make_service(serial=2, active_serial=1, restart_success=True, restart_output=""):
|
||||
svc = MagicMock()
|
||||
svc.force = False
|
||||
svc.restart_command = "/usr/sbin/service isc-dhcp-server restart"
|
||||
config = MagicMock()
|
||||
config.serial = serial
|
||||
config.active_serial = active_serial
|
||||
config.dhcpd_configuration.return_value = "# dhcpd config\n"
|
||||
svc.get_config.return_value = config
|
||||
svc.needs_update.return_value = serial != active_serial
|
||||
svc.write_config.return_value = (active_serial, "- old\n+ new")
|
||||
svc.restart_service.return_value = (restart_success, restart_output)
|
||||
return svc, config
|
||||
|
||||
|
||||
# --- handle: nothing to do ---
|
||||
|
||||
def test_handle_prints_nothing_to_do_when_no_update_needed(cmd):
|
||||
svc, config = make_service(serial=1, active_serial=1)
|
||||
svc.needs_update.return_value = False
|
||||
with patch("mdb.management.commands.dhcp_sync.DhcpSyncService", return_value=svc):
|
||||
cmd.handle(output=None, force=False, restart=False)
|
||||
assert "Nothing to do" in cmd.stdout.getvalue()
|
||||
|
||||
|
||||
# --- handle: stdout mode ---
|
||||
|
||||
def test_handle_prints_config_to_stdout_when_no_output_path(cmd):
|
||||
svc, config = make_service()
|
||||
with patch("mdb.management.commands.dhcp_sync.DhcpSyncService", return_value=svc):
|
||||
cmd.handle(output=None, force=False, restart=False)
|
||||
assert "# dhcpd config" in cmd.stdout.getvalue()
|
||||
assert "Not writing to file" in cmd.stdout.getvalue()
|
||||
|
||||
|
||||
def test_handle_does_not_write_file_when_no_output_path(cmd):
|
||||
svc, config = make_service()
|
||||
with patch("mdb.management.commands.dhcp_sync.DhcpSyncService", return_value=svc):
|
||||
cmd.handle(output=None, force=False, restart=False)
|
||||
svc.write_config.assert_not_called()
|
||||
|
||||
|
||||
# --- handle: file mode ---
|
||||
|
||||
def test_handle_calls_write_config_when_output_given(cmd, tmp_path):
|
||||
svc, config = make_service()
|
||||
output_path = str(tmp_path / "dhcpd.conf")
|
||||
with patch("mdb.management.commands.dhcp_sync.DhcpSyncService", return_value=svc):
|
||||
cmd.handle(output=output_path, force=False, restart=False)
|
||||
svc.write_config.assert_called_once_with(config, output_path)
|
||||
|
||||
|
||||
def test_handle_does_not_restart_when_restart_false(cmd, tmp_path):
|
||||
svc, config = make_service()
|
||||
output_path = str(tmp_path / "dhcpd.conf")
|
||||
with patch("mdb.management.commands.dhcp_sync.DhcpSyncService", return_value=svc):
|
||||
with patch.object(cmd, "restart_and_notify") as mock_restart:
|
||||
cmd.handle(output=output_path, force=False, restart=False)
|
||||
mock_restart.assert_not_called()
|
||||
|
||||
|
||||
def test_handle_calls_restart_and_notify_when_restart_true(cmd, tmp_path):
|
||||
svc, config = make_service()
|
||||
output_path = str(tmp_path / "dhcpd.conf")
|
||||
with patch("mdb.management.commands.dhcp_sync.DhcpSyncService", return_value=svc):
|
||||
with patch.object(cmd, "restart_and_notify") as mock_restart:
|
||||
cmd.handle(output=output_path, force=False, restart=True)
|
||||
mock_restart.assert_called_once()
|
||||
|
||||
|
||||
# --- restart_and_notify ---
|
||||
|
||||
def test_restart_and_notify_success_writes_ok(cmd):
|
||||
svc, config = make_service(restart_success=True)
|
||||
with patch("mdb.management.commands.dhcp_sync.discord"):
|
||||
cmd.restart_and_notify(svc, config, old_serial=1, diff="- old\n+ new")
|
||||
assert "ok" in cmd.stdout.getvalue()
|
||||
|
||||
|
||||
def test_restart_and_notify_success_calls_discord(cmd):
|
||||
svc, config = make_service(restart_success=True)
|
||||
with patch("mdb.management.commands.dhcp_sync.discord") as mock_discord:
|
||||
cmd.restart_and_notify(svc, config, old_serial=1, diff="- old\n+ new")
|
||||
mock_discord.notify.assert_called_once()
|
||||
assert "✅" in mock_discord.notify.call_args.kwargs["subject"]
|
||||
|
||||
|
||||
def test_restart_and_notify_success_passes_diff_as_preformatted(cmd):
|
||||
svc, config = make_service(restart_success=True)
|
||||
with patch("mdb.management.commands.dhcp_sync.discord") as mock_discord:
|
||||
cmd.restart_and_notify(svc, config, old_serial=1, diff="- old\n+ new")
|
||||
assert mock_discord.notify.call_args.kwargs["preformatted"] == "- old\n+ new"
|
||||
|
||||
|
||||
def test_restart_and_notify_failure_writes_fail_and_exits(cmd):
|
||||
svc, config = make_service(restart_success=False, restart_output="Job failed")
|
||||
with patch("mdb.management.commands.dhcp_sync.discord"):
|
||||
with patch("os.getlogin", return_value="testuser"):
|
||||
with pytest.raises(SystemExit) as exc:
|
||||
cmd.restart_and_notify(svc, config, old_serial=1, diff="")
|
||||
assert exc.value.code == 1
|
||||
assert "fail" in cmd.stdout.getvalue()
|
||||
|
||||
|
||||
def test_restart_and_notify_failure_discord_includes_command_and_output(cmd):
|
||||
svc, config = make_service(restart_success=False, restart_output="Job failed")
|
||||
with patch("mdb.management.commands.dhcp_sync.discord") as mock_discord:
|
||||
with patch("os.getlogin", return_value="testuser"):
|
||||
with pytest.raises(SystemExit):
|
||||
cmd.restart_and_notify(svc, config, old_serial=1, diff="")
|
||||
subject = mock_discord.notify.call_args.kwargs["subject"]
|
||||
message = mock_discord.notify.call_args.kwargs["message"]
|
||||
assert "⚠️" in subject
|
||||
assert "isc-dhcp-server" in message
|
||||
assert "Job failed" in message
|
||||
Reference in new issue
Block a user