Files
adaptive-multi-protocol-gat…/tests/test_dns_manifest.py
T

140 lines
4.6 KiB
Python

from __future__ import annotations
import json
from pathlib import Path
import tempfile
import unittest
from ampg.dns import DNSZoneSnapshot, ProviderDNSRecord
from ampg.dns_manifest import (
load_dns_manifest,
merge_dns_manifest_records,
reconcile_dns_manifest,
)
def manifest_document():
return {
"contract_version": 1,
"app_id": "bloomword",
"zone": "bloomword.earth",
"provider": "namecheap",
"preserve_unmanaged": True,
"protected_names": ["@"],
"records": [
{
"name": "shop",
"type": "A",
"value": "94.72.115.61",
"ttl": 300,
"replace": "all",
"conflict_types": ["CNAME", "ALIAS"],
},
{
"name": "shop",
"type": "TXT",
"value": "v=spf1 include:mail.baby ~all",
"ttl": 300,
"replace": "value_prefix",
"match_value_prefix": "v=spf1",
},
],
}
class FakeProvider:
name = "namecheap"
def __init__(self, records):
self.records = tuple(records)
self.writes = 0
def get_hosts(self, zone):
return DNSZoneSnapshot(
"namecheap", zone, "<zone />", self.records, "ok", "ok"
)
def set_hosts(self, zone, records, *, mail_policy):
self.writes += 1
self.records = tuple(records)
class DNSManifestTests(unittest.TestCase):
def setUp(self):
self.temporary = tempfile.TemporaryDirectory()
self.path = Path(self.temporary.name) / "dns.json"
self.path.write_text(
json.dumps(manifest_document()), encoding="utf-8"
)
def tearDown(self):
self.temporary.cleanup()
def test_merge_preserves_apex_mail_and_unrelated_shop_txt(self):
manifest = load_dns_manifest(self.path)
existing = (
ProviderDNSRecord("@", "A", "153.75.248.86", 1800),
ProviderDNSRecord(
"@", "MX", "eforward1.registrar-servers.com.", 1800, 10
),
ProviderDNSRecord(
"@",
"TXT",
"v=spf1 include:spf.efwd.registrar-servers.com ~all",
1800,
),
ProviderDNSRecord("shop", "CNAME", "parking.example.", 1800),
ProviderDNSRecord("shop", "TXT", "verification=keep", 1800),
ProviderDNSRecord("shop", "TXT", "v=spf1 -all", 1800),
)
merged = merge_dns_manifest_records(existing, manifest.records)
identities = {
(record.name, record.type, record.value) for record in merged
}
self.assertIn(("@", "A", "153.75.248.86"), identities)
self.assertIn(
("@", "MX", "eforward1.registrar-servers.com."), identities
)
self.assertIn(("shop", "TXT", "verification=keep"), identities)
self.assertNotIn(("shop", "CNAME", "parking.example."), identities)
self.assertNotIn(("shop", "TXT", "v=spf1 -all"), identities)
self.assertIn(("shop", "A", "94.72.115.61"), identities)
def test_protected_apex_cannot_be_managed(self):
document = manifest_document()
document["records"][0]["name"] = "@"
self.path.write_text(json.dumps(document), encoding="utf-8")
with self.assertRaisesRegex(ValueError, "protected"):
load_dns_manifest(self.path)
def test_unresolved_values_are_rejected(self):
document = manifest_document()
document["records"][0]["value"] = "{{ prod_ip }}"
self.path.write_text(json.dumps(document), encoding="utf-8")
with self.assertRaisesRegex(ValueError, "unresolved"):
load_dns_manifest(self.path)
def test_connected_plan_is_read_only_and_apply_is_idempotent(self):
manifest = load_dns_manifest(self.path)
provider = FakeProvider(
[ProviderDNSRecord("@", "A", "153.75.248.86", 1800)]
)
planned = reconcile_dns_manifest(manifest, provider)
self.assertEqual("planned", planned["status"])
self.assertEqual(0, provider.writes)
backup_dir = Path(self.temporary.name) / "backups"
applied = reconcile_dns_manifest(
manifest, provider, apply=True, backup_dir=backup_dir
)
self.assertEqual("applied", applied["status"])
self.assertEqual(1, provider.writes)
second = reconcile_dns_manifest(
manifest, provider, apply=True, backup_dir=backup_dir
)
self.assertEqual("verified", second["status"])
self.assertEqual(1, provider.writes)
if __name__ == "__main__":
unittest.main()