128 lines
5.8 KiB
Python
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()
|