Skip to content

Commit e3e5c35

Browse files
authored
Fix health check crash and integration test failures (DNS + CI)
## Summary - Fix `/v1/meta/status` health check crash: `check_all_rir_status()` did not exist; replaced with `get_rir_server_status()` - Fix 6 DNS `rir_client` functions accepting plain `str` args when `api.py` and tests pass Pydantic model objects (`DNSResolveInput`, `DNSEnumerateInput`, etc.), causing `AttributeError` on `.strip()` - Fix integration test attribute mismatches: `result.errors` vs `.error`, `result.a_records` vs `.records`, `result.checked_count` vs `.lists_checked`, `result.consistent` vs `.propagation_complete`
2 parents a00b3eb + eded9ea commit e3e5c35

3 files changed

Lines changed: 31 additions & 31 deletions

File tree

api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,8 @@ async def meta_cache(request: Request):
229229
@limiter.limit(_DEFAULT_RATE)
230230
async def meta_status(request: Request):
231231
"""Pings all 5 RIR RDAP servers and returns their status."""
232-
statuses = await rir_client.check_all_rir_status()
233-
return [s.model_dump() for s in statuses]
232+
statuses = await rir_client.get_rir_server_status()
233+
return statuses
234234

235235

236236
# ──────────────────────────────────────────────────────────────

rir_client.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,12 +2060,12 @@ async def _dns_query(name: str, rdtype: str, resolver: Optional[dns.asyncresolve
20602060

20612061
# ── E1 — Forward & Reverse DNS Resolution ────────────────────
20622062

2063-
async def dns_resolve(target: str) -> DNSResolveResult:
2063+
async def dns_resolve(inp: "DNSResolveInput") -> DNSResolveResult:
20642064
"""
20652065
Resolve A/AAAA records for a domain, or PTR records for an IP address.
20662066
For IP inputs, also correlates with RDAP owner to flag PTR mismatches.
20672067
"""
2068-
target = target.strip()
2068+
target = inp.target.strip()
20692069
is_ip = False
20702070
errors: list[str] = []
20712071

@@ -2126,12 +2126,12 @@ async def dns_resolve(target: str) -> DNSResolveResult:
21262126

21272127
# ── E2 — Full DNS Record Enumeration ─────────────────────────
21282128

2129-
async def dns_enumerate(domain: str) -> DNSEnumerateResult:
2129+
async def dns_enumerate(inp: "DNSEnumerateInput") -> DNSEnumerateResult:
21302130
"""
21312131
Fetch all common DNS record types (A, AAAA, MX, NS, SOA, TXT, CAA,
21322132
DNSKEY, SRV, CNAME) for a domain. Flags SPF and DMARC values from TXT.
21332133
"""
2134-
domain = domain.strip().lower()
2134+
domain = inp.domain.strip().lower()
21352135
record_types = ["A", "AAAA", "MX", "NS", "SOA", "TXT", "CAA", "DNSKEY", "SRV", "CNAME"]
21362136
records: dict[str, list[DNSRecord]] = {}
21372137
errors: list[str] = []
@@ -2192,7 +2192,7 @@ async def _fetch(rtype: str) -> list[DNSRecord]:
21922192

21932193
# ── E3 — DNSSEC Chain Validation ─────────────────────────────
21942194

2195-
async def dns_dnssec(domain: str) -> DNSSECResult:
2195+
async def dns_dnssec(inp: "DNSSECInput") -> DNSSECResult:
21962196
"""
21972197
Check DNSSEC deployment for a domain.
21982198
Returns SECURE / INSECURE / BOGUS / INDETERMINATE.
@@ -2202,7 +2202,7 @@ async def dns_dnssec(domain: str) -> DNSSECResult:
22022202
BOGUS: Records present but RRSIG expiry issues or mismatch.
22032203
INDETERMINATE: Cannot determine status (query failures).
22042204
"""
2205-
domain = domain.strip().lower()
2205+
domain = inp.domain.strip().lower()
22062206
errors: list[str] = []
22072207
has_dnskey = False
22082208
has_rrsig = False
@@ -2268,12 +2268,12 @@ async def dns_dnssec(domain: str) -> DNSSECResult:
22682268

22692269
# ── E4 — DNSBL Blocklist Checking ────────────────────────────
22702270

2271-
async def dns_dnsbl(ip: str) -> DNSBLResult:
2271+
async def dns_dnsbl(inp: "DNSBLInput") -> DNSBLResult:
22722272
"""
22732273
Check an IPv4 address against 30 DNS blocklists in parallel.
22742274
Uses pure DNS A-record lookups — no external APIs or keys needed.
22752275
"""
2276-
ip = ip.strip()
2276+
ip = inp.ip.strip()
22772277
errors: list[str] = []
22782278

22792279
# Reverse the IP octets for DNSBL queries: 1.2.3.4 → 4.3.2.1
@@ -2324,12 +2324,12 @@ async def _check_one(zone: str, desc: str) -> DNSBLEntry:
23242324

23252325
# ── E5 — Email Security Record Analysis ──────────────────────
23262326

2327-
async def dns_email_security(domain: str) -> EmailSecurityResult:
2327+
async def dns_email_security(inp: "EmailSecurityInput") -> EmailSecurityResult:
23282328
"""
23292329
Comprehensive email security audit: SPF, DMARC, DKIM, MX.
23302330
All pure DNS — no external APIs or keys needed.
23312331
"""
2332-
domain = domain.strip().lower()
2332+
domain = inp.domain.strip().lower()
23332333
errors: list[str] = []
23342334
recommendations: list[str] = []
23352335

@@ -2455,13 +2455,13 @@ async def _probe_dkim(selector: str) -> Optional[str]:
24552455

24562456
# ── E7 — DNS Propagation Check ───────────────────────────────
24572457

2458-
async def dns_propagation(domain: str, record_type: str = "A") -> DNSPropagationResult:
2458+
async def dns_propagation(inp: "DNSPropagationInput") -> DNSPropagationResult:
24592459
"""
24602460
Query the same domain from 10 geographically distributed public
24612461
resolvers and compare results to detect propagation lag.
24622462
"""
2463-
domain = domain.strip().lower()
2464-
record_type = record_type.upper()
2463+
domain = inp.domain.strip().lower()
2464+
record_type = inp.record_type.upper()
24652465
errors: list[str] = []
24662466
entries: list[PropagationEntry] = []
24672467

test_integration.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -563,18 +563,18 @@ async def test_dns_resolve():
563563
print(" Expect: A records with 1.1.1.x IPs, RDAP holder = APNIC/Cloudflare")
564564
try:
565565
from models import DNSResolveInput
566-
inp = DNSResolveInput(target="cloudflare.com", record_type="A")
566+
inp = DNSResolveInput(target="cloudflare.com")
567567
result = await rir_client.dns_resolve(inp)
568568

569-
if result.error:
570-
fail("DNS resolve cloudflare.com", result.error); return
569+
if result.errors:
570+
fail("DNS resolve cloudflare.com", result.errors[0]); return
571571

572-
ok("Records returned", f"{len(result.records)} A record(s)") if result.records else fail("Records returned", "empty")
573-
ips = [r.value for r in result.records]
572+
ips = result.a_records
573+
ok("Records returned", f"{len(ips)} A record(s)") if ips else fail("Records returned", "empty")
574574
has_cloudflare_ip = any(ip.startswith("1.1.1.") or ip.startswith("104.") for ip in ips)
575575
ok("Cloudflare IP returned", f"IPs: {ips[:3]}") if has_cloudflare_ip else ok("A records returned", f"IPs: {ips[:3]}")
576-
if result.rdap_correlation:
577-
ok("RDAP correlation present", f"holder={result.rdap_correlation.get('holder','?')}")
576+
if result.rdap_org:
577+
ok("RDAP correlation present", f"org={result.rdap_org}")
578578
else:
579579
skip("RDAP correlation", "No correlation (may be normal if RDAP lookup skipped)")
580580
except Exception:
@@ -611,7 +611,7 @@ async def test_dns_dnssec():
611611
inp = DNSSECInput(domain="cloudflare.com")
612612
result = await rir_client.dns_dnssec(inp)
613613

614-
ok(f"DNSSEC status: {result.status}", f"chain_valid={result.chain_valid}, dnskey_count={result.dnskey_count}")
614+
ok(f"DNSSEC status: {result.status}", f"has_dnskey={result.has_dnskey}, has_rrsig={result.has_rrsig}, has_ds={result.has_ds}")
615615
if result.status == "SECURE":
616616
ok("DNSSEC chain is SECURE")
617617
elif result.status == "INSECURE":
@@ -634,7 +634,7 @@ async def test_dns_dnsbl():
634634
result = await rir_client.dns_dnsbl(inp)
635635

636636
listed = [e for e in result.entries if e.listed]
637-
ok(f"Checked {result.lists_checked} blocklists", f"{len(listed)} listed")
637+
ok(f"Checked {result.checked_count} blocklists", f"{len(listed)} listed")
638638
if len(listed) == 0:
639639
ok("1.1.1.1 is CLEAN across all lists")
640640
elif len(listed) <= 2:
@@ -654,14 +654,14 @@ async def test_dns_email_security():
654654
inp = EmailSecurityInput(domain="cloudflare.com")
655655
result = await rir_client.dns_email_security(inp)
656656

657-
ok("SPF present", result.spf_record[:80] if result.spf_record else "—") if result.spf_present else fail("SPF present", "Missing SPF record")
658-
ok("DMARC present", result.dmarc_record[:80] if result.dmarc_record else "—") if result.dmarc_present else fail("DMARC present", "Missing DMARC record")
657+
ok("SPF present", result.spf_record[:80] if result.spf_record else "—") if result.spf_valid else fail("SPF present", "Missing SPF record")
658+
ok("DMARC present", f"policy={result.dmarc_policy}") if result.dmarc_present else fail("DMARC present", "Missing DMARC record")
659659
if result.dmarc_policy in ("reject", "quarantine"):
660660
ok(f"DMARC policy is strong", f"p={result.dmarc_policy}")
661661
else:
662662
skip("DMARC policy strength", f"p={result.dmarc_policy}")
663663
ok(f"MX records found", f"{len(result.mx_records)} MX record(s)") if result.mx_records else skip("MX records", "No MX (may query subdomains)")
664-
ok(f"Risk level assessed", f"{result.risk_level} (score={result.score})")
664+
ok(f"Risk level assessed", f"{result.risk_level}")
665665
except Exception:
666666
fail("Email security", traceback.format_exc()[-120:])
667667

@@ -675,10 +675,10 @@ async def test_dns_propagation():
675675
inp = DNSPropagationInput(domain="cloudflare.com", record_type="A")
676676
result = await rir_client.dns_propagation(inp)
677677

678-
ok(f"Queried {len(result.results)} resolvers", f"majority_answer={result.majority_answer[:2] if result.majority_answer else '?'}")
679-
answered = [e for e in result.results if e.answers]
680-
ok(f"Resolvers answered", f"{len(answered)}/{len(result.results)} returned records") if answered else fail("Resolvers answered", "None returned records")
681-
if result.propagation_complete:
678+
ok(f"Queried {len(result.entries)} resolvers", f"majority_answer={result.majority_answer[:2] if result.majority_answer else '?'}")
679+
answered = [e for e in result.entries if e.response]
680+
ok(f"Resolvers answered", f"{len(answered)}/{len(result.entries)} returned records") if answered else fail("Resolvers answered", "None returned records")
681+
if result.consistent:
682682
ok("Propagation complete (majority agree)")
683683
else:
684684
skip("Propagation complete", "Some resolvers diverging — transient or expected")

0 commit comments

Comments
 (0)