From bac1c5ac51149459be3a1a82f7581557a5df2046 Mon Sep 17 00:00:00 2001 From: godosa Date: Wed, 7 Oct 2026 11:12:50 +0200 Subject: test: README link guard Co-Authored-By: Claude Sonnet 5.5 --- tests/test_readme_links.py | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 tests/test_readme_links.py diff --git a/tests/test_readme_links.py b/tests/test_readme_links.py new file mode 100644 index 0000000..8e997a7 --- /dev/null +++ b/tests/test_readme_links.py @@ -0,0 +1,40 @@ +"""README.md relative links must stay inside the repo and point to existing files.""" +import re +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parent.parent +LINK = re.compile(r'!?\[[^\]]*\]\(([^)\s]+)(?:\s+"[^"]*")?\)') + + +def bad_links(text, root=ROOT): + bad = [] + for target in LINK.findall(text): + if re.match(r'^([a-zA-Z][a-zA-Z0-9+.-]*:|#|//)', target): + continue + path = target.split('#')[0].split('?')[0] + if not path: + continue + p = (root / path).resolve() + if root.resolve() not in (p, *p.parents): + bad.append((target, 'escapes repo')) + elif not p.exists(): + bad.append((target, 'missing')) + return bad + + +class ReadmeLinks(unittest.TestCase): + def test_current_readme(self): + self.assertEqual(bad_links((ROOT / 'README.md').read_text()), []) + + def test_detects_escape(self): + self.assertEqual(bad_links('[v](../worldmap-viewer)'), + [('../worldmap-viewer', 'escapes repo')]) + + def test_detects_missing(self): + self.assertEqual(bad_links('![i](nope/none.png)'), + [('nope/none.png', 'missing')]) + + +if __name__ == '__main__': + unittest.main() -- cgit