131 lines
4.7 KiB
Python
131 lines
4.7 KiB
Python
"""Exercise automatic route discovery and mistakes that break search metadata."""
|
|
|
|
import contextlib
|
|
import importlib.util
|
|
import io
|
|
from pathlib import Path
|
|
import shutil
|
|
import tempfile
|
|
import unittest
|
|
import xml.etree.ElementTree as ET
|
|
|
|
|
|
ROOT = Path(__file__).resolve().parent.parent
|
|
|
|
|
|
def load_script(name):
|
|
"""Load a repository script without executing its command-line entrypoint."""
|
|
spec = importlib.util.spec_from_file_location(name, ROOT / "scripts" / f"{name}.py")
|
|
module = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(module)
|
|
return module
|
|
|
|
|
|
PREPARE = load_script("prepare-website")
|
|
VERIFY = load_script("verify-website")
|
|
|
|
|
|
class WebsiteSearchChecks(unittest.TestCase):
|
|
"""Use authored pages and temporary fixtures to check the publishing boundary."""
|
|
|
|
def test_sitemap_tracks_added_moved_removed_and_nonindexable_pages(self):
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
site = Path(directory)
|
|
|
|
def page(name, head="", body=""):
|
|
path = site / name
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(f"<html><head>{head}</head><body>{body}</body></html>")
|
|
return path
|
|
|
|
page("index.html")
|
|
nested = page("guides/new/index.html")
|
|
page(
|
|
"examples/fixture/index.html",
|
|
'<meta name="robots" content="NOINDEX, follow">',
|
|
)
|
|
page("draft.html", '<meta name="googlebot" content="none">')
|
|
page("code & notes.html")
|
|
page("visible/index.html", body='<meta name="robots" content="noindex">')
|
|
(site / "asset.json").write_text("{}")
|
|
|
|
def urls():
|
|
with contextlib.redirect_stdout(io.StringIO()):
|
|
PREPARE.prepare_sitemap(site)
|
|
sitemap = ET.parse(site / "sitemap.xml")
|
|
return {node.text for node in sitemap.findall(".//{*}loc")}
|
|
|
|
self.assertEqual(
|
|
urls(),
|
|
{
|
|
"https://rea.tools/",
|
|
"https://rea.tools/guides/new/",
|
|
"https://rea.tools/code%20%26%20notes.html",
|
|
"https://rea.tools/visible/",
|
|
},
|
|
)
|
|
nested.rename(site / "moved.html")
|
|
self.assertIn("https://rea.tools/moved.html", urls())
|
|
self.assertNotIn("https://rea.tools/guides/new/", urls())
|
|
(site / "moved.html").unlink()
|
|
self.assertNotIn("https://rea.tools/moved.html", urls())
|
|
|
|
def test_verifier_rejects_wrong_canonicals_stale_sitemaps_and_missing_previews(
|
|
self,
|
|
):
|
|
cases = (
|
|
(
|
|
"index.html",
|
|
'rel="canonical" href="https://rea.tools/"',
|
|
'rel="canonical" href="https://morluto.github.io/rea/"',
|
|
"head canonical",
|
|
),
|
|
(
|
|
"showcase/aegis/index.html",
|
|
'rel="canonical" href="https://rea.tools/showcase/aegis/"',
|
|
'rel="canonical" href="https://rea.tools/"',
|
|
"head canonical",
|
|
),
|
|
(
|
|
"examples/notes-electron/index.html",
|
|
'content="noindex"',
|
|
'content="index"',
|
|
"must have a head noindex",
|
|
),
|
|
(
|
|
"sitemap.xml",
|
|
"https://rea.tools/faq/",
|
|
"https://rea.tools/deleted/",
|
|
"each indexable page once",
|
|
),
|
|
(
|
|
"faq/index.html",
|
|
'name="twitter:card" content="summary_large_image"',
|
|
'name="twitter:card" content="summary"',
|
|
"twitter:card",
|
|
),
|
|
)
|
|
for name, old, new, message in cases:
|
|
with self.subTest(name=name), tempfile.TemporaryDirectory() as directory:
|
|
site = Path(directory) / "public"
|
|
shutil.copytree(ROOT / "website/public", site)
|
|
path = site / name
|
|
source = path.read_text()
|
|
self.assertIn(old, source)
|
|
path.write_text(source.replace(old, new, 1))
|
|
self.assertTrue(
|
|
any(message in error for error in VERIFY.check_site(site)[3])
|
|
)
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
site = Path(directory) / "public"
|
|
shutil.copytree(ROOT / "website/public", site)
|
|
(site / "assets/social-card.png").unlink()
|
|
self.assertIn(
|
|
"Social preview PNG is missing; run scripts/prepare-website.py",
|
|
VERIFY.check_site(site)[3],
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|