From 644652b1869ff02c082f8c89dc0ebbfd637eaf7f Mon Sep 17 00:00:00 2001 From: John Winter Murphy Date: Sat, 1 Aug 2026 14:31:05 -0400 Subject: [PATCH] Fix Namecheap non-MX preference normalization Ignore Namecheap MXPref placeholders outside MX records, add round-trip regression coverage, and keep the declared pytest gate collectable. --- ampg/dns.py | 9 +++++++-- tests/test_dns_manifest.py | 25 ++++++++++++++++++++++++- tests/test_service_manifest.py | 8 ++++---- 3 files changed, 35 insertions(+), 7 deletions(-) diff --git a/ampg/dns.py b/ampg/dns.py index a73799e..410e9c1 100644 --- a/ampg/dns.py +++ b/ampg/dns.py @@ -1023,13 +1023,18 @@ class NamecheapDNSProvider: for element in root.iter(): if _xml_localname(element.tag) != "host": continue + record_type = (element.get("Type") or "").upper() records.append( ProviderDNSRecord( name=element.get("Name") or "@", - type=(element.get("Type") or "").upper(), + type=record_type, value=element.get("Address") or "", ttl=int(element.get("TTL") or DEFAULT_TTL), - mx_pref=_optional_int(element.get("MXPref")), + mx_pref=( + _optional_int(element.get("MXPref")) + if record_type == "MX" + else None + ), ) ) return DNSZoneSnapshot( diff --git a/tests/test_dns_manifest.py b/tests/test_dns_manifest.py index 54e5070..30a622e 100644 --- a/tests/test_dns_manifest.py +++ b/tests/test_dns_manifest.py @@ -5,7 +5,7 @@ from pathlib import Path import tempfile import unittest -from ampg.dns import DNSZoneSnapshot, ProviderDNSRecord +from ampg.dns import DNSZoneSnapshot, NamecheapDNSProvider, ProviderDNSRecord from ampg.dns_manifest import ( load_dns_manifest, merge_dns_manifest_records, @@ -60,6 +60,29 @@ class FakeProvider: self.records = tuple(records) +class NamecheapProviderTests(unittest.TestCase): + def test_non_mx_preference_placeholder_is_ignored(self): + response = """\ + + + + + + + + +""" + provider = NamecheapDNSProvider.__new__(NamecheapDNSProvider) + provider._request = lambda _command, _params: response + provider._domain_params = lambda _domain: {"SLD": "example", "TLD": "test"} + + snapshot = provider.get_hosts("example.test") + + self.assertEqual("ok", snapshot.status) + self.assertIsNone(snapshot.records[0].mx_pref) + self.assertEqual(20, snapshot.records[1].mx_pref) + + class DNSManifestTests(unittest.TestCase): def setUp(self): self.temporary = tempfile.TemporaryDirectory() diff --git a/tests/test_service_manifest.py b/tests/test_service_manifest.py index 43faab3..cddb856 100644 --- a/tests/test_service_manifest.py +++ b/tests/test_service_manifest.py @@ -22,7 +22,7 @@ def golden() -> dict: return json.loads(GOLDEN_PATH.read_text(encoding="utf-8")) -def test_sign(secret: int, message: bytes) -> tuple[str, str]: +def _sign_for_test(secret: int, message: bytes) -> tuple[str, str]: point = bip340.point_mul(secret, bip340.SECP256K1_GENERATOR) assert point is not None adjusted = secret if point[1] % 2 == 0 else bip340.SECP256K1_ORDER - secret @@ -138,7 +138,7 @@ class ServiceManifestTest(unittest.TestCase): manifest = deepcopy(golden()["manifest"]) root_secret = int(golden()["test_secret_key"], 16) signing_secret = 4 - signing_key, _ = test_sign(signing_secret, bytes(32)) + signing_key, _ = _sign_for_test(signing_secret, bytes(32)) delegation_payload = { "schema": sm.DELEGATION_SCHEMA, "service_id": manifest["payload"]["service_id"], @@ -148,10 +148,10 @@ class ServiceManifestTest(unittest.TestCase): "expires_at": "2037-01-01T00:00:00Z", "sequence": 1, } - root_key, delegation_signature = test_sign( + root_key, delegation_signature = _sign_for_test( root_secret, sm.delegation_digest(delegation_payload) ) - signing_key, manifest_signature = test_sign( + signing_key, manifest_signature = _sign_for_test( signing_secret, sm.service_manifest_digest(manifest["payload"]) ) manifest["delegation"] = {