Add Arch-based Docker test setup, shallow clone mode support and pkgmgr tests (see ChatGPT conversation: https://chatgpt.com/share/693052a1-edd0-800f-a9d6-c154b8e7d8e0)
This commit is contained in:
25
.github/workflows/test.yml
vendored
Normal file
25
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
name: Test package-manager
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
- master
|
||||||
|
- develop
|
||||||
|
- "*"
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 30
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Show Docker version
|
||||||
|
run: docker version
|
||||||
|
|
||||||
|
- name: Run tests via make (builds Docker image and runs unit + integration tests)
|
||||||
|
run: make test
|
||||||
35
Dockerfile
35
Dockerfile
@@ -1,31 +1,40 @@
|
|||||||
FROM python:3.11-slim
|
FROM archlinux:latest
|
||||||
|
|
||||||
# Install system dependencies (make, pip) as per README
|
# Update system and install core tooling
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
RUN pacman -Syu --noconfirm \
|
||||||
|
&& pacman -S --noconfirm --needed \
|
||||||
git \
|
git \
|
||||||
make \
|
make \
|
||||||
python3-pip \
|
sudo \
|
||||||
python3-venv \
|
python \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
python-pip \
|
||||||
|
python-virtualenv \
|
||||||
|
python-setuptools \
|
||||||
|
python-wheel \
|
||||||
|
&& pacman -Scc --noconfirm
|
||||||
|
|
||||||
# Ensure local bin is in PATH (for aliases) as per README
|
# Ensure local bin is in PATH (for pkgmgr links)
|
||||||
ENV PATH="/root/.local/bin:$PATH"
|
ENV PATH="/root/.local/bin:$PATH"
|
||||||
|
|
||||||
# Create and activate a virtual environment
|
# Create virtual environment
|
||||||
ENV VIRTUAL_ENV=/root/.venvs/pkgmgr
|
ENV VIRTUAL_ENV=/root/.venvs/pkgmgr
|
||||||
RUN python3 -m venv $VIRTUAL_ENV
|
RUN python -m venv $VIRTUAL_ENV
|
||||||
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
ENV PATH="$VIRTUAL_ENV/bin:$PATH"
|
||||||
|
|
||||||
# Copy local package-manager source into the image
|
# Working directory for the package-manager project
|
||||||
WORKDIR /root/Repositories/github.com/kevinveenbirkenbach/package-manager
|
WORKDIR /root/Repositories/github.com/kevinveenbirkenbach/package-manager
|
||||||
|
|
||||||
|
# Copy local package-manager source into container
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
# Install Python dependencies and set up the tool non-interactively
|
# Install Python dependencies and register pkgmgr inside the venv
|
||||||
RUN pip install --upgrade pip \
|
RUN pip install --upgrade pip \
|
||||||
&& pip install PyYAML \
|
&& pip install PyYAML \
|
||||||
&& chmod +x main.py \
|
&& chmod +x main.py \
|
||||||
&& python main.py install package-manager --quiet --clone-mode https
|
&& python main.py install package-manager --quiet --clone-mode shallow --no-verification
|
||||||
|
|
||||||
|
# Copy again to allow rebuild-based code changes
|
||||||
|
COPY . .
|
||||||
|
|
||||||
# Default entrypoint for pkgmgr
|
|
||||||
ENTRYPOINT ["pkgmgr"]
|
ENTRYPOINT ["pkgmgr"]
|
||||||
CMD ["--help"]
|
CMD ["--help"]
|
||||||
|
|||||||
4
Makefile
4
Makefile
@@ -3,6 +3,10 @@
|
|||||||
setup: install
|
setup: install
|
||||||
@python3 main.py install
|
@python3 main.py install
|
||||||
|
|
||||||
|
test:
|
||||||
|
docker build -t package-manager-test .
|
||||||
|
docker run --rm --entrypoint python package-manager-test -m unittest discover -s tests -p "test_*.py"
|
||||||
|
|
||||||
install:
|
install:
|
||||||
@echo "Making 'main.py' executable..."
|
@echo "Making 'main.py' executable..."
|
||||||
@chmod +x main.py
|
@chmod +x main.py
|
||||||
|
|||||||
47
main.py
47
main.py
@@ -112,10 +112,29 @@ For detailed help on each command, use:
|
|||||||
|
|
||||||
def add_install_update_arguments(subparser):
|
def add_install_update_arguments(subparser):
|
||||||
add_identifier_arguments(subparser)
|
add_identifier_arguments(subparser)
|
||||||
subparser.add_argument("-q", "--quiet", action="store_true", help="Suppress warnings and info messages")
|
subparser.add_argument(
|
||||||
subparser.add_argument("--no-verification", action="store_true", default=False, help="Disable verification via commit/gpg")
|
"-q",
|
||||||
subparser.add_argument("--dependencies", action="store_true", help="Also pull and update dependencies")
|
"--quiet",
|
||||||
subparser.add_argument("--clone-mode", choices=["ssh", "https"], default="ssh", help="Specify the clone mode (default: ssh)")
|
action="store_true",
|
||||||
|
help="Suppress warnings and info messages",
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--no-verification",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Disable verification via commit/gpg",
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--dependencies",
|
||||||
|
action="store_true",
|
||||||
|
help="Also pull and update dependencies",
|
||||||
|
)
|
||||||
|
subparser.add_argument(
|
||||||
|
"--clone-mode",
|
||||||
|
choices=["ssh", "https", "shallow"],
|
||||||
|
default="ssh",
|
||||||
|
help="Specify the clone mode: ssh, https, or shallow (HTTPS shallow clone; default: ssh)",
|
||||||
|
)
|
||||||
|
|
||||||
install_parser = subparsers.add_parser("install", help="Setup repository/repositories alias links to executables")
|
install_parser = subparsers.add_parser("install", help="Setup repository/repositories alias links to executables")
|
||||||
add_install_update_arguments(install_parser)
|
add_install_update_arguments(install_parser)
|
||||||
@@ -213,10 +232,20 @@ For detailed help on each command, use:
|
|||||||
description=f"Executes '{command} {subcommand}' for the identified repos.\nTo recieve more help execute '{command} {subcommand} --help'",
|
description=f"Executes '{command} {subcommand}' for the identified repos.\nTo recieve more help execute '{command} {subcommand} --help'",
|
||||||
formatter_class=argparse.RawTextHelpFormatter
|
formatter_class=argparse.RawTextHelpFormatter
|
||||||
)
|
)
|
||||||
if subcommand in ["pull","clone"]:
|
if subcommand in ["pull", "clone"]:
|
||||||
proxy_command_parsers[f"{command}_{subcommand}"].add_argument("--no-verification", action="store_true", default=False, help="Disable verification via commit/gpg")
|
proxy_command_parsers[f"{command}_{subcommand}"].add_argument(
|
||||||
|
"--no-verification",
|
||||||
|
action="store_true",
|
||||||
|
default=False,
|
||||||
|
help="Disable verification via commit/gpg",
|
||||||
|
)
|
||||||
if subcommand == "clone":
|
if subcommand == "clone":
|
||||||
proxy_command_parsers[f"{command}_{subcommand}"].add_argument("--clone-mode", choices=["ssh", "https"], default="ssh", help="Specify the clone mode (default: ssh)")
|
proxy_command_parsers[f"{command}_{subcommand}"].add_argument(
|
||||||
|
"--clone-mode",
|
||||||
|
choices=["ssh", "https", "shallow"],
|
||||||
|
default="ssh",
|
||||||
|
help="Specify the clone mode: ssh, https, or shallow (HTTPS shallow clone; default: ssh)",
|
||||||
|
)
|
||||||
add_identifier_arguments(proxy_command_parsers[f"{command}_{subcommand}"])
|
add_identifier_arguments(proxy_command_parsers[f"{command}_{subcommand}"])
|
||||||
|
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -331,7 +360,7 @@ For detailed help on each command, use:
|
|||||||
status_repos(selected,REPOSITORIES_BASE_DIR, ALL_REPOSITORIES, args.extra_args, list_only=args.list, system_status=args.system, preview=args.preview)
|
status_repos(selected,REPOSITORIES_BASE_DIR, ALL_REPOSITORIES, args.extra_args, list_only=args.list, system_status=args.system, preview=args.preview)
|
||||||
elif args.command == "explore":
|
elif args.command == "explore":
|
||||||
for repository in selected:
|
for repository in selected:
|
||||||
run_command(f"nautilus {repository["directory"]} & disown")
|
run_command(f"nautilus {repository['directory']} & disown")
|
||||||
elif args.command == "code":
|
elif args.command == "code":
|
||||||
if not selected:
|
if not selected:
|
||||||
print("No repositories selected.")
|
print("No repositories selected.")
|
||||||
@@ -371,7 +400,7 @@ For detailed help on each command, use:
|
|||||||
# Join the provided shell command parts into one string.
|
# Join the provided shell command parts into one string.
|
||||||
command_to_run = " ".join(args.shell_command)
|
command_to_run = " ".join(args.shell_command)
|
||||||
for repository in selected:
|
for repository in selected:
|
||||||
print(f"Executing in '{repository["directory"]}': {command_to_run}")
|
print(f"Executing in '{repository['directory']}': {command_to_run}")
|
||||||
run_command(command_to_run, cwd=repository["directory"], preview=args.preview)
|
run_command(command_to_run, cwd=repository["directory"], preview=args.preview)
|
||||||
elif args.command == "config":
|
elif args.command == "config":
|
||||||
if args.subcommand == "show":
|
if args.subcommand == "show":
|
||||||
|
|||||||
@@ -22,25 +22,48 @@ def clone_repos(
|
|||||||
parent_dir = os.path.dirname(repo_dir)
|
parent_dir = os.path.dirname(repo_dir)
|
||||||
os.makedirs(parent_dir, exist_ok=True)
|
os.makedirs(parent_dir, exist_ok=True)
|
||||||
# Build clone URL based on the clone_mode
|
# Build clone URL based on the clone_mode
|
||||||
|
# Build clone URL based on the clone_mode
|
||||||
if clone_mode == "ssh":
|
if clone_mode == "ssh":
|
||||||
clone_url = f"git@{repo.get('provider')}:{repo.get('account')}/{repo.get('repository')}.git"
|
clone_url = (
|
||||||
elif clone_mode == "https":
|
f"git@{repo.get('provider')}:"
|
||||||
|
f"{repo.get('account')}/"
|
||||||
|
f"{repo.get('repository')}.git"
|
||||||
|
)
|
||||||
|
elif clone_mode in ("https", "shallow"):
|
||||||
# Use replacement if defined, otherwise construct from provider/account/repository
|
# Use replacement if defined, otherwise construct from provider/account/repository
|
||||||
if repo.get("replacement"):
|
if repo.get("replacement"):
|
||||||
clone_url = f"https://{repo.get('replacement')}.git"
|
clone_url = f"https://{repo.get('replacement')}.git"
|
||||||
else:
|
else:
|
||||||
clone_url = f"https://{repo.get('provider')}/{repo.get('account')}/{repo.get('repository')}.git"
|
clone_url = (
|
||||||
|
f"https://{repo.get('provider')}/"
|
||||||
|
f"{repo.get('account')}/"
|
||||||
|
f"{repo.get('repository')}.git"
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
print(f"Unknown clone mode '{clone_mode}'. Aborting clone for {repo_identifier}.")
|
print(f"Unknown clone mode '{clone_mode}'. Aborting clone for {repo_identifier}.")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
print(f"[INFO] Attempting to clone '{repo_identifier}' using {clone_mode.upper()} from {clone_url} into '{repo_dir}'.")
|
# Build base clone command
|
||||||
|
base_clone_cmd = "git clone"
|
||||||
|
if clone_mode == "shallow":
|
||||||
|
# Shallow clone: only latest state via HTTPS, no full history
|
||||||
|
base_clone_cmd += " --depth 1 --single-branch"
|
||||||
|
|
||||||
|
mode_label = "HTTPS (shallow)" if clone_mode == "shallow" else clone_mode.upper()
|
||||||
|
print(
|
||||||
|
f"[INFO] Attempting to clone '{repo_identifier}' using {mode_label} "
|
||||||
|
f"from {clone_url} into '{repo_dir}'."
|
||||||
|
)
|
||||||
|
|
||||||
if preview:
|
if preview:
|
||||||
print(f"[Preview] Would run: git clone {clone_url} {repo_dir} in {parent_dir}")
|
print(f"[Preview] Would run: {base_clone_cmd} {clone_url} {repo_dir} in {parent_dir}")
|
||||||
result = subprocess.CompletedProcess(args=[], returncode=0)
|
result = subprocess.CompletedProcess(args=[], returncode=0)
|
||||||
else:
|
else:
|
||||||
result = subprocess.run(f"git clone {clone_url} {repo_dir}", cwd=parent_dir, shell=True)
|
result = subprocess.run(
|
||||||
|
f"{base_clone_cmd} {clone_url} {repo_dir}",
|
||||||
|
cwd=parent_dir,
|
||||||
|
shell=True,
|
||||||
|
)
|
||||||
|
|
||||||
if result.returncode != 0:
|
if result.returncode != 0:
|
||||||
# Only offer fallback if the original mode was SSH.
|
# Only offer fallback if the original mode was SSH.
|
||||||
|
|||||||
168
tests/test_clone_repos.py
Normal file
168
tests/test_clone_repos.py
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
# tests/test_clone_repos.py
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch, MagicMock
|
||||||
|
|
||||||
|
from pkgmgr.clone_repos import clone_repos
|
||||||
|
|
||||||
|
|
||||||
|
class TestCloneRepos(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.repo = {
|
||||||
|
"provider": "github.com",
|
||||||
|
"account": "user",
|
||||||
|
"repository": "repo",
|
||||||
|
}
|
||||||
|
self.selected = [self.repo]
|
||||||
|
self.base_dir = "/tmp/repos"
|
||||||
|
self.all_repos = self.selected
|
||||||
|
|
||||||
|
@patch("pkgmgr.clone_repos.verify_repository")
|
||||||
|
@patch("pkgmgr.clone_repos.subprocess.run")
|
||||||
|
@patch("pkgmgr.clone_repos.os.makedirs")
|
||||||
|
@patch("pkgmgr.clone_repos.os.path.exists")
|
||||||
|
@patch("pkgmgr.clone_repos.get_repo_dir")
|
||||||
|
@patch("pkgmgr.clone_repos.get_repo_identifier")
|
||||||
|
def test_clone_ssh_mode_uses_ssh_url(
|
||||||
|
self,
|
||||||
|
mock_get_repo_identifier,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_exists,
|
||||||
|
mock_makedirs,
|
||||||
|
mock_run,
|
||||||
|
mock_verify,
|
||||||
|
):
|
||||||
|
mock_get_repo_identifier.return_value = "github.com/user/repo"
|
||||||
|
mock_get_repo_dir.return_value = "/tmp/repos/user/repo"
|
||||||
|
mock_exists.return_value = False
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
mock_verify.return_value = (True, [], "hash", "key")
|
||||||
|
|
||||||
|
clone_repos(
|
||||||
|
self.selected,
|
||||||
|
self.base_dir,
|
||||||
|
self.all_repos,
|
||||||
|
preview=False,
|
||||||
|
no_verification=True,
|
||||||
|
clone_mode="ssh",
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_run.assert_called_once()
|
||||||
|
# subprocess.run wird mit positional args aufgerufen
|
||||||
|
cmd = mock_run.call_args[0][0]
|
||||||
|
cwd = mock_run.call_args[1]["cwd"]
|
||||||
|
|
||||||
|
self.assertIn("git clone", cmd)
|
||||||
|
self.assertIn("git@github.com:user/repo.git", cmd)
|
||||||
|
self.assertEqual(cwd, "/tmp/repos/user")
|
||||||
|
|
||||||
|
@patch("pkgmgr.clone_repos.verify_repository")
|
||||||
|
@patch("pkgmgr.clone_repos.subprocess.run")
|
||||||
|
@patch("pkgmgr.clone_repos.os.makedirs")
|
||||||
|
@patch("pkgmgr.clone_repos.os.path.exists")
|
||||||
|
@patch("pkgmgr.clone_repos.get_repo_dir")
|
||||||
|
@patch("pkgmgr.clone_repos.get_repo_identifier")
|
||||||
|
def test_clone_https_mode_uses_https_url(
|
||||||
|
self,
|
||||||
|
mock_get_repo_identifier,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_exists,
|
||||||
|
mock_makedirs,
|
||||||
|
mock_run,
|
||||||
|
mock_verify,
|
||||||
|
):
|
||||||
|
mock_get_repo_identifier.return_value = "github.com/user/repo"
|
||||||
|
mock_get_repo_dir.return_value = "/tmp/repos/user/repo"
|
||||||
|
mock_exists.return_value = False
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
mock_verify.return_value = (True, [], "hash", "key")
|
||||||
|
|
||||||
|
clone_repos(
|
||||||
|
self.selected,
|
||||||
|
self.base_dir,
|
||||||
|
self.all_repos,
|
||||||
|
preview=False,
|
||||||
|
no_verification=True,
|
||||||
|
clone_mode="https",
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_run.assert_called_once()
|
||||||
|
cmd = mock_run.call_args[0][0]
|
||||||
|
cwd = mock_run.call_args[1]["cwd"]
|
||||||
|
|
||||||
|
self.assertIn("git clone", cmd)
|
||||||
|
self.assertIn("https://github.com/user/repo.git", cmd)
|
||||||
|
self.assertEqual(cwd, "/tmp/repos/user")
|
||||||
|
|
||||||
|
@patch("pkgmgr.clone_repos.verify_repository")
|
||||||
|
@patch("pkgmgr.clone_repos.subprocess.run")
|
||||||
|
@patch("pkgmgr.clone_repos.os.makedirs")
|
||||||
|
@patch("pkgmgr.clone_repos.os.path.exists")
|
||||||
|
@patch("pkgmgr.clone_repos.get_repo_dir")
|
||||||
|
@patch("pkgmgr.clone_repos.get_repo_identifier")
|
||||||
|
def test_clone_shallow_mode_uses_https_with_depth(
|
||||||
|
self,
|
||||||
|
mock_get_repo_identifier,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_exists,
|
||||||
|
mock_makedirs,
|
||||||
|
mock_run,
|
||||||
|
mock_verify,
|
||||||
|
):
|
||||||
|
mock_get_repo_identifier.return_value = "github.com/user/repo"
|
||||||
|
mock_get_repo_dir.return_value = "/tmp/repos/user/repo"
|
||||||
|
mock_exists.return_value = False
|
||||||
|
mock_run.return_value = MagicMock(returncode=0)
|
||||||
|
mock_verify.return_value = (True, [], "hash", "key")
|
||||||
|
|
||||||
|
clone_repos(
|
||||||
|
self.selected,
|
||||||
|
self.base_dir,
|
||||||
|
self.all_repos,
|
||||||
|
preview=False,
|
||||||
|
no_verification=True,
|
||||||
|
clone_mode="shallow",
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_run.assert_called_once()
|
||||||
|
cmd = mock_run.call_args[0][0]
|
||||||
|
cwd = mock_run.call_args[1]["cwd"]
|
||||||
|
|
||||||
|
self.assertIn("git clone --depth 1 --single-branch", cmd)
|
||||||
|
self.assertIn("https://github.com/user/repo.git", cmd)
|
||||||
|
self.assertEqual(cwd, "/tmp/repos/user")
|
||||||
|
|
||||||
|
@patch("pkgmgr.clone_repos.verify_repository")
|
||||||
|
@patch("pkgmgr.clone_repos.subprocess.run")
|
||||||
|
@patch("pkgmgr.clone_repos.os.makedirs")
|
||||||
|
@patch("pkgmgr.clone_repos.os.path.exists")
|
||||||
|
@patch("pkgmgr.clone_repos.get_repo_dir")
|
||||||
|
@patch("pkgmgr.clone_repos.get_repo_identifier")
|
||||||
|
def test_preview_mode_does_not_call_subprocess_run(
|
||||||
|
self,
|
||||||
|
mock_get_repo_identifier,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_exists,
|
||||||
|
mock_makedirs,
|
||||||
|
mock_run,
|
||||||
|
mock_verify,
|
||||||
|
):
|
||||||
|
mock_get_repo_identifier.return_value = "github.com/user/repo"
|
||||||
|
mock_get_repo_dir.return_value = "/tmp/repos/user/repo"
|
||||||
|
mock_exists.return_value = False
|
||||||
|
mock_verify.return_value = (True, [], "hash", "key")
|
||||||
|
|
||||||
|
clone_repos(
|
||||||
|
self.selected,
|
||||||
|
self.base_dir,
|
||||||
|
self.all_repos,
|
||||||
|
preview=True,
|
||||||
|
no_verification=True,
|
||||||
|
clone_mode="shallow",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Im Preview-Modus sollte subprocess.run nicht aufgerufen werden
|
||||||
|
mock_run.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
129
tests/test_install_repos.py
Normal file
129
tests/test_install_repos.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
# tests/test_install_repos.py
|
||||||
|
import os
|
||||||
|
import unittest
|
||||||
|
from unittest.mock import patch, MagicMock, mock_open
|
||||||
|
|
||||||
|
from pkgmgr.install_repos import install_repos
|
||||||
|
|
||||||
|
|
||||||
|
class TestInstallRepos(unittest.TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.repo = {
|
||||||
|
"provider": "github.com",
|
||||||
|
"account": "user",
|
||||||
|
"repository": "repo",
|
||||||
|
}
|
||||||
|
self.selected = [self.repo]
|
||||||
|
self.base_dir = "/tmp/repos"
|
||||||
|
self.bin_dir = "/tmp/bin"
|
||||||
|
self.all_repos = self.selected
|
||||||
|
|
||||||
|
@patch("pkgmgr.install_repos.clone_repos")
|
||||||
|
@patch("pkgmgr.install_repos.os.path.exists")
|
||||||
|
@patch("pkgmgr.install_repos.get_repo_dir")
|
||||||
|
@patch("pkgmgr.install_repos.get_repo_identifier")
|
||||||
|
def test_calls_clone_repos_with_clone_mode(
|
||||||
|
self,
|
||||||
|
mock_get_repo_identifier,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_exists,
|
||||||
|
mock_clone_repos,
|
||||||
|
):
|
||||||
|
mock_get_repo_identifier.return_value = "github.com/user/repo"
|
||||||
|
mock_get_repo_dir.return_value = "/tmp/repos/user/repo"
|
||||||
|
# Repo-Verzeichnis existiert nicht -> soll geklont werden
|
||||||
|
mock_exists.return_value = False
|
||||||
|
|
||||||
|
install_repos(
|
||||||
|
self.selected,
|
||||||
|
self.base_dir,
|
||||||
|
self.bin_dir,
|
||||||
|
self.all_repos,
|
||||||
|
no_verification=True,
|
||||||
|
preview=False,
|
||||||
|
quiet=True,
|
||||||
|
clone_mode="shallow",
|
||||||
|
update_dependencies=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
mock_clone_repos.assert_called_once()
|
||||||
|
args, kwargs = mock_clone_repos.call_args
|
||||||
|
# clone_mode ist letztes Argument
|
||||||
|
self.assertEqual(args[-1], "shallow")
|
||||||
|
|
||||||
|
@patch("pkgmgr.install_repos.run_command")
|
||||||
|
@patch("pkgmgr.install_repos.open", new_callable=mock_open, create=True)
|
||||||
|
@patch("pkgmgr.install_repos.yaml.safe_load")
|
||||||
|
@patch("pkgmgr.install_repos.os.path.exists")
|
||||||
|
@patch("pkgmgr.install_repos.create_ink")
|
||||||
|
@patch("pkgmgr.install_repos.verify_repository")
|
||||||
|
@patch("pkgmgr.install_repos.get_repo_dir")
|
||||||
|
@patch("pkgmgr.install_repos.get_repo_identifier")
|
||||||
|
def test_pkgmgr_requirements_propagate_clone_mode(
|
||||||
|
self,
|
||||||
|
mock_get_repo_identifier,
|
||||||
|
mock_get_repo_dir,
|
||||||
|
mock_verify,
|
||||||
|
mock_create_ink,
|
||||||
|
mock_exists,
|
||||||
|
mock_safe_load,
|
||||||
|
mock_open_file,
|
||||||
|
mock_run_command,
|
||||||
|
):
|
||||||
|
mock_get_repo_identifier.return_value = "github.com/user/repo"
|
||||||
|
repo_dir = "/tmp/repos/user/repo"
|
||||||
|
mock_get_repo_dir.return_value = repo_dir
|
||||||
|
|
||||||
|
# exists() muss True für repo_dir & requirements.yml liefern,
|
||||||
|
# sonst werden die Anforderungen nie verarbeitet.
|
||||||
|
def exists_side_effect(path):
|
||||||
|
if path == repo_dir:
|
||||||
|
return True
|
||||||
|
if path == os.path.join(repo_dir, "requirements.yml"):
|
||||||
|
return True
|
||||||
|
# requirements.txt und Makefile sollen "nicht existieren"
|
||||||
|
return False
|
||||||
|
|
||||||
|
mock_exists.side_effect = exists_side_effect
|
||||||
|
|
||||||
|
mock_verify.return_value = (True, [], "hash", "key")
|
||||||
|
|
||||||
|
# requirements.yml enthält pkgmgr-Dependencies
|
||||||
|
mock_safe_load.return_value = {
|
||||||
|
"pkgmgr": ["github.com/other/account/dep"],
|
||||||
|
}
|
||||||
|
|
||||||
|
commands = []
|
||||||
|
|
||||||
|
def run_command_side_effect(cmd, cwd=None, preview=False):
|
||||||
|
commands.append((cmd, cwd, preview))
|
||||||
|
|
||||||
|
mock_run_command.side_effect = run_command_side_effect
|
||||||
|
|
||||||
|
install_repos(
|
||||||
|
self.selected,
|
||||||
|
self.base_dir,
|
||||||
|
self.bin_dir,
|
||||||
|
self.all_repos,
|
||||||
|
no_verification=False,
|
||||||
|
preview=False,
|
||||||
|
quiet=True,
|
||||||
|
clone_mode="shallow",
|
||||||
|
update_dependencies=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Prüfen, dass ein pkgmgr install Befehl mit --clone-mode shallow gebaut wurde
|
||||||
|
pkgmgr_install_cmds = [
|
||||||
|
c for (c, cwd, preview) in commands if "pkgmgr install" in c
|
||||||
|
]
|
||||||
|
self.assertTrue(
|
||||||
|
pkgmgr_install_cmds,
|
||||||
|
f"No pkgmgr install command was executed. Commands seen: {commands}",
|
||||||
|
)
|
||||||
|
|
||||||
|
cmd = pkgmgr_install_cmds[0]
|
||||||
|
self.assertIn("--clone-mode shallow", cmd)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
47
tests/test_integration_install_all_shallow.py
Normal file
47
tests/test_integration_install_all_shallow.py
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
# tests/test_integration_install_all_shallow.py
|
||||||
|
"""
|
||||||
|
Integration test: install all configured repositories using
|
||||||
|
--clone-mode shallow (HTTPS shallow clone) and --no-verification.
|
||||||
|
|
||||||
|
This test is intended to be run inside the Docker container where:
|
||||||
|
- network access is available,
|
||||||
|
- the config/config.yaml is present,
|
||||||
|
- and it is safe to perform real git operations.
|
||||||
|
|
||||||
|
It passes if the command completes without raising an exception.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import runpy
|
||||||
|
import sys
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
|
||||||
|
class TestIntegrationInstallAllShallow(unittest.TestCase):
|
||||||
|
def test_install_all_repositories_shallow(self):
|
||||||
|
"""
|
||||||
|
Run: pkgmgr install --all --clone-mode shallow --no-verification
|
||||||
|
|
||||||
|
This will perform real installations/clones inside the container.
|
||||||
|
The test succeeds if no exception is raised.
|
||||||
|
"""
|
||||||
|
original_argv = sys.argv
|
||||||
|
try:
|
||||||
|
sys.argv = [
|
||||||
|
"pkgmgr",
|
||||||
|
"install",
|
||||||
|
"--all",
|
||||||
|
"--clone-mode",
|
||||||
|
"shallow",
|
||||||
|
"--no-verification",
|
||||||
|
]
|
||||||
|
|
||||||
|
# Execute main.py as if it was called from CLI.
|
||||||
|
# This will run the full install pipeline inside the container.
|
||||||
|
runpy.run_module("main", run_name="__main__")
|
||||||
|
|
||||||
|
finally:
|
||||||
|
sys.argv = original_argv
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
19
tests/test_main.py
Normal file
19
tests/test_main.py
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# tests/test_main.py
|
||||||
|
import unittest
|
||||||
|
import main
|
||||||
|
|
||||||
|
|
||||||
|
class TestMainModule(unittest.TestCase):
|
||||||
|
def test_proxy_commands_defined(self):
|
||||||
|
"""
|
||||||
|
Basic sanity check: main.py should define PROXY_COMMANDS
|
||||||
|
with git/docker/docker compose entries.
|
||||||
|
"""
|
||||||
|
self.assertTrue(hasattr(main, "PROXY_COMMANDS"))
|
||||||
|
self.assertIn("git", main.PROXY_COMMANDS)
|
||||||
|
self.assertIn("docker", main.PROXY_COMMANDS)
|
||||||
|
self.assertIn("docker compose", main.PROXY_COMMANDS)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
unittest.main()
|
||||||
Reference in New Issue
Block a user