Files
ThothII/scripts/test-verify-public-docs.py
T
Codex b1c510a097
Publish documentation / publish (push) Successful in 36s
fix(docs): preserve theme assets in deny-by-default publication
2026-09-16 09:36:30 +02:00

128 lines
5.8 KiB
Python

#!/usr/bin/env python3
"""Negative fixtures for public documentation publication checks."""
import importlib.util
import json
from pathlib import Path
import tempfile
import unittest
spec = importlib.util.spec_from_file_location(
"public_docs", Path(__file__).with_name("verify-public-docs.py")
)
verifier = importlib.util.module_from_spec(spec)
spec.loader.exec_module(verifier)
class PublicationBoundaryTest(unittest.TestCase):
def setUp(self):
self.temp = tempfile.TemporaryDirectory(prefix="thoth-public-docs-")
self.addCleanup(self.temp.cleanup)
self.root = Path(self.temp.name)
self.site = self.root / "site"
self.config = "nav:\n- Home: index.md\nexclude_docs: |\n *\n"
for source in ["index.md", *sorted(verifier.PUBLIC_ASSETS)]:
self.config += f" !/{source}\n"
self.write(self.root / "docs" / source, "# Public")
self.write(self.site / verifier.output_path(source), "public")
self.write(self.root / "mkdocs.yml", self.config)
for asset in sorted(verifier.THEME_ASSETS):
self.config += f" !/{asset}\n"
self.write(self.site / asset, "theme fixture")
self.write(self.root / "mkdocs.yml", self.config)
self.search = {"docs": [{"location": "", "text": "public"}]}
self.write_search()
self.write(self.root / "docs/plans/private.md", "# Internal")
self.write(self.root / "docs/reports/screenshot.png", "internal asset")
self.write(self.site / "index.html", '<link rel="stylesheet" href="css/base.css"><script src="js/base.js"></script>')
self.write(self.site / "css/base.css", '@font-face { src: url("../fonts/demo.woff2?v=1"); }')
self.write(self.site / "fonts/demo.woff2", "font fixture")
self.write(self.site / "js/base.js", "// script fixture")
@staticmethod
def write(path, content):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(content)
def write_search(self):
self.write(self.site / "search/search_index.json", json.dumps(self.search))
def test_public_only(self):
self.assertEqual(verifier.check(self.root, self.site), 1)
def test_internal_nav(self):
self.write(self.root / "mkdocs.yml", self.config.replace(
"- Home: index.md", "- Home: index.md\n- Internal: plans/private.md"
))
with self.assertRaisesRegex(ValueError, "internal page"):
verifier.check(self.root, self.site)
def test_unlisted_exception(self):
self.write(self.root / "mkdocs.yml", self.config + " !/plans/private.md\n")
with self.assertRaisesRegex(ValueError, "exceptions must match"):
verifier.check(self.root, self.site)
def test_missing_deny_default(self):
self.write(self.root / "mkdocs.yml", self.config.replace(" *\n", ""))
with self.assertRaisesRegex(ValueError, "deny-by-default"):
verifier.check(self.root, self.site)
def test_leaked_internal_page(self):
self.write(self.site / "plans/private/index.html", "internal")
with self.assertRaisesRegex(ValueError, "internal file leaked"):
verifier.check(self.root, self.site)
def test_leaked_internal_asset(self):
self.write(self.site / "reports/screenshot.png", "internal")
with self.assertRaisesRegex(ValueError, "internal file leaked"):
verifier.check(self.root, self.site)
def test_internal_search_entry(self):
self.search["docs"].append({"location": "plans/private/#internal", "text": "internal"})
self.write_search()
with self.assertRaisesRegex(ValueError, "non-public page in search"):
verifier.check(self.root, self.site)
def test_missing_public_page(self):
(self.site / "index.html").unlink()
with self.assertRaisesRegex(ValueError, "missing generated public"):
verifier.check(self.root, self.site)
def test_missing_theme_stylesheet(self):
(self.site / "css/base.css").unlink()
with self.assertRaisesRegex(ValueError, "missing generated page asset: css/base.css"):
verifier.check(self.root, self.site)
def test_docs_cannot_shadow_theme_assets(self):
self.write(self.root / "docs/css/base.css", "internal content")
with self.assertRaisesRegex(ValueError, "docs source shadows theme asset"):
verifier.check(self.root, self.site)
def test_missing_theme_script(self):
(self.site / "js/base.js").unlink()
with self.assertRaisesRegex(ValueError, "missing generated page asset: js/base.js"):
verifier.check(self.root, self.site)
def test_missing_css_font(self):
(self.site / "fonts/demo.woff2").unlink()
with self.assertRaisesRegex(ValueError, "missing generated page asset: fonts/demo.woff2"):
verifier.check(self.root, self.site)
def test_asset_outside_site_prefix(self):
self.write(self.root / "mkdocs.yml", self.config + "site_url: https://docs.example.invalid/manual/\n")
self.write(self.site / "index.html", '<link rel="stylesheet" href="/css/base.css">')
with self.assertRaisesRegex(ValueError, "asset escapes public site prefix"):
verifier.check(self.root, self.site)
def test_nested_installation_asset_resolution(self):
self.write(self.site / "install/manual/index.html", '<link rel="stylesheet" href="../../css/base.css">')
verifier.check_assets(self.site, ["install/manual.md"], "https://docs.example.invalid/manual/")
(self.site / "css/base.css").unlink()
with self.assertRaisesRegex(ValueError, "missing generated page asset: css/base.css"):
verifier.check_assets(self.site, ["install/manual.md"], "https://docs.example.invalid/manual/")
if __name__ == "__main__":
unittest.main()