From a2fcc7321d51a84c6cb249d41b1f0ef4d86a993d Mon Sep 17 00:00:00 2001 From: Jonas Braathen Date: Thu, 19 Mar 2026 01:34:45 +0100 Subject: [PATCH] Send separate zone update diffs on succses --- mdb/management/commands/zone_sync.py | 36 ++++++++++++++-------------- tests/test_zone_sync_command.py | 32 ++++++++++++++++--------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/mdb/management/commands/zone_sync.py b/mdb/management/commands/zone_sync.py index ab7b995..5ad1ae5 100644 --- a/mdb/management/commands/zone_sync.py +++ b/mdb/management/commands/zone_sync.py @@ -55,26 +55,25 @@ class Command(BaseCommand): service.sync_all_zones() if service.changes and not debug: - errors = "\n\n\n".join( - [v["errors"] for v in service.changes.values() if v["errors"]] - ) - diffs = "\n\n\n".join( - [v["diff"] for v in service.changes.values() if v["diff"]] - ) - zones = list(service.changes.keys()) - self.reload_and_notify(service, errors, diffs, zones) + self.reload_and_notify(service, service.changes) - def reload_and_notify(self, service, errors, diffs, zones): + def reload_and_notify(self, service, changes): self.stdout.write("reloading zones... ") success, failed_command, output = service.run_reload_commands() + zones = list(changes.keys()) zone_list = "\n".join("- " + z for z in zones) if not success: self.stdout.write("fail\n") + errors = "\n\n\n".join( + [v["errors"] for v in changes.values() if v["errors"]] + ) + diffs = "\n\n\n".join([v["diff"] for v in changes.values() if v["diff"]]) message = ( "Failed to reload zones, please inspect...\n\n" - "%s@%s$ %s\n%s\n%s" % (os.getlogin(), os.uname()[1], failed_command, output, zone_list) + "%s@%s$ %s\n%s\n%s" + % (os.getlogin(), os.uname()[1], failed_command, output, zone_list) ) if errors: message += "\n\n" + errors @@ -86,11 +85,12 @@ class Command(BaseCommand): raise SystemExit(1) self.stdout.write("ok\n") - message = "Successfully updated bind zone files for\n" + zone_list - if errors: - message += "\n\n" + errors - discord.notify( - subject="✅ Success: bind", - message=message, - preformatted=diffs or None, - ) + for zone, info in changes.items(): + message = "Successfully updated bind zone file for `%s`" % zone + if info["errors"]: + message += "\n\n" + info["errors"] + discord.notify( + subject="✅ Success: bind", + message=message, + preformatted=info["diff"] or None, + ) diff --git a/tests/test_zone_sync_command.py b/tests/test_zone_sync_command.py index e7adda8..5cf3318 100644 --- a/tests/test_zone_sync_command.py +++ b/tests/test_zone_sync_command.py @@ -24,6 +24,7 @@ def make_service(changes=None): # --- run_reload_commands --- + def test_run_reload_commands_calls_all_on_success(service): with patch("mdb.services.zone_sync.getstatusoutput", return_value=(0, "")) as mock: success, _, _ = service.run_reload_commands() @@ -45,26 +46,34 @@ def test_run_reload_commands_stops_on_first_failure(service): # --- reload_and_notify --- + def test_reload_and_notify_success_writes_ok(cmd): svc = make_service() - with patch("mdb.management.commands.zone_sync.discord") as mock_discord: - cmd.reload_and_notify(svc, errors="", diffs="diff", zones=["example.org"]) + with patch("mdb.management.commands.zone_sync.discord"): + cmd.reload_and_notify(svc, svc.changes) assert "ok" in cmd.stdout.getvalue() -def test_reload_and_notify_success_calls_discord(cmd): - svc = make_service() +def test_reload_and_notify_success_calls_discord_once_per_zone(cmd): + changes = { + "example.org": {"errors": None, "diff": "- old\n+ new"}, + "other.net": {"errors": None, "diff": "- x\n+ y"}, + } + svc = make_service(changes=changes) with patch("mdb.management.commands.zone_sync.discord") as mock_discord: - cmd.reload_and_notify(svc, errors="", diffs="diff", zones=["example.org"]) - mock_discord.notify.assert_called_once() - assert "✅" in mock_discord.notify.call_args.kwargs["subject"] - assert "- example.org" in mock_discord.notify.call_args.kwargs["message"] + cmd.reload_and_notify(svc, svc.changes) + assert mock_discord.notify.call_count == 2 + subjects = [c.kwargs["subject"] for c in mock_discord.notify.call_args_list] + messages = [c.kwargs["message"] for c in mock_discord.notify.call_args_list] + assert all("✅" in s for s in subjects) + assert any("`example.org`" in m for m in messages) + assert any("`other.net`" in m for m in messages) def test_reload_and_notify_success_passes_diff_as_preformatted(cmd): svc = make_service() with patch("mdb.management.commands.zone_sync.discord") as mock_discord: - cmd.reload_and_notify(svc, errors="", diffs="- old\n+ new", zones=["example.org"]) + cmd.reload_and_notify(svc, svc.changes) assert mock_discord.notify.call_args.kwargs["preformatted"] == "- old\n+ new" @@ -74,7 +83,7 @@ def test_reload_and_notify_failure_writes_fail_and_exits(cmd): with patch("mdb.management.commands.zone_sync.discord"): with patch("os.getlogin", return_value="testuser"): with pytest.raises(SystemExit) as exc: - cmd.reload_and_notify(svc, errors="", diffs="", zones=["example.org"]) + cmd.reload_and_notify(svc, svc.changes) assert exc.value.code == 1 assert "fail" in cmd.stdout.getvalue() @@ -85,7 +94,7 @@ def test_reload_and_notify_failure_discord_includes_command_and_output(cmd): with patch("mdb.management.commands.zone_sync.discord") as mock_discord: with patch("os.getlogin", return_value="testuser"): with pytest.raises(SystemExit): - cmd.reload_and_notify(svc, errors="", diffs="", zones=["example.org"]) + cmd.reload_and_notify(svc, svc.changes) subject = mock_discord.notify.call_args.kwargs["subject"] message = mock_discord.notify.call_args.kwargs["message"] assert "⚠️" in subject @@ -96,6 +105,7 @@ def test_reload_and_notify_failure_discord_includes_command_and_output(cmd): # --- handle --- + @pytest.mark.django_db def test_handle_exits_if_zone_not_found(cmd): with pytest.raises(SystemExit) as exc: