diff options
| author | Danilo M. <danix@danix.xyz> | 2026-09-09 09:18:19 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-09-09 09:18:19 +0200 |
| commit | 66fc4c8354d7acd9e50329283844ccb0ceac63a7 (patch) | |
| tree | 89f8bc3809db494fdf951b3a7b6de5f6494e8b8a /tests | |
| parent | c1887ab10ee16bbaf19025575759efa031133b65 (diff) | |
| download | abusectl-66fc4c8354d7acd9e50329283844ccb0ceac63a7.tar.gz abusectl-66fc4c8354d7acd9e50329283844ccb0ceac63a7.zip | |
feat: cache the IANA bootstrap, and prefer a stale copy to none
Seven day TTL under XDG_CACHE_HOME, deliberately not in a case
directory: this is a copy of a public map, not evidence.
A failed refetch falls back to the stale copy. Staleness is safe in this
direction, since a server that has moved simply misses and reads as no
contact, while losing IANA entirely would stop the user filing a report.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wrfqr2xqQfhtXCscU7zrdz
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test_rdap.py | 73 |
1 files changed, 73 insertions, 0 deletions
diff --git a/tests/test_rdap.py b/tests/test_rdap.py index 681e2bb..3fc2c65 100644 --- a/tests/test_rdap.py +++ b/tests/test_rdap.py @@ -52,5 +52,78 @@ class RedirectPolicy(unittest.TestCase): self.assertIsNotNone(result) +import json +import tempfile +import time +from pathlib import Path + + +class Bootstrap(unittest.TestCase): + def setUp(self): + self.tmp = tempfile.TemporaryDirectory() + self.cache = Path(self.tmp.name) + self.addCleanup(self.tmp.cleanup) + + def test_a_missing_file_is_fetched_and_cached(self): + calls = [] + + def fetch(url): + calls.append(url) + return {"services": []} + + data = rdap.bootstrap("ipv4", cache_root=self.cache, fetch=fetch) + + self.assertEqual(data, {"services": []}) + self.assertEqual(calls, ["https://data.iana.org/rdap/ipv4.json"]) + self.assertTrue((self.cache / "ipv4.json").exists()) + + def test_a_fresh_cache_is_not_refetched(self): + (self.cache / "ipv4.json").write_text(json.dumps({"services": ["cached"]})) + + def fetch(url): + raise AssertionError(f"should not have fetched {url}") + + data = rdap.bootstrap("ipv4", cache_root=self.cache, fetch=fetch) + self.assertEqual(data, {"services": ["cached"]}) + + def test_a_stale_cache_is_refetched(self): + path = self.cache / "ipv4.json" + path.write_text(json.dumps({"services": ["old"]})) + old = time.time() - (rdap._BOOTSTRAP_TTL + 60) + import os + os.utime(path, (old, old)) + + data = rdap.bootstrap( + "ipv4", cache_root=self.cache, fetch=lambda url: {"services": ["new"]} + ) + self.assertEqual(data, {"services": ["new"]}) + + def test_a_failed_refetch_falls_back_to_the_stale_copy(self): + """Losing IANA must not stop the user filing a report. + + Last week's map is almost certainly still correct, and a stale + bootstrap fails safe: the worst case is querying a server that has + moved, which misses and reads as no contact. + """ + path = self.cache / "ipv4.json" + path.write_text(json.dumps({"services": ["old"]})) + old = time.time() - (rdap._BOOTSTRAP_TTL + 60) + import os + os.utime(path, (old, old)) + + def fetch(url): + raise OSError("network is unreachable") + + data = rdap.bootstrap("ipv4", cache_root=self.cache, fetch=fetch) + self.assertEqual(data, {"services": ["old"]}) + + def test_a_failed_fetch_with_no_cache_raises(self): + def fetch(url): + raise OSError("network is unreachable") + + with self.assertRaises(rdap.BootstrapUnavailable): + rdap.bootstrap("ipv4", cache_root=self.cache, fetch=fetch) + + if __name__ == "__main__": unittest.main() |
