mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
A submodule on the repo's host was allowed unpinned, on any port. The owner of the repo's domain could serve the clone from a public address, then answer the submodule's lookup with an internal address (DNS rebinding), or point it at another port on that name. The repo's host is now resolved once per download, and every fetch (ls-remote, clone, fetch, submodule update) is pinned to that answer. A submodule on the repo's host *and port* is fetched from those same addresses, still allowing a LAN forge the user chose; another port on that name is checked like any other host. Tests no longer use real DNS. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
104 lines
4.2 KiB
Python
104 lines
4.2 KiB
Python
import os
|
|
import pathlib
|
|
import subprocess
|
|
from typing import Callable
|
|
|
|
import pytest
|
|
from mock import mock
|
|
|
|
from modules.repos import lib
|
|
from modules.repos.downloader import GitDownloader
|
|
from wrolpi.downloader import DownloadManager
|
|
|
|
# Commits made by the tests have a fixed identity and do not read the developer's git config.
|
|
TEST_GIT_ENV = dict(
|
|
GIT_AUTHOR_NAME='WROLPi Test', GIT_AUTHOR_EMAIL='test@example.com',
|
|
GIT_COMMITTER_NAME='WROLPi Test', GIT_COMMITTER_EMAIL='test@example.com',
|
|
GIT_CONFIG_NOSYSTEM='1', GIT_CONFIG_GLOBAL=os.devnull,
|
|
)
|
|
|
|
|
|
class GitRemote:
|
|
"""A local repo which the Repos module sees as `https://git.example.com/<owner>/<name>`."""
|
|
|
|
def __init__(self, path: pathlib.Path, url: str):
|
|
self.path = path
|
|
self.url = url
|
|
|
|
def git(self, *args: str) -> str:
|
|
env = dict(os.environ, **TEST_GIT_ENV)
|
|
result = subprocess.run(['git', *args], cwd=self.path, env=env, check=True, capture_output=True)
|
|
return result.stdout.decode().strip()
|
|
|
|
def commit(self, files: dict, message: str = 'Commit') -> str:
|
|
"""Write `files` ({relative path: contents}), commit them, return the commit's sha."""
|
|
for name, contents in files.items():
|
|
file = self.path / name
|
|
file.parent.mkdir(parents=True, exist_ok=True)
|
|
file.write_text(contents)
|
|
self.git('add', '--all')
|
|
self.git('commit', '--quiet', '-m', message)
|
|
return self.git('rev-parse', 'HEAD')
|
|
|
|
@property
|
|
def head(self) -> str:
|
|
return self.git('rev-parse', 'HEAD')
|
|
|
|
def add_submodule(self, path: str, submodule: 'GitRemote', url: str = None) -> str:
|
|
"""Add `submodule` (at its current commit) at `path`, and commit it. `url` overrides the submodule's URL."""
|
|
gitmodules = self.path / '.gitmodules'
|
|
existing = gitmodules.read_text() if gitmodules.exists() else ''
|
|
gitmodules.write_text(f'{existing}[submodule "{path}"]\n\tpath = {path}\n\turl = {url or submodule.url}\n')
|
|
self.git('update-index', '--add', '--cacheinfo', f'160000,{submodule.head},{path}')
|
|
self.git('add', '.gitmodules')
|
|
self.git('commit', '--quiet', '-m', f'Add submodule {path}')
|
|
return self.head
|
|
|
|
|
|
@pytest.fixture
|
|
def git_remote_factory(tmp_path_factory) -> Callable[..., GitRemote]:
|
|
"""Create local git repos which are cloned over `https://git.example.com/...` URLs.
|
|
|
|
git's `url.<base>.insteadOf` rewrites the https URL to the local path, and `file://` is allowed for the test,
|
|
so the Repos module runs the same commands it runs in production."""
|
|
remotes = dict()
|
|
# Outside the media directory (which is `tmp_path`).
|
|
remotes_directory = tmp_path_factory.mktemp('remotes')
|
|
|
|
def git_env():
|
|
env = original_git_env()
|
|
env['GIT_CONFIG_COUNT'] = str(len(remotes))
|
|
for idx, (url, path) in enumerate(remotes.items()):
|
|
env[f'GIT_CONFIG_KEY_{idx}'] = f'url.file://{path}.insteadOf'
|
|
env[f'GIT_CONFIG_VALUE_{idx}'] = url
|
|
return env
|
|
|
|
original_git_env = lib.git_env
|
|
|
|
def factory(name: str = 'example', owner: str = 'owner', branch: str = 'main',
|
|
files: dict = None) -> GitRemote:
|
|
path = remotes_directory / owner / name
|
|
path.mkdir(parents=True)
|
|
url = f'https://git.example.com/{owner}/{name}'
|
|
remote = GitRemote(path, url)
|
|
remote.git('init', '--quiet', f'--initial-branch={branch}')
|
|
remote.commit(files or {'README.md': f'# {name}\n\nThe {name} repo.\n'}, 'Initial commit')
|
|
remotes[url] = path
|
|
return remote
|
|
|
|
def resolve_host_addresses(host):
|
|
# The remotes' host has a fixed public address; no test uses real DNS.
|
|
if host == 'git.example.com':
|
|
return ['93.184.215.14']
|
|
raise OSError(f'Unknown host {host}')
|
|
|
|
with mock.patch('modules.repos.lib.ALLOWED_PROTOCOLS', ('https', 'file')), \
|
|
mock.patch('modules.repos.downloader.git_env', git_env), \
|
|
mock.patch('modules.repos.lib.resolve_host_addresses', resolve_host_addresses):
|
|
yield factory
|
|
|
|
|
|
@pytest.fixture
|
|
def repos_download_manager(test_download_manager) -> DownloadManager:
|
|
test_download_manager.register_downloader(GitDownloader())
|
|
yield test_download_manager
|