import os import re import subprocess import tempfile import textwrap import unittest from pathlib import Path REPOSITORY_ROOT = Path(__file__).resolve().parents[1] INFRASTRUCTURE_ROOT = REPOSITORY_ROOT.parent / "hetzner.develton.com" EXPECTED_HUGO_VERSION = "0.165.0" EXPECTED_HUGO_SHA256 = "f43494894cdf4a8630a201d5c828051c77f523cc66bb3938b30806835470ac20" def read(path: Path) -> str: return path.read_text(encoding="utf-8") def workflow_run_block(workflow: str, step_name: str) -> str: match = re.search( rf"^ - name: {re.escape(step_name)}\n" r" run: \|\n" r"(?P(?: .*\n)+)", workflow, re.MULTILINE, ) if match is None: raise AssertionError(f"Workflow step not found: {step_name}") return textwrap.dedent(match.group("body")) class HugoVersionPinTests(unittest.TestCase): def test_legacy_image_uses_expected_hugo_version(self) -> None: dockerfile = read(REPOSITORY_ROOT / "Dockerfile") match = re.search(r"^ARG HUGO_VERSION=(\S+)$", dockerfile, re.MULTILINE) self.assertIsNotNone(match, "Dockerfile must declare HUGO_VERSION") self.assertEqual(EXPECTED_HUGO_VERSION, match.group(1)) self.assertIn("hugo_extended_${HUGO_VERSION}_", dockerfile) def test_shared_workflow_pins_and_validates_extended_hugo(self) -> None: workflow = read( REPOSITORY_ROOT / ".forgejo" / "workflows" / "deploy-hugo-site.yml" ) self.assertRegex( workflow, re.compile( rf'^ HUGO_VERSION: ["\']{re.escape(EXPECTED_HUGO_VERSION)}["\']$', re.MULTILINE, ), ) self.assertIn(f' HUGO_SHA256: "{EXPECTED_HUGO_SHA256}"', workflow) path = re.search(r"^ PATH: (.+)$", workflow, re.MULTILINE) self.assertIsNotNone(path) self.assertNotIn("/snap/hugo", path.group(1)) checkout_position = workflow.find("- name: Checkout repository") install_position = workflow.find("- name: Install pinned Hugo Extended") self.assertGreaterEqual(checkout_position, 0) self.assertGreater(install_position, checkout_position) installation = workflow_run_block(workflow, "Install pinned Hugo Extended") self.assertIn("sha256sum --check --strict", installation) self.assertIn("GITHUB_PATH", installation) validation_position = workflow.find("- name: Validate Hugo version") deploy_position = workflow.find("- name: Run deploy script") self.assertGreater(validation_position, install_position) self.assertGreater(deploy_position, validation_position) validation = workflow_run_block(workflow, "Validate Hugo version") self.assertIn("hugo version", validation) self.assertIn("exit 1", validation) def test_shared_workflow_version_guard_fails_closed(self) -> None: workflow = read( REPOSITORY_ROOT / ".forgejo" / "workflows" / "deploy-hugo-site.yml" ) validation = workflow_run_block(workflow, "Validate Hugo version") with tempfile.TemporaryDirectory() as temporary_directory: hugo_directory = Path(temporary_directory) / f"hugo-{EXPECTED_HUGO_VERSION}" hugo_directory.mkdir() fake_hugo = hugo_directory / "hugo" fake_hugo.write_text( "#!/bin/sh\nprintf '%s\\n' \"$FAKE_HUGO_VERSION_OUTPUT\"\n", encoding="utf-8", ) fake_hugo.chmod(0o755) environment = os.environ.copy() environment["PATH"] = f"{hugo_directory}:{environment['PATH']}" environment["RUNNER_TEMP"] = temporary_directory environment["HUGO_VERSION"] = EXPECTED_HUGO_VERSION accepted_outputs = ( ( "hugo v0.165.0+extended linux/amd64 " "BuildDate=unknown VendorInfo=gohugoio" ), ( "hugo v0.165.0-67fb390c6309dc2b8361e714fc5d71e3e4d625cf" "+extended linux/amd64 BuildDate=unknown VendorInfo=snap:0.165.0" ), ) for accepted_output in accepted_outputs: with self.subTest(accepted_output=accepted_output): environment["FAKE_HUGO_VERSION_OUTPUT"] = accepted_output accepted = subprocess.run( ["bash", "-c", validation], env=environment, capture_output=True, text=True, check=False, ) self.assertEqual(0, accepted.returncode, accepted.stderr) rejected_outputs = ( "hugo v0.164.0+extended linux/amd64 BuildDate=unknown", "hugo v0.165.1+extended linux/amd64 BuildDate=unknown", "hugo v0.165.1-abcdef+extended linux/amd64 BuildDate=unknown", "hugo v0.165.0 linux/amd64 BuildDate=unknown", "hugo v0.165.0-abcdef linux/amd64 BuildDate=unknown", ) for rejected_output in rejected_outputs: with self.subTest(rejected_output=rejected_output): environment["FAKE_HUGO_VERSION_OUTPUT"] = rejected_output rejected = subprocess.run( ["bash", "-c", validation], env=environment, capture_output=True, text=True, check=False, ) self.assertNotEqual(0, rejected.returncode) other_directory = Path(temporary_directory) / "other" other_directory.mkdir() other_hugo = other_directory / "hugo" other_hugo.write_text(fake_hugo.read_text(), encoding="utf-8") other_hugo.chmod(0o755) environment["FAKE_HUGO_VERSION_OUTPUT"] = accepted_outputs[0] environment["PATH"] = f"{other_directory}:{environment['PATH']}" rejected = subprocess.run( ["bash", "-c", validation], env=environment, capture_output=True, text=True, check=False, ) self.assertNotEqual(0, rejected.returncode) self.assertIn("Expected hugo at", rejected.stderr) def test_install_step_rejects_checksum_mismatch(self) -> None: workflow = read( REPOSITORY_ROOT / ".forgejo" / "workflows" / "deploy-hugo-site.yml" ) installation = workflow_run_block(workflow, "Install pinned Hugo Extended") with tempfile.TemporaryDirectory() as temporary_directory: temporary_path = Path(temporary_directory) fake_bin = temporary_path / "bin" fake_bin.mkdir() fake_curl = fake_bin / "curl" fake_curl.write_text( '#!/bin/sh\n' 'while [ "$#" -gt 0 ]; do\n' ' if [ "$1" = "--output" ]; then\n' ' printf junk > "$2"\n' ' exit 0\n' ' fi\n' ' shift\n' 'done\n' 'exit 1\n', encoding="utf-8", ) fake_curl.chmod(0o755) github_path = temporary_path / "github_path" github_path.touch() environment = os.environ.copy() environment.update( PATH=f"{fake_bin}:{environment['PATH']}", RUNNER_TEMP=temporary_directory, GITHUB_PATH=str(github_path), HUGO_VERSION=EXPECTED_HUGO_VERSION, HUGO_SHA256=EXPECTED_HUGO_SHA256, ) rejected = subprocess.run( ["bash", "-c", installation], env=environment, capture_output=True, text=True, check=False, ) self.assertNotEqual(0, rejected.returncode) self.assertIn("FAILED", rejected.stdout) self.assertEqual("", github_path.read_text()) self.assertFalse((temporary_path / f"hugo-{EXPECTED_HUGO_VERSION}" / "hugo").exists()) def test_infrastructure_scaffold_uses_expected_hugo_version(self) -> None: bootstrap = read( INFRASTRUCTURE_ROOT / "ansible" / "tasks" / "bootstrap-hugo-site-from-deployment.yml" ) self.assertIn(f'HUGO_VERSION: "{EXPECTED_HUGO_VERSION}"', bootstrap) self.assertNotRegex(bootstrap, r'HUGO_VERSION: "(?!0\.165\.0)[^"]+"') def test_readme_documents_supported_sites_and_version_guarantee(self) -> None: readme = read(REPOSITORY_ROOT / "README.md") expected_sites = ( "neilborromeo.com", "boholtoday.com", "boholcentral.com", "komsysnet.com", "thefreelancemastery.com", ) documented_sites = re.findall(r"^- `([^`]+)`$", readme, re.MULTILINE) self.assertEqual(list(expected_sites), documented_sites[: len(expected_sites)]) self.assertIn("Hugo Extended 0.165.0", readme) self.assertIn("fails before deployment", readme) if __name__ == "__main__": unittest.main()