"""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 ( IntegerKeyValueSetting, 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 = IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial") assert obj.value == catz_serial + 1 def test_does_not_increment_without_on_commit(self, make_zone, catz_serial): make_zone(name="no-commit.test") obj = IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial") assert obj.value == 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 = IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial") assert obj.value == 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") with captureOnCommitCallbacks(execute=True): zone.delete() obj = IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial") assert obj.value == catz_serial + 1 def test_does_not_increment_without_on_commit(self, make_zone, catz_serial): zone = make_zone(name="delete-no-commit.test") zone.delete() obj = IntegerKeyValueSetting.objects.get(key="catalog-zone-soa-serial") assert obj.value == catz_serial @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 = IntegerKeyValueSetting.objects.get( key="catalog-zone-soa-serial" ).value 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 = IntegerKeyValueSetting.objects.get( key="catalog-zone-soa-serial" ).value assert new_serial == original_serial + 1 def test_no_dedup_without_request(self, make_zone): zone = make_zone(name="nodedup.test") original_serial = IntegerKeyValueSetting.objects.get( key="catalog-zone-soa-serial" ).value 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 = IntegerKeyValueSetting.objects.get( key="catalog-zone-soa-serial" ).value 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