365 lines
14 KiB
Python
365 lines
14 KiB
Python
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/GovOPlaN/govoplan-core.git",
|
|
bootstrap.clone_remote(
|
|
"git@git.add-ideas.de:GovOPlaN/govoplan-core.git",
|
|
transport=bootstrap.PUBLIC_HTTPS_TRANSPORT,
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
"https://git.add-ideas.de/GovOPlaN/govoplan-core.git",
|
|
bootstrap.clone_remote(
|
|
"ssh://git@git.add-ideas.de/GovOPlaN/govoplan-core.git",
|
|
transport=bootstrap.PUBLIC_HTTPS_TRANSPORT,
|
|
),
|
|
)
|
|
self.assertEqual(
|
|
"https://git.add-ideas.de/GovOPlaN/govoplan-core.git",
|
|
bootstrap.clone_remote(
|
|
"https://git.add-ideas.de/GovOPlaN/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:GovOPlaN/govoplan-core.git",
|
|
"https://token@git.add-ideas.de/GovOPlaN/govoplan-core.git",
|
|
"https://git.add-ideas.de:443/GovOPlaN/govoplan-core.git",
|
|
"https://git.add-ideas.de/add-ideas/../govoplan-core.git",
|
|
"https://git.add-ideas.de/GovOPlaN/govoplan-core.git?ref=main",
|
|
"ssh://root@git.add-ideas.de/GovOPlaN/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:"
|
|
"GovOPlaN/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/GovOPlaN/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:"
|
|
"GovOPlaN/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:GovOPlaN/"
|
|
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/GovOPlaN/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:"
|
|
"GovOPlaN/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:"
|
|
"GovOPlaN/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()
|