from __future__ import annotations import importlib.util import json from pathlib import Path import sys import tempfile import unittest from unittest.mock import patch META_ROOT = Path(__file__).resolve().parents[1] SCRIPT = META_ROOT / "tools" / "repo" / "bootstrap-repositories.py" def load_bootstrap_module(): spec = importlib.util.spec_from_file_location("bootstrap_repositories", SCRIPT) if spec is None or spec.loader is None: raise RuntimeError(f"Could not load {SCRIPT}") module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module spec.loader.exec_module(module) return module class RepositoryBootstrapTests(unittest.TestCase): def test_public_https_transport_rewrites_registered_gitea_remotes(self) -> None: bootstrap = load_bootstrap_module() self.assertEqual( "https://git.add-ideas.de/add-ideas/govoplan-core.git", bootstrap.clone_remote( "git@git.add-ideas.de:add-ideas/govoplan-core.git", transport=bootstrap.PUBLIC_HTTPS_TRANSPORT, ), ) self.assertEqual( "https://git.add-ideas.de/add-ideas/govoplan-core.git", bootstrap.clone_remote( "ssh://git@git.add-ideas.de/add-ideas/govoplan-core.git", transport=bootstrap.PUBLIC_HTTPS_TRANSPORT, ), ) self.assertEqual( "https://git.add-ideas.de/add-ideas/govoplan-core.git", bootstrap.clone_remote( "https://git.add-ideas.de/add-ideas/govoplan-core.git", transport=bootstrap.PUBLIC_HTTPS_TRANSPORT, ), ) def test_registered_transport_preserves_the_manifest_remote(self) -> None: bootstrap = load_bootstrap_module() remote = "git@example.test:private/repository.git" self.assertEqual( remote, bootstrap.clone_remote( remote, transport=bootstrap.REGISTERED_TRANSPORT, ), ) def test_public_https_transport_fails_closed_for_other_hosts(self) -> None: bootstrap = load_bootstrap_module() unsafe_remotes = ( "git@example.test:add-ideas/govoplan-core.git", "https://token@git.add-ideas.de/add-ideas/govoplan-core.git", "https://git.add-ideas.de:443/add-ideas/govoplan-core.git", "https://git.add-ideas.de/add-ideas/../govoplan-core.git", "https://git.add-ideas.de/add-ideas/govoplan-core.git?ref=main", "ssh://root@git.add-ideas.de/add-ideas/govoplan-core.git", ) for remote in unsafe_remotes: with self.subTest(remote=remote), self.assertRaises(ValueError): bootstrap.clone_remote( remote, transport=bootstrap.PUBLIC_HTTPS_TRANSPORT, ) def test_main_clones_missing_repositories_over_public_https(self) -> None: bootstrap = load_bootstrap_module() with tempfile.TemporaryDirectory(prefix="govoplan-bootstrap-") as directory: root = Path(directory) parent = root / "checkouts" parent.mkdir() root.joinpath("repositories.json").write_text( json.dumps( { "default_parent": str(parent), "repositories": [ { "name": "govoplan-core", "path": "govoplan-core", "remote": ( "git@git.add-ideas.de:" "add-ideas/govoplan-core.git" ), } ], } ), encoding="utf-8", ) with ( patch.object(bootstrap, "ROOT", root), patch.object(bootstrap.subprocess, "run") as runner, ): status = bootstrap.main( [ "--parent", str(parent), "--transport", bootstrap.PUBLIC_HTTPS_TRANSPORT, ] ) self.assertEqual(0, status) runner.assert_called_once_with( [ "git", "-c", "credential.helper=", "clone", "--", "https://git.add-ideas.de/add-ideas/govoplan-core.git", str(parent / "govoplan-core"), ], check=True, env=runner.call_args.kwargs["env"], ) environment = runner.call_args.kwargs["env"] self.assertEqual("/bin/false", environment["GIT_ASKPASS"]) self.assertEqual("0", environment["GIT_TERMINAL_PROMPT"]) def test_main_validates_every_remote_before_cloning(self) -> None: bootstrap = load_bootstrap_module() with tempfile.TemporaryDirectory(prefix="govoplan-bootstrap-") as directory: root = Path(directory) parent = root / "checkouts" parent.mkdir() root.joinpath("repositories.json").write_text( json.dumps( { "default_parent": str(parent), "repositories": [ { "name": "govoplan-core", "path": "govoplan-core", "remote": ( "git@git.add-ideas.de:" "add-ideas/govoplan-core.git" ), }, { "name": "unsafe", "path": "unsafe", "remote": "git@example.test:private/unsafe.git", }, ], } ), encoding="utf-8", ) with ( patch.object(bootstrap, "ROOT", root), patch.object(bootstrap.subprocess, "run") as runner, self.assertRaises(ValueError), ): bootstrap.main( [ "--parent", str(parent), "--transport", bootstrap.PUBLIC_HTTPS_TRANSPORT, ] ) runner.assert_not_called() def test_main_preserves_an_explicit_private_repository_transport(self) -> None: bootstrap = load_bootstrap_module() with tempfile.TemporaryDirectory(prefix="govoplan-bootstrap-") as directory: root = Path(directory) parent = root / "checkouts" parent.mkdir() root.joinpath("repositories.json").write_text( json.dumps( { "default_parent": str(parent), "repositories": [ { "name": "website", "path": "website", "remote": ( "git@git.add-ideas.de:" "add-ideas/addideas-govoplan-website.git" ), "bootstrap_transport": ( bootstrap.REGISTERED_TRANSPORT ), } ], } ), encoding="utf-8", ) with ( patch.object(bootstrap, "ROOT", root), patch.object(bootstrap.subprocess, "run") as runner, ): status = bootstrap.main( [ "--parent", str(parent), "--transport", bootstrap.PUBLIC_HTTPS_TRANSPORT, ] ) self.assertEqual(0, status) command = runner.call_args.args[0] self.assertEqual( "git@git.add-ideas.de:add-ideas/addideas-govoplan-website.git", command[-2], ) def test_main_limits_bootstrap_to_selected_repositories(self) -> None: bootstrap = load_bootstrap_module() with tempfile.TemporaryDirectory(prefix="govoplan-bootstrap-") as directory: root = Path(directory) parent = root / "checkouts" parent.mkdir() root.joinpath("repositories.json").write_text( json.dumps( { "default_parent": str(parent), "repositories": [ { "name": name, "path": name, "remote": ( "git@git.add-ideas.de:add-ideas/" f"{name}.git" ), } for name in ("govoplan-core", "govoplan-poll") ], } ), encoding="utf-8", ) for repository_filter in ( ["--repo", "govoplan-core"], ["--exclude-repo", "govoplan-poll"], ): with ( self.subTest(repository_filter=repository_filter), patch.object(bootstrap, "ROOT", root), patch.object(bootstrap.subprocess, "run") as runner, ): status = bootstrap.main( [ "--parent", str(parent), "--transport", bootstrap.PUBLIC_HTTPS_TRANSPORT, *repository_filter, ] ) self.assertEqual(0, status) runner.assert_called_once() self.assertEqual( "https://git.add-ideas.de/add-ideas/govoplan-core.git", runner.call_args.args[0][-2], ) def test_main_rejects_unknown_or_conflicting_repository_filters(self) -> None: bootstrap = load_bootstrap_module() with tempfile.TemporaryDirectory(prefix="govoplan-bootstrap-") as directory: root = Path(directory) parent = root / "checkouts" parent.mkdir() root.joinpath("repositories.json").write_text( json.dumps( { "default_parent": str(parent), "repositories": [ { "name": "govoplan-core", "path": "govoplan-core", "remote": ( "git@git.add-ideas.de:" "add-ideas/govoplan-core.git" ), } ], } ), encoding="utf-8", ) with ( patch.object(bootstrap, "ROOT", root), patch.object(bootstrap.subprocess, "run") as runner, ): with self.assertRaisesRegex(ValueError, "unknown registered"): bootstrap.main(["--repo", "govoplan-missing"]) with self.assertRaisesRegex(ValueError, "selected and excluded"): bootstrap.main( [ "--repo", "govoplan-core", "--exclude-repo", "govoplan-core", ] ) runner.assert_not_called() def test_check_reports_missing_without_cloning(self) -> None: bootstrap = load_bootstrap_module() with tempfile.TemporaryDirectory(prefix="govoplan-bootstrap-") as directory: root = Path(directory) parent = root / "checkouts" parent.mkdir() root.joinpath("repositories.json").write_text( json.dumps( { "default_parent": str(parent), "repositories": [ { "name": "govoplan-core", "path": "govoplan-core", "remote": ( "git@git.add-ideas.de:" "add-ideas/govoplan-core.git" ), } ], } ), encoding="utf-8", ) with ( patch.object(bootstrap, "ROOT", root), patch.object(bootstrap.subprocess, "run") as runner, ): status = bootstrap.main( [ "--check", "--parent", str(parent), "--transport", bootstrap.PUBLIC_HTTPS_TRANSPORT, ] ) self.assertEqual(1, status) runner.assert_not_called() if __name__ == "__main__": unittest.main()