Files
netbox-plugin-dns-bridge/netbox_dns_bridge/tests/conftest.py
T
sven 1c51206196 Upgrade models to full NetboxModel to allow UI/API interaction:
modified:   netbox_dns_bridge/models.py
deleted:    netbox_dns_bridge/migrations/0003_catalogzone_and_per_zone_identifier.py
new file:   netbox_dns_bridge/migrations/0003_full_netbox_models.py

Adjust model access and function calls to match new models:
modified:   netbox_dns_bridge/signals/netbox_dns_zones.py
modified:   netbox_dns_bridge/management/commands/dns_transfer_endpoint/catalog_zone_manager.py
modified:   netbox_dns_bridge/management/commands/dns_transfer_endpoint/request_handler.py

Update UI elements and permission interactions:
modified:   netbox_dns_bridge/filtersets.py
modified:   netbox_dns_bridge/forms.py
modified:   netbox_dns_bridge/jobs/notify.py
modified:   netbox_dns_bridge/navigation.py
modified:   netbox_dns_bridge/tables.py
deleted:    netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_members.html
new file:   netbox_dns_bridge/templates/netbox_dns_bridge/catalogzone.html
new file:   netbox_dns_bridge/templates/netbox_dns_bridge/catalogzonemember.html
deleted:    netbox_dns_bridge/templates/netbox_dns_bridge/notify_overview.html
new file:   netbox_dns_bridge/templates/netbox_dns_bridge/seentransferclient.html
modified:   netbox_dns_bridge/urls.py
modified:   netbox_dns_bridge/views.py

AI/LLM updated tests (Not verified):
modified:   netbox_dns_bridge/tests/conftest.py
modified:   netbox_dns_bridge/tests/test_catalog_zone_manager.py
modified:   netbox_dns_bridge/tests/test_models.py
modified:   netbox_dns_bridge/tests/test_notify.py
modified:   netbox_dns_bridge/tests/test_request_handler_integration.py
modified:   netbox_dns_bridge/tests/test_signals.py
modified:   netbox_dns_bridge/tests/test_smoke.py
modified:   netbox_dns_bridge/tests/test_views.py
2026-08-17 17:05:25 +02:00

259 lines
7.7 KiB
Python

