"""Tests for netbox_dns_bridge.utils: get_logger, build_dns_zone, export_bind_zone_file.""" import os import tempfile import dns.name import dns.rdatatype import dns.rdataclass import pytest from netbox_dns_bridge.utils import get_logger, build_dns_zone, export_bind_zone_file class TestGetLogger: def test_returns_dns_bridge_logger(self): logger = get_logger("some.module") assert logger.name == "dns-bridge" def test_ignores_name_argument(self): a = get_logger("module.a") b = get_logger("module.b") assert a is b assert a.name == "dns-bridge" def test_logger_is_singleton(self): import logging logger = get_logger("x") assert logger is logging.getLogger("dns-bridge") @pytest.mark.django_db class TestBuildDnsZone: def test_basic_a_record(self, make_zone, make_record): zone = make_zone(name="a.test") make_record(zone, name="www", type_="A", value="192.0.2.1") dns_zone = build_dns_zone(zone) origin = dns.name.from_text("a.test", dns.name.root) rds = dns_zone.get_rdataset(origin, dns.rdatatype.SOA) assert rds is not None a_rds = dns_zone.get_rdataset( dns.name.from_text("www", origin), dns.rdatatype.A ) assert a_rds is not None assert str(a_rds[0]) == "192.0.2.1" def test_apex_record_empty_name(self, make_zone, make_record): zone = make_zone(name="apex.test") make_record(zone, name="@", type_="A", value="192.0.2.10") dns_zone = build_dns_zone(zone) origin = dns.name.from_text("apex.test", dns.name.root) a_rds = dns_zone.get_rdataset(origin, dns.rdatatype.A) assert a_rds is not None assert str(a_rds[0]) == "192.0.2.10" def test_ttl_fallback_to_zone_default(self, make_zone, make_record): zone = make_zone(name="ttl.test", default_ttl=7200) make_record(zone, name="host", type_="A", value="192.0.2.2", ttl=None) dns_zone = build_dns_zone(zone) origin = dns.name.from_text("ttl.test", dns.name.root) a_rds = dns_zone.get_rdataset( dns.name.from_text("host", origin), dns.rdatatype.A ) assert a_rds.ttl == 7200 def test_record_ttl_overrides_zone_default(self, make_zone, make_record): zone = make_zone(name="ttl2.test", default_ttl=7200) make_record(zone, name="host", type_="A", value="192.0.2.3", ttl=300) dns_zone = build_dns_zone(zone) origin = dns.name.from_text("ttl2.test", dns.name.root) a_rds = dns_zone.get_rdataset( dns.name.from_text("host", origin), dns.rdatatype.A ) assert a_rds.ttl == 300 def test_txt_record_quoting(self, make_zone, make_record): zone = make_zone(name="txt.test") make_record(zone, name="spf", type_="TXT", value="v=spf1 -all") dns_zone = build_dns_zone(zone) origin = dns.name.from_text("txt.test", dns.name.root) txt_rds = dns_zone.get_rdataset( dns.name.from_text("spf", origin), dns.rdatatype.TXT ) assert txt_rds is not None assert str(txt_rds[0]) == '"v=spf1 -all"' def test_txt_record_already_quoted(self, make_zone, make_record): zone = make_zone(name="txtq.test") make_record(zone, name="dkim", type_="TXT", value='"k=rsa; p=MIGf"') dns_zone = build_dns_zone(zone) origin = dns.name.from_text("txtq.test", dns.name.root) txt_rds = dns_zone.get_rdataset( dns.name.from_text("dkim", origin), dns.rdatatype.TXT ) assert txt_rds is not None assert "k=rsa" in str(txt_rds[0]) def test_txt_record_long_value_chunked(self, make_zone, make_record): zone = make_zone(name="longtxt.test") long_val = "a" * 300 make_record(zone, name="big", type_="TXT", value=long_val) dns_zone = build_dns_zone(zone) origin = dns.name.from_text("longtxt.test", dns.name.root) txt_rds = dns_zone.get_rdataset( dns.name.from_text("big", origin), dns.rdatatype.TXT ) assert txt_rds is not None wire = str(txt_rds[0]) assert "a" * 300 in wire.replace('"', "").replace(" ", "") def test_absolute_name_record(self, make_zone, make_record): zone = make_zone(name="abs.test") make_record(zone, name="out.abs.test.", type_="A", value="192.0.2.99") dns_zone = build_dns_zone(zone) abs_name = dns.name.from_text("out.abs.test", dns.name.root) a_rds = dns_zone.get_rdataset(abs_name, dns.rdatatype.A) assert a_rds is not None assert str(a_rds[0]) == "192.0.2.99" def test_multiple_records_same_name_type(self, make_zone, make_record): zone = make_zone(name="multi.test") make_record(zone, name="lb", type_="A", value="192.0.2.1") make_record(zone, name="lb", type_="A", value="192.0.2.2") dns_zone = build_dns_zone(zone) origin = dns.name.from_text("multi.test", dns.name.root) a_rds = dns_zone.get_rdataset( dns.name.from_text("lb", origin), dns.rdatatype.A ) assert a_rds is not None assert len(a_rds) == 2 values = {str(r) for r in a_rds} assert values == {"192.0.2.1", "192.0.2.2"} def test_inactive_records_excluded(self, make_zone, make_record): from netbox_dns.choices import RecordStatusChoices zone = make_zone(name="inactive.test") make_record(zone, name="active", type_="A", value="192.0.2.1", status=RecordStatusChoices.STATUS_ACTIVE) make_record(zone, name="inactive", type_="A", value="192.0.2.2", status=RecordStatusChoices.STATUS_INACTIVE) dns_zone = build_dns_zone(zone) origin = dns.name.from_text("inactive.test", dns.name.root) assert dns_zone.get_rdataset( dns.name.from_text("active", origin), dns.rdatatype.A ) is not None assert dns_zone.get_rdataset( dns.name.from_text("inactive", origin), dns.rdatatype.A ) is None def test_zone_rdclass_is_in(self, make_zone, make_record): zone = make_zone(name="rdclass.test") make_record(zone, name="x", type_="A", value="192.0.2.1") dns_zone = build_dns_zone(zone) assert dns_zone.rdclass == dns.rdataclass.IN def test_aaaa_record(self, make_zone, make_record): zone = make_zone(name="aaaa.test") make_record(zone, name="v6", type_="AAAA", value="2001:db8::1") dns_zone = build_dns_zone(zone) origin = dns.name.from_text("aaaa.test", dns.name.root) aaaa_rds = dns_zone.get_rdataset( dns.name.from_text("v6", origin), dns.rdatatype.AAAA ) assert aaaa_rds is not None assert str(aaaa_rds[0]) == "2001:db8::1" def test_cname_record(self, make_zone, make_record): zone = make_zone(name="cname.test") make_record(zone, name="alias", type_="CNAME", value="target.test.") dns_zone = build_dns_zone(zone) origin = dns.name.from_text("cname.test", dns.name.root) cnames = dns_zone.get_rdataset( dns.name.from_text("alias", origin), dns.rdatatype.CNAME ) assert cnames is not None assert str(cnames[0]) == "target.test." def test_mx_record(self, make_zone, make_record): zone = make_zone(name="mx.test") make_record(zone, name="@", type_="MX", value="10 mail.test.") dns_zone = build_dns_zone(zone) origin = dns.name.from_text("mx.test", dns.name.root) mx_rds = dns_zone.get_rdataset(origin, dns.rdatatype.MX) assert mx_rds is not None assert mx_rds[0].preference == 10 assert str(mx_rds[0].exchange) == "mail.test." @pytest.mark.django_db class TestExportBindZoneFile: def test_writes_valid_zone_file(self, make_zone, make_record): zone = make_zone(name="export.test") make_record(zone, name="www", type_="A", value="192.0.2.1") with tempfile.NamedTemporaryFile(mode="w", suffix=".zone", delete=False) as f: path = f.name try: export_bind_zone_file(zone, path) with open(path) as f: content = f.read() assert "SOA" in content assert "192.0.2.1" in content assert "www" in content finally: os.unlink(path) def test_io_error_on_bad_path(self, make_zone): zone = make_zone(name="badpath.test") with pytest.raises(IOError): export_bind_zone_file(zone, "/nonexistent/dir/zone.file")