From 21cb038d696514f17da1329188e83bbe80297b29 Mon Sep 17 00:00:00 2001 From: Xget7 Date: Tue, 20 Oct 2026 11:28:23 -0300 Subject: [PATCH] Fail the script tests when a root or docs/ page links a missing file docs/ARCHITECTURE.md links the files that own each pipeline stage; a rename that orphans one now turns the scripts job red. --- scripts/tests/test_doc_links.py | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 scripts/tests/test_doc_links.py diff --git a/scripts/tests/test_doc_links.py b/scripts/tests/test_doc_links.py new file mode 100644 index 0000000..0cad48f --- /dev/null +++ b/scripts/tests/test_doc_links.py @@ -0,0 +1,43 @@ +from pathlib import Path +import re +import subprocess +import unittest + +ROOT = Path(__file__).resolve().parents[2] +# Root and docs/ pages only: package READMEs and distribution/ link paths of the npm package +# and the mirror repos, which resolve there and not in this checkout. +PAGES = (":(glob)*.md", ":(glob)docs/**/*.md") +FENCE = re.compile(r"```.*?```", re.S) +LINK = re.compile(r"\]\(([^)\s]+)\)|^\[[^\]]+\]:\s*(\S+)", re.M) +EXTERNAL = re.compile(r"^[a-z][a-z0-9+.-]*:", re.I) + + +def tracked_pages(): + out = subprocess.run(["git", "ls-files", "--", *PAGES], cwd=ROOT, check=True, + capture_output=True, text=True).stdout + return sorted(set(out.split())) + + +def broken_links(page): + text = FENCE.sub("", (ROOT / page).read_text(encoding="utf-8")) + broken = [] + for match in LINK.finditer(text): + target = (match.group(1) or match.group(2)).split("#")[0] + if not target or EXTERNAL.match(target): + continue + if not (ROOT / page).parent.joinpath(target).exists(): + broken.append(target) + return broken + + +class DocLinksTest(unittest.TestCase): + def test_relative_links_resolve(self): + pages = tracked_pages() + self.assertIn("docs/ARCHITECTURE.md", pages) + for page in pages: + with self.subTest(page=page): + self.assertEqual(broken_links(page), []) + + +if __name__ == "__main__": + unittest.main()