"""Tests for signal handlers: zone_pre_save, zone_post_save, zone_post_delete. Uses ``captureOnCommitCallbacks(execute=True)`` to fire ``transaction.on_commit`` callbacks inside the transaction-wrapped ``@pytest.mark.django_db`` test scope. """ from unittest.mock import patch, MagicMock import pytest from django.test.testcases import TestCase as _DjangoTestCase # captureOnCommitCallbacks is a classmethod on TestCase in Django 6.x captureOnCommitCallbacks = _DjangoTestCase.captureOnCommitCallbacks from netbox_dns_bridge.models import ( CatalogZone, CatalogZoneMemberIdentifier, ) @pytest.mark.django_db class TestZonePreSave: def test_stores_old_name_and_serial_on_existing_zone(self, make_zone): zone = make_zone(name="presave.test", soa_serial=100) zone.refresh_from_db() zone.soa_serial = 200 zone.save() assert getattr(zone, "_old_name", None) == "presave.test" assert getattr(zone, "_old_soa_serial", None) == 100 def test_old_attrs_none_on_new_zone(self, make_zone): zone = make_zone(name="newzone.test") assert getattr(zone, "_old_name", None) is None assert getattr(zone, "_old_soa_serial", None) is None @pytest.mark.django_db class TestZonePostSaveCreate: def test_creates_catz_member_identifier_on_new_zone(self, make_zone): zone = make_zone(name="postcreate.test") assert CatalogZoneMemberIdentifier.objects.filter(zone=zone).exists() def test_increments_soa_serial_on_commit(self, make_zone, catz_serial): with captureOnCommitCallbacks(execute=True): zone = make_zone(name="serial-inc.test") obj = CatalogZone.objects.get(view=zone.view) assert obj.soa_serial == catz_serial + 1 def test_does_not_increment_without_on_commit(self, make_zone, catz_serial): zone = make_zone(name="no-commit.test") obj = CatalogZone.objects.get(view=zone.view) assert obj.soa_serial == catz_serial def test_schedule_client_notify_called_when_enabled(self, make_zone, patch_settings): patch_settings({"notify_clients": True}) with patch( "netbox_dns_bridge.signals.netbox_dns_zones.notify_handler.schedule_client_notify" ) as mock_notify: with patch( "netbox_dns_bridge.signals.netbox_dns_zones.notify_handler.schedule_ns_notify" ): with captureOnCommitCallbacks(execute=True): make_zone(name="notify.test") mock_notify.assert_called_once() def test_schedule_ns_notify_called_when_serial_changed(self, make_zone, patch_settings): patch_settings({ "notify_clients": False, "notify_ns_all_zones": True, }) with patch( "netbox_dns_bridge.signals.netbox_dns_zones.notify_handler.schedule_ns_notify" ) as mock_ns_notify: with captureOnCommitCallbacks(execute=True): make_zone(name="nsnotify.test") mock_ns_notify.assert_called_once() def test_schedule_ns_notify_not_called_when_disabled(self, make_zone, patch_settings): patch_settings({ "notify_clients": False, "notify_ns_all_zones": False, }) with patch( "netbox_dns_bridge.signals.netbox_dns_zones.notify_handler.schedule_ns_notify" ) as mock_ns_notify: with captureOnCommitCallbacks(execute=True): make_zone(name="nonsnotify.test") mock_ns_notify.assert_not_called() @pytest.mark.django_db class TestZonePostSaveUpdate: def test_updates_catz_identifier_on_name_change(self, make_zone): zone = make_zone(name="rename-old.test") old_name = CatalogZoneMemberIdentifier.objects.get(zone=zone).name zone.name = "rename-new.test" zone.save() new_name = CatalogZoneMemberIdentifier.objects.get(zone=zone).name assert old_name != new_name def test_no_identifier_update_when_name_unchanged(self, make_zone): zone = make_zone(name="nochange.test") ident = CatalogZoneMemberIdentifier.objects.get(zone=zone) original_name = ident.name zone.soa_serial = zone.soa_serial + 1 zone.save() ident.refresh_from_db() assert ident.name == original_name def test_increments_serial_on_commit_on_update(self, make_zone, catz_serial): zone = make_zone(name="update-serial.test") with captureOnCommitCallbacks(execute=True): zone.soa_serial = zone.soa_serial + 1 zone.save() obj = CatalogZone.objects.get(view=zone.view) assert obj.soa_serial == catz_serial + 1 @pytest.mark.django_db class TestZonePostDelete: def test_increments_soa_serial_on_commit_on_delete(self, make_zone, catz_serial): zone = make_zone(name="delete-serial.test") view = zone.view with captureOnCommitCallbacks(execute=True): zone.delete() obj = CatalogZone.objects.get(view=view) assert obj.soa_serial == catz_serial + 1 def test_does_not_increment_without_on_commit(self, make_zone, catz_serial): zone = make_zone(name="delete-no-commit.test") view = zone.view zone.delete() obj = CatalogZone.objects.get(view=view) assert obj.soa_serial == catz_serial @pytest.mark.django_db class TestZoneViewChange: def test_increments_both_catalog_zone_serials(self, make_zone, make_view, catz_serial): view_a = make_view(name="view-a") view_b = make_view(name="view-b") # view_b has no zones yet, so no CatalogZone was auto-created for it. CatalogZone.objects.create(view=view_b, soa_serial=5) zone = make_zone(name="move.test", view=view_a) serial_a_before = CatalogZone.objects.get(view=view_a).soa_serial serial_b_before = CatalogZone.objects.get(view=view_b).soa_serial with captureOnCommitCallbacks(execute=True): zone.view = view_b zone.save() serial_a_after = CatalogZone.objects.get(view=view_a).soa_serial serial_b_after = CatalogZone.objects.get(view=view_b).soa_serial assert serial_a_after == serial_a_before + 1 assert serial_b_after == serial_b_before + 1 def test_updates_identifier_catalog_zone_fk(self, make_zone, make_view): view_a = make_view(name="fk-view-a") view_b = make_view(name="fk-view-b") zone = make_zone(name="fk-move.test", view=view_a) ident = CatalogZoneMemberIdentifier.objects.get(zone=zone) assert ident.catalog_zone == CatalogZone.objects.get(view=view_a) zone.view = view_b zone.save() ident.refresh_from_db() assert ident.catalog_zone == CatalogZone.objects.get(view=view_b) @pytest.mark.django_db class TestSignalDedup: def test_dedup_skips_second_save_in_same_request(self, make_zone): from netbox.context import current_request from django.contrib.auth import get_user_model zone = make_zone(name="dedup.test") original_serial = CatalogZone.objects.get( view=zone.view ).soa_serial User = get_user_model() user = User(username="dedup-user", is_superuser=True) user.save() request = MagicMock() request._handled_zones = set() request.user = user import uuid as _uuid request.id = _uuid.uuid4() with current_request.set(request): with captureOnCommitCallbacks(execute=True): zone.soa_serial = zone.soa_serial + 1 zone.save() with captureOnCommitCallbacks(execute=True): zone.soa_serial = zone.soa_serial + 2 zone.save() new_serial = CatalogZone.objects.get( view=zone.view ).soa_serial assert new_serial == original_serial + 1 def test_no_dedup_without_request(self, make_zone): zone = make_zone(name="nodedup.test") original_serial = CatalogZone.objects.get( view=zone.view ).soa_serial with captureOnCommitCallbacks(execute=True): zone.soa_serial = zone.soa_serial + 1 zone.save() with captureOnCommitCallbacks(execute=True): zone.soa_serial = zone.soa_serial + 2 zone.save() new_serial = CatalogZone.objects.get( view=zone.view ).soa_serial assert new_serial == original_serial + 2 @pytest.mark.django_db class TestNotifyNsEnabled: def test_enabled_by_all_zones_flag(self, make_zone, patch_settings): patch_settings({"notify_ns_all_zones": True}) from netbox_dns_bridge.signals.netbox_dns_zones import _notify_ns_enabled zone = make_zone(name="nsflag.test") assert _notify_ns_enabled(zone) is True def test_disabled_by_default(self, make_zone, patch_settings): patch_settings({}) from netbox_dns_bridge.signals.netbox_dns_zones import _notify_ns_enabled zone = make_zone(name="nsdisabled.test") assert _notify_ns_enabled(zone) is False def test_enabled_by_custom_field(self, make_zone, patch_settings): patch_settings({"notify_ns_custom_field_name": "enable_notify"}) from netbox_dns_bridge.signals.netbox_dns_zones import _notify_ns_enabled zone = make_zone(name="nscf.test") zone.custom_field_data = {"enable_notify": True} assert _notify_ns_enabled(zone) is True def test_disabled_when_custom_field_false(self, make_zone, patch_settings): patch_settings({"notify_ns_custom_field_name": "enable_notify"}) from netbox_dns_bridge.signals.netbox_dns_zones import _notify_ns_enabled zone = make_zone(name="nscf-false.test") zone.custom_field_data = {"enable_notify": False} assert _notify_ns_enabled(zone) is False def test_disabled_when_custom_field_missing(self, make_zone, patch_settings): patch_settings({"notify_ns_custom_field_name": "enable_notify"}) from netbox_dns_bridge.signals.netbox_dns_zones import _notify_ns_enabled zone = make_zone(name="nscf-missing.test") zone.custom_field_data = {} assert _notify_ns_enabled(zone) is False