"""Pytest configuration and shared fixtures.
Tests run inside the NetBox container (``DJANGO_SETTINGS_MODULE=netbox.configuration``)
where ``netbox`` and ``netbox_dns`` are importable. The plugin's module-level
``SETTINGS`` bindings are patched per-test via the ``patch_settings`` fixture.
Heavy imports are deferred into fixtures so this conftest imports cleanly before
the Django app registry is guaranteed ready (the root conftest calls
``django.setup()``).
"""
from __future__ import annotations
import pytest
@pytest.fixture
def default_view(db):
"""Ensure NetBox DNS has a default view (required before creating zones)."""
from netbox_dns.models import View
view, _ = View.objects.get_or_create(
name="default", defaults={"default_view": True}
)
return view
def _settings_modules():
from netbox_dns_bridge.management.commands.dns_transfer_endpoint import (
request_handler,
)
from netbox_dns_bridge.jobs import notify
return [notify, request_handler]
@pytest.fixture
def patch_settings(monkeypatch):
"""Patch the module-level ``SETTINGS`` dict on every consumer module."""
mods = _settings_modules()
originals = {mod: mod.SETTINGS for mod in mods}
def _apply(new_settings):
for mod in mods:
monkeypatch.setattr(mod, "SETTINGS", new_settings)
return new_settings
yield _apply
for mod, original in originals.items():
mod.SETTINGS = original
@pytest.fixture
def tsig_key():
from netbox_dns_bridge.tests._helpers import make_tsig_key
return make_tsig_key("testkey.")
@pytest.fixture
def capture_handler():
"""Factory: build a capture handler for unit tests."""
from netbox_dns_bridge.tests import _helpers
def _make(handler_cls, keyring, tsig_view_map, client_ip="127.0.0.1"):
return _helpers.make_capture_handler(
handler_cls, keyring, tsig_view_map, client_ip
)
return _make
# ---------------------------------------------------------------------------
# Database factories
# ---------------------------------------------------------------------------
@pytest.fixture
def make_view(db, default_view):
from netbox_dns.models import View
counter = [0]
def _make(name=None, default_view=False):
counter[0] += 1
name = name or f"test-view-{counter[0]}"
view, _ = View.objects.get_or_create(
name=name, defaults={"default_view": default_view}
)
return view
return _make
@pytest.fixture
def make_nameserver(db):
from netbox_dns.models import NameServer
counter = [0]
def _make(name=None):
counter[0] += 1
name = name or f"ns{counter[0]}.example.test"
ns, _ = NameServer.objects.get_or_create(name=name)
return ns
return _make
@pytest.fixture
def make_zone(db, make_view, make_nameserver):
from netbox_dns.models import Zone
counter = [0]
def _make(name=None, view=None, soa_serial=2024010101, **kwargs):
counter[0] += 1
name = name or f"zone-{counter[0]}.example.test"
view = view or make_view()
ns = make_nameserver(f"ns1.{name}")
defaults = dict(
view=view,
name=name,
soa_mname=ns,
soa_rname=f"hostmaster.{name}",
soa_ttl=300,
soa_serial=soa_serial,
soa_serial_auto=False,
soa_refresh=300,
soa_retry=100,
soa_expire=1209600,
soa_minimum=3600,
default_ttl=300,
status="active",
)
defaults.update(kwargs)
zone = Zone.objects.create(**defaults)
# Ensure an active SOA record exists (netbox_dns usually creates one,
# but be deterministic so build_dns_zone / _get_soa_serial always find it).
from netbox_dns.models import Record
from netbox_dns.choices import RecordStatusChoices
if not Record.objects.filter(zone=zone, type="SOA", active=True).exists():
fqdn = zone.name
Record.objects.create(
zone=zone,
name="",
fqdn=fqdn,
type="SOA",
value=(
f"ns1.{zone.name}. hostmaster.{zone.name}. {soa_serial} "
f"300 100 1209600 3600"
),
ttl=zone.soa_ttl,
status=RecordStatusChoices.STATUS_ACTIVE,
managed=False,
)
return zone
return _make
@pytest.fixture
def make_record(db):
from netbox_dns.models import Record
from netbox_dns.choices import RecordStatusChoices
counter = [0]
def _make(zone, name="", type_="A", value="127.0.0.1", ttl=None,
status=RecordStatusChoices.STATUS_ACTIVE, managed=False):
counter[0] += 1
fqdn = f"{name}.{zone.name}".lstrip(".") if name else zone.name
return Record.objects.create(
zone=zone,
name=name,
fqdn=fqdn,
type=type_,
value=value,
ttl=ttl if ttl is not None else zone.default_ttl,
status=status,
managed=managed,
)
return _make
@pytest.fixture
def catz_serial(db):
"""Initial SOA serial value (1) for freshly created catalog zones.
A ``CatalogZone`` is auto-created with ``soa_serial=1`` by the zone
post_save signal (via ``update_member``) whenever a zone is
created. Tests compare against this initial value.
"""
return 1
# ---------------------------------------------------------------------------
# Real-socket integration server
# ---------------------------------------------------------------------------
@pytest.fixture
def dns_server(tsig_key):
"""Start real TCP+UDP DNS servers on 127.0.0.1:0 in background threads.
Usage::
srv = dns_server.start(views={"testkey.": view_obj})
resp = dns.query.tcp(msg, srv["host"], port=srv["port"])
"""
import threading
import dns.name
from netbox_dns_bridge.management.commands.dns_transfer_endpoint.dns_server import (
TCPDNSServer,
UDPDNSServer,
)
from netbox_dns_bridge.management.commands.dns_transfer_endpoint.request_handler import (
TCPRequestHandler,
UDPRequestHandler,
)
class _Server:
def __init__(self, key):
self.tsig_key = key
self._tcp = None
self._udp = None
def start(self, views):
keyring = {self.tsig_key.name: self.tsig_key}
tsig_view_map = {}
for key_name, view in views.items():
canon = (
dns.name.from_text(key_name, origin=dns.name.root)
.canonicalize()
.to_text()
)
tsig_view_map[canon] = view
self._tcp = TCPDNSServer(
("127.0.0.1", 0), TCPRequestHandler, keyring, tsig_view_map
)
port = self._tcp.server_address[1]
# Bind UDP to the same port number (TCP/UDP sockets may share it).
self._udp = UDPDNSServer(
("127.0.0.1", port), UDPRequestHandler, keyring, tsig_view_map
)
threading.Thread(target=self._tcp.serve_forever, daemon=True).start()
threading.Thread(target=self._udp.serve_forever, daemon=True).start()
return {
"host": "127.0.0.1",
"port": port,
"tsig_key": self.tsig_key,
}
def shutdown(self):
for srv in (self._tcp, self._udp):
if srv is not None:
srv.shutdown()
srv.server_close()
server = _Server(tsig_key)
try:
yield server
finally:
server.shutdown()