Refactor: Restructure pkgmgr into actions/, core/, and cli/ (full module breakup)

This commit introduces a large-scale structural refactor of the pkgmgr
codebase. All functionality has been moved from the previous flat
top-level layout into three clearly separated namespaces:

  • pkgmgr.actions      – high-level operations invoked by the CLI
  • pkgmgr.core         – pure logic, helpers, repository utilities,
                          versioning, git helpers, config IO, and
                          command resolution
  • pkgmgr.cli          – parser, dispatch, context, and command
                          handlers

Key improvements:
  - Moved all “branch”, “release”, “changelog”, repo-management
    actions, installer pipelines, and proxy execution logic into
    pkgmgr.actions.<domain>.
  - Reworked installer structure under
        pkgmgr.actions.repository.install.installers
    including OS-package installers, Nix, Python, and Makefile.
  - Consolidated all low-level functionality under pkgmgr.core:
        • git helpers → core/git
        • config load/save → core/config
        • repository helpers → core/repository
        • versioning & semver → core/version
        • command helpers (alias, resolve, run, ink) → core/command
  - Replaced pkgmgr.cli_core with pkgmgr.cli and updated all imports.
  - Added minimal __init__.py files for clean package exposure.
  - Updated all E2E, integration, and unit tests with new module paths.
  - Fixed patch targets so mocks point to the new structure.
  - Ensured backward compatibility at the CLI boundary (pkgmgr entry point unchanged).

This refactor produces a cleaner, layered architecture:
  - `core` = logic
  - `actions` = orchestrated behaviour
  - `cli` = user interface

Reference: ChatGPT-assisted refactor discussion
https://chatgpt.com/share/6938221c-e24c-800f-8317-7732cedf39b9
This commit is contained in:
Kevin Veen-Birkenbach
2025-12-09 14:20:19 +01:00
parent 59d0355b91
commit d50891dfe5
92 changed files with 381 additions and 388 deletions

View File

View File

@@ -6,14 +6,14 @@
High-level helpers for branch-related operations.
This module encapsulates the actual Git logic so the CLI layer
(pkgmgr.cli_core.commands.branch) stays thin and testable.
(pkgmgr.cli.commands.branch) stays thin and testable.
"""
from __future__ import annotations
from typing import Optional
from pkgmgr.git_utils import run_git, GitError, get_current_branch
from pkgmgr.core.git import run_git, GitError, get_current_branch
def open_branch(

View File

@@ -13,7 +13,7 @@ from __future__ import annotations
from typing import Optional
from pkgmgr.git_utils import run_git, GitError
from pkgmgr.core.git import run_git, GitError
def generate_changelog(

View File

View File

@@ -26,8 +26,8 @@ import os
import subprocess
from typing import Any, Dict
from pkgmgr.generate_alias import generate_alias
from pkgmgr.save_user_config import save_user_config
from pkgmgr.core.command.alias import generate_alias
from pkgmgr.core.config.save import save_user_config
def config_init(

View File

@@ -1,5 +1,5 @@
import yaml
from .load_config import load_config
from pkgmgr.core.config.load import load_config
def show_config(selected_repos, user_config_path, full_config=False):
"""Display configuration for one or more repositories, or the entire merged config."""

View File

@@ -1,7 +1,7 @@
import os
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.run_command import run_command
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.command.run import run_command
import sys
def exec_proxy_command(proxy_prefix: str, selected_repos, repositories_base_dir, all_repos, proxy_command: str, extra_args, preview: bool):

View File

@@ -39,9 +39,9 @@ import tempfile
from datetime import date, datetime
from typing import Optional, Tuple
from pkgmgr.git_utils import get_tags, get_current_branch, GitError
from pkgmgr.branch_commands import close_branch
from pkgmgr.versioning import (
from pkgmgr.core.git import get_tags, get_current_branch, GitError
from pkgmgr.actions.branch import close_branch
from pkgmgr.core.version.semver import (
SemVer,
find_latest_version,
bump_major,

View File

View File

@@ -1,8 +1,8 @@
import subprocess
import os
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.verify import verify_repository
from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.repository.verify import verify_repository
def clone_repos(
selected_repos,

View File

@@ -2,8 +2,8 @@ import os
import subprocess
import sys
import yaml
from pkgmgr.generate_alias import generate_alias
from pkgmgr.save_user_config import save_user_config
from pkgmgr.core.command.alias import generate_alias
from pkgmgr.core.config.save import save_user_config
def create_repo(identifier, config_merged, user_config_path, bin_dir, remote=False, preview=False):
"""

View File

@@ -1,7 +1,7 @@
import os
import sys
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.repository.dir import get_repo_dir
def deinstall_repos(selected_repos, repositories_base_dir, bin_dir, all_repos, preview=False):
for repo in selected_repos:

View File

@@ -1,7 +1,7 @@
import shutil
import os
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.repository.dir import get_repo_dir
def delete_repos(selected_repos, repositories_base_dir, all_repos, preview=False):
for repo in selected_repos:

View File

@@ -21,23 +21,23 @@ focused installer classes.
import os
from typing import List, Dict, Any
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.create_ink import create_ink
from pkgmgr.verify import verify_repository
from pkgmgr.clone_repos import clone_repos
from pkgmgr.context import RepoContext
from pkgmgr.resolve_command import resolve_command_for_repo
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.command.ink import create_ink
from pkgmgr.core.repository.verify import verify_repository
from pkgmgr.actions.repository.clone import clone_repos
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.core.command.resolve import resolve_command_for_repo
# Installer implementations
from pkgmgr.installers.os_packages import (
from pkgmgr.actions.repository.install.installers.os_packages import (
ArchPkgbuildInstaller,
DebianControlInstaller,
RpmSpecInstaller,
)
from pkgmgr.installers.nix_flake import NixFlakeInstaller
from pkgmgr.installers.python import PythonInstaller
from pkgmgr.installers.makefile import MakefileInstaller
from pkgmgr.actions.repository.install.installers.nix_flake import NixFlakeInstaller
from pkgmgr.actions.repository.install.installers.python import PythonInstaller
from pkgmgr.actions.repository.install.installers.makefile import MakefileInstaller
# Layering:

View File

@@ -38,7 +38,7 @@ from abc import ABC, abstractmethod
from typing import Iterable, TYPE_CHECKING
if TYPE_CHECKING:
from pkgmgr.context import RepoContext
from pkgmgr.actions.repository.install.context import RepoContext
# ---------------------------------------------------------------------------

View File

@@ -0,0 +1,19 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Installer package for pkgmgr.
This exposes all installer classes so users can import them directly from
pkgmgr.actions.repository.install.installers.
"""
from pkgmgr.actions.repository.install.installers.base import BaseInstaller # noqa: F401
from pkgmgr.actions.repository.install.installers.nix_flake import NixFlakeInstaller # noqa: F401
from pkgmgr.actions.repository.install.installers.python import PythonInstaller # noqa: F401
from pkgmgr.actions.repository.install.installers.makefile import MakefileInstaller # noqa: F401
# OS-specific installers
from pkgmgr.actions.repository.install.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller # noqa: F401
from pkgmgr.actions.repository.install.installers.os_packages.debian_control import DebianControlInstaller # noqa: F401
from pkgmgr.actions.repository.install.installers.os_packages.rpm_spec import RpmSpecInstaller # noqa: F401

View File

@@ -8,8 +8,8 @@ Base interface for all installer components in the pkgmgr installation pipeline.
from abc import ABC, abstractmethod
from typing import Set
from pkgmgr.context import RepoContext
from pkgmgr.capabilities import CAPABILITY_MATCHERS
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.capabilities import CAPABILITY_MATCHERS
class BaseInstaller(ABC):

View File

@@ -12,9 +12,9 @@ installation step.
import os
import re
from pkgmgr.context import RepoContext
from pkgmgr.installers.base import BaseInstaller
from pkgmgr.run_command import run_command
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
from pkgmgr.core.command.run import run_command
class MakefileInstaller(BaseInstaller):

View File

@@ -19,12 +19,12 @@ import os
import shutil
from typing import TYPE_CHECKING
from pkgmgr.installers.base import BaseInstaller
from pkgmgr.run_command import run_command
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
from pkgmgr.core.command.run import run_command
if TYPE_CHECKING:
from pkgmgr.context import RepoContext
from pkgmgr.install_repos import InstallContext
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install import InstallContext
class NixFlakeInstaller(BaseInstaller):

View File

@@ -3,9 +3,9 @@
import os
import shutil
from pkgmgr.context import RepoContext
from pkgmgr.installers.base import BaseInstaller
from pkgmgr.run_command import run_command
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
from pkgmgr.core.command.run import run_command
class ArchPkgbuildInstaller(BaseInstaller):

View File

@@ -20,9 +20,9 @@ import shutil
from typing import List
from pkgmgr.context import RepoContext
from pkgmgr.installers.base import BaseInstaller
from pkgmgr.run_command import run_command
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
from pkgmgr.core.command.run import run_command
class DebianControlInstaller(BaseInstaller):

View File

@@ -19,9 +19,9 @@ import shutil
from typing import List, Optional
from pkgmgr.context import RepoContext
from pkgmgr.installers.base import BaseInstaller
from pkgmgr.run_command import run_command
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
from pkgmgr.core.command.run import run_command
class RpmSpecInstaller(BaseInstaller):

View File

@@ -17,8 +17,8 @@ All installation failures are treated as fatal errors (SystemExit).
import os
import sys
from pkgmgr.installers.base import BaseInstaller
from pkgmgr.run_command import run_command
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
from pkgmgr.core.command.run import run_command
class PythonInstaller(BaseInstaller):

View File

@@ -1,9 +1,9 @@
import os
import subprocess
import sys
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.verify import verify_repository
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.repository.verify import verify_repository
def pull_with_verification(
selected_repos,

View File

@@ -1,9 +1,9 @@
import sys
import shutil
from .exec_proxy_command import exec_proxy_command
from .run_command import run_command
from .get_repo_identifier import get_repo_identifier
from pkgmgr.actions.proxy import exec_proxy_command
from pkgmgr.core.command.run import run_command
from pkgmgr.core.repository.identifier import get_repo_identifier
def status_repos(

View File

@@ -1,8 +1,8 @@
import sys
import shutil
from pkgmgr.pull_with_verification import pull_with_verification
from pkgmgr.install_repos import install_repos
from pkgmgr.actions.repository.pull import pull_with_verification
from pkgmgr.actions.repository.install import install_repos
def update_repos(
@@ -54,7 +54,7 @@ def update_repos(
)
if system_update:
from pkgmgr.run_command import run_command
from pkgmgr.core.command.run import run_command
# Nix: upgrade all profile entries (if Nix is available)
if shutil.which("nix") is not None:

13
pkgmgr/cli.py → pkgmgr/cli/__init__.py Executable file → Normal file
View File

@@ -1,13 +1,16 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
from __future__ import annotations
import os
import sys
from pkgmgr.load_config import load_config
from pkgmgr.cli_core import CLIContext, create_parser, dispatch_command
from pkgmgr.core.config.load import load_config
from .context import CLIContext
from .parser import create_parser
from .dispatch import dispatch_command
__all__ = ["CLIContext", "create_parser", "dispatch_command", "main"]
# User config lives in the home directory:
# ~/.config/pkgmgr/config.yaml

View File

@@ -3,8 +3,8 @@ from __future__ import annotations
import sys
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.branch_commands import open_branch, close_branch
from pkgmgr.cli.context import CLIContext
from pkgmgr.actions.branch import open_branch, close_branch
def handle_branch(args, ctx: CLIContext) -> None:

View File

@@ -4,12 +4,12 @@ import os
import sys
from typing import Any, Dict, List, Optional, Tuple
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.git_utils import get_tags
from pkgmgr.versioning import SemVer, extract_semver_from_tags
from pkgmgr.changelog import generate_changelog
from pkgmgr.cli.context import CLIContext
from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.git import get_tags
from pkgmgr.core.version.semver import SemVer, extract_semver_from_tags
from pkgmgr.actions.changelog import generate_changelog
Repository = Dict[str, Any]

View File

@@ -11,13 +11,13 @@ from typing import Any, Dict
import yaml
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.config_init import config_init
from pkgmgr.interactive_add import interactive_add
from pkgmgr.resolve_repos import resolve_repos
from pkgmgr.save_user_config import save_user_config
from pkgmgr.show_config import show_config
from pkgmgr.run_command import run_command
from pkgmgr.cli.context import CLIContext
from pkgmgr.actions.config.init import config_init
from pkgmgr.actions.config.add import interactive_add
from pkgmgr.core.repository.resolve import resolve_repos
from pkgmgr.core.config.save import save_user_config
from pkgmgr.actions.config.show import show_config
from pkgmgr.core.command.run import run_command
def _load_user_config(user_config_path: str) -> Dict[str, Any]:

View File

@@ -3,8 +3,8 @@ from __future__ import annotations
import sys
from typing import Any, Dict, List
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.exec_proxy_command import exec_proxy_command
from pkgmgr.cli.context import CLIContext
from pkgmgr.actions.proxy import exec_proxy_command
Repository = Dict[str, Any]

View File

@@ -13,7 +13,7 @@ Responsibilities:
- Take the parsed argparse.Namespace for the `release` command.
- Use the list of selected repositories provided by dispatch_command().
- Optionally list affected repositories when --list is set.
- For each selected repository, run pkgmgr.release.release(...) in
- For each selected repository, run pkgmgr.actions.release.release(...) in
the context of that repository directory.
"""
@@ -22,10 +22,10 @@ from __future__ import annotations
import os
from typing import Any, Dict, List
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.release import release as run_release
from pkgmgr.cli.context import CLIContext
from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.actions.release import release as run_release
Repository = Dict[str, Any]
@@ -46,7 +46,7 @@ def handle_release(
3) For each selected repository:
- Resolve its identifier and local directory.
- Change into that directory.
- Call pkgmgr.release.release(...) with the parsed options.
- Call pkgmgr.actions.release.release(...) with the parsed options.
"""
if not selected:
print("[pkgmgr] No repositories selected for release.")

View File

@@ -6,16 +6,16 @@ from __future__ import annotations
import sys
from typing import Any, Dict, List
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.install_repos import install_repos
from pkgmgr.deinstall_repos import deinstall_repos
from pkgmgr.delete_repos import delete_repos
from pkgmgr.update_repos import update_repos
from pkgmgr.status_repos import status_repos
from pkgmgr.list_repositories import list_repositories
from pkgmgr.run_command import run_command
from pkgmgr.create_repo import create_repo
from pkgmgr.get_selected_repos import get_selected_repos
from pkgmgr.cli.context import CLIContext
from pkgmgr.actions.repository.install import install_repos
from pkgmgr.actions.repository.deinstall import deinstall_repos
from pkgmgr.actions.repository.delete import delete_repos
from pkgmgr.actions.repository.update import update_repos
from pkgmgr.actions.repository.status import status_repos
from pkgmgr.actions.repository.list import list_repositories
from pkgmgr.core.command.run import run_command
from pkgmgr.actions.repository.create import create_repo
from pkgmgr.core.repository.selected import get_selected_repos
Repository = Dict[str, Any]

View File

@@ -5,9 +5,9 @@ import os
from typing import Any, Dict, List
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.run_command import run_command
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.cli.context import CLIContext
from pkgmgr.core.command.run import run_command
from pkgmgr.core.repository.identifier import get_repo_identifier
Repository = Dict[str, Any]

View File

@@ -4,12 +4,12 @@ import os
import sys
from typing import Any, Dict, List, Optional, Tuple
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.git_utils import get_tags
from pkgmgr.versioning import SemVer, find_latest_version
from pkgmgr.version_sources import (
from pkgmgr.cli.context import CLIContext
from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.git import get_tags
from pkgmgr.core.version.semver import SemVer, find_latest_version
from pkgmgr.core.version.source import (
read_pyproject_version,
read_flake_version,
read_pkgbuild_version,

View File

@@ -7,12 +7,12 @@ import os
import sys
from typing import List, Dict, Any
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.cli_core.proxy import maybe_handle_proxy
from pkgmgr.get_selected_repos import get_selected_repos
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.cli.context import CLIContext
from pkgmgr.cli.proxy import maybe_handle_proxy
from pkgmgr.core.repository.selected import get_selected_repos
from pkgmgr.core.repository.dir import get_repo_dir
from pkgmgr.cli_core.commands import (
from pkgmgr.cli.commands import (
handle_repos_command,
handle_tools_command,
handle_release,

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
import argparse
from pkgmgr.cli_core.proxy import register_proxy_commands
from pkgmgr.cli.proxy import register_proxy_commands
class SortedSubParsersAction(argparse._SubParsersAction):

View File

@@ -8,12 +8,12 @@ import os
import sys
from typing import Dict, List, Any
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.clone_repos import clone_repos
from pkgmgr.exec_proxy_command import exec_proxy_command
from pkgmgr.pull_with_verification import pull_with_verification
from pkgmgr.get_selected_repos import get_selected_repos
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.cli.context import CLIContext
from pkgmgr.actions.repository.clone import clone_repos
from pkgmgr.actions.proxy import exec_proxy_command
from pkgmgr.actions.repository.pull import pull_with_verification
from pkgmgr.core.repository.selected import get_selected_repos
from pkgmgr.core.repository.dir import get_repo_dir
PROXY_COMMANDS: Dict[str, List[str]] = {

View File

@@ -1,5 +0,0 @@
from .context import CLIContext
from .parser import create_parser
from .dispatch import dispatch_command
__all__ = ["CLIContext", "create_parser", "dispatch_command"]

View File

View File

@@ -2,8 +2,8 @@
# -*- coding: utf-8 -*-
import os
from pkgmgr.get_repo_identifier import get_repo_identifier
from pkgmgr.get_repo_dir import get_repo_dir
from pkgmgr.core.repository.identifier import get_repo_identifier
from pkgmgr.core.repository.dir import get_repo_dir
def create_ink(repo, repositories_base_dir, bin_dir, all_repos,

View File

View File

View File

@@ -7,7 +7,7 @@ import os
import re
from typing import Any, Dict, List, Sequence
from pkgmgr.resolve_repos import resolve_repos
from pkgmgr.core.repository.resolve import resolve_repos
Repository = Dict[str, Any]

View File

View File

@@ -1,19 +0,0 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Installer package for pkgmgr.
This exposes all installer classes so users can import them directly from
pkgmgr.installers.
"""
from pkgmgr.installers.base import BaseInstaller # noqa: F401
from pkgmgr.installers.nix_flake import NixFlakeInstaller # noqa: F401
from pkgmgr.installers.python import PythonInstaller # noqa: F401
from pkgmgr.installers.makefile import MakefileInstaller # noqa: F401
# OS-specific installers
from pkgmgr.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller # noqa: F401
from pkgmgr.installers.os_packages.debian_control import DebianControlInstaller # noqa: F401
from pkgmgr.installers.os_packages.rpm_spec import RpmSpecInstaller # noqa: F401

View File

@@ -31,7 +31,7 @@ class TestIntegrationBranchCommands(unittest.TestCase):
finally:
sys.argv = original_argv
@patch("pkgmgr.cli_core.commands.branch.open_branch")
@patch("pkgmgr.cli.commands.branch.open_branch")
def test_branch_open_with_name_and_base(self, mock_open_branch) -> None:
"""
`pkgmgr branch open feature/test --base develop` must forward
@@ -47,7 +47,7 @@ class TestIntegrationBranchCommands(unittest.TestCase):
self.assertEqual(kwargs.get("base_branch"), "develop")
self.assertEqual(kwargs.get("cwd"), ".")
@patch("pkgmgr.cli_core.commands.branch.open_branch")
@patch("pkgmgr.cli.commands.branch.open_branch")
def test_branch_open_without_name_uses_default_base(
self,
mock_open_branch,
@@ -68,7 +68,7 @@ class TestIntegrationBranchCommands(unittest.TestCase):
# close subcommand
# ------------------------------------------------------------------
@patch("pkgmgr.cli_core.commands.branch.close_branch")
@patch("pkgmgr.cli.commands.branch.close_branch")
def test_branch_close_with_name_and_base(self, mock_close_branch) -> None:
"""
`pkgmgr branch close feature/test --base develop` must forward
@@ -84,7 +84,7 @@ class TestIntegrationBranchCommands(unittest.TestCase):
self.assertEqual(kwargs.get("base_branch"), "develop")
self.assertEqual(kwargs.get("cwd"), ".")
@patch("pkgmgr.cli_core.commands.branch.close_branch")
@patch("pkgmgr.cli.commands.branch.close_branch")
def test_branch_close_without_name_uses_default_base(
self,
mock_close_branch,

View File

@@ -6,7 +6,7 @@ End-to-end style integration tests for the `pkgmgr release` CLI command.
These tests exercise the real top-level entry point (main.py) and mock
the high-level helper used by the CLI wiring
(pkgmgr.cli_core.commands.release.run_release) to ensure that argument
(pkgmgr.cli.commands.release.run_release) to ensure that argument
parsing and dispatch behave as expected, in particular the new `close`
flag.
@@ -52,8 +52,8 @@ class TestIntegrationReleaseCommand(unittest.TestCase):
# Behaviour without --close
# ------------------------------------------------------------------
@patch("pkgmgr.cli_core.commands.release.run_release")
@patch("pkgmgr.cli_core.dispatch._select_repo_for_current_directory")
@patch("pkgmgr.cli.commands.release.run_release")
@patch("pkgmgr.cli.dispatch._select_repo_for_current_directory")
def test_release_without_close_flag(
self,
mock_select_repo,
@@ -95,8 +95,8 @@ class TestIntegrationReleaseCommand(unittest.TestCase):
# Behaviour with --close
# ------------------------------------------------------------------
@patch("pkgmgr.cli_core.commands.release.run_release")
@patch("pkgmgr.cli_core.dispatch._select_repo_for_current_directory")
@patch("pkgmgr.cli.commands.release.run_release")
@patch("pkgmgr.cli.dispatch._select_repo_for_current_directory")
def test_release_with_close_flag(
self,
mock_select_repo,

View File

@@ -28,7 +28,7 @@ import sys
import unittest
from typing import List
from pkgmgr.load_config import load_config
from pkgmgr.core.config.load import load_config
# Resolve project root (the repo where main.py lives, e.g. /src)
PROJECT_ROOT = os.path.abspath(

View File

@@ -5,9 +5,9 @@ import tempfile
import unittest
from unittest.mock import patch
import pkgmgr.install_repos as install_module
from pkgmgr.install_repos import install_repos
from pkgmgr.installers.base import BaseInstaller
import pkgmgr.actions.repository.install as install_module
from pkgmgr.actions.repository.install import install_repos
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
class DummyInstaller(BaseInstaller):
@@ -26,10 +26,10 @@ class DummyInstaller(BaseInstaller):
class TestInstallReposIntegration(unittest.TestCase):
@patch("pkgmgr.install_repos.verify_repository")
@patch("pkgmgr.install_repos.clone_repos")
@patch("pkgmgr.install_repos.get_repo_dir")
@patch("pkgmgr.install_repos.get_repo_identifier")
@patch("pkgmgr.actions.repository.install.verify_repository")
@patch("pkgmgr.actions.repository.install.clone_repos")
@patch("pkgmgr.actions.repository.install.get_repo_dir")
@patch("pkgmgr.actions.repository.install.get_repo_identifier")
def test_system_binary_vs_nix_binary(
self,
mock_get_repo_identifier,
@@ -100,8 +100,8 @@ class TestInstallReposIntegration(unittest.TestCase):
nix_tool_path = "/nix/profile/bin/repo-nix"
# Patch resolve_command_for_repo at the install_repos module level
with patch("pkgmgr.install_repos.resolve_command_for_repo") as mock_resolve, \
patch("pkgmgr.install_repos.os.path.exists") as mock_exists_install:
with patch("pkgmgr.actions.repository.install.resolve_command_for_repo") as mock_resolve, \
patch("pkgmgr.actions.repository.install.os.path.exists") as mock_exists_install:
def fake_resolve_command(repo, repo_identifier: str, repo_dir: str):
"""

View File

@@ -61,12 +61,12 @@ import tempfile
import unittest
from unittest.mock import patch
import pkgmgr.install_repos as install_mod
from pkgmgr.install_repos import install_repos
from pkgmgr.installers.nix_flake import NixFlakeInstaller
from pkgmgr.installers.python import PythonInstaller
from pkgmgr.installers.makefile import MakefileInstaller
from pkgmgr.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller
import pkgmgr.actions.repository.install as install_mod
from pkgmgr.actions.repository.install import install_repos
from pkgmgr.actions.repository.install.installers.nix_flake import NixFlakeInstaller
from pkgmgr.actions.repository.install.installers.python import PythonInstaller
from pkgmgr.actions.repository.install.installers.makefile import MakefileInstaller
from pkgmgr.actions.repository.install.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller
class TestRecursiveCapabilitiesIntegration(unittest.TestCase):

View File

@@ -2,12 +2,12 @@
# -*- coding: utf-8 -*-
"""
Unit tests for pkgmgr.cli_core.commands.release.
Unit tests for pkgmgr.cli.commands.release.
These tests focus on the wiring layer:
- Argument handling for the release command as defined by the
top-level parser (cli_core.parser.create_parser).
- Correct invocation of pkgmgr.release.release(...) for the
- Correct invocation of pkgmgr.actions.release.release(...) for the
selected repositories.
- Behaviour of --preview, --list, --close, and -f/--force.
"""
@@ -46,19 +46,19 @@ class TestReleaseCommand(unittest.TestCase):
Build a real top-level parser and parse the given argv list
to obtain the Namespace for the `release` command.
"""
from pkgmgr.cli_core.parser import create_parser
from pkgmgr.cli.parser import create_parser
parser = create_parser("test parser")
args = parser.parse_args(argv)
self.assertEqual(args.command, "release")
return args
@patch("pkgmgr.cli_core.commands.release.os.path.isdir", return_value=True)
@patch("pkgmgr.cli_core.commands.release.run_release")
@patch("pkgmgr.cli_core.commands.release.get_repo_dir")
@patch("pkgmgr.cli_core.commands.release.get_repo_identifier")
@patch("pkgmgr.cli_core.commands.release.os.chdir")
@patch("pkgmgr.cli_core.commands.release.os.getcwd", return_value="/cwd")
@patch("pkgmgr.cli.commands.release.os.path.isdir", return_value=True)
@patch("pkgmgr.cli.commands.release.run_release")
@patch("pkgmgr.cli.commands.release.get_repo_dir")
@patch("pkgmgr.cli.commands.release.get_repo_identifier")
@patch("pkgmgr.cli.commands.release.os.chdir")
@patch("pkgmgr.cli.commands.release.os.getcwd", return_value="/cwd")
def test_release_with_close_and_message(
self,
mock_getcwd,
@@ -69,7 +69,7 @@ class TestReleaseCommand(unittest.TestCase):
mock_isdir,
) -> None:
"""
The release handler should call pkgmgr.release.release() with:
The release handler should call pkgmgr.actions.release.release() with:
- release_type (e.g. minor)
- provided message
- preview flag
@@ -78,7 +78,7 @@ class TestReleaseCommand(unittest.TestCase):
It must change into the repository directory and then back.
"""
from pkgmgr.cli_core.commands.release import handle_release
from pkgmgr.cli.commands.release import handle_release
repo = {"name": "dummy-repo"}
selected = [repo]
@@ -116,12 +116,12 @@ class TestReleaseCommand(unittest.TestCase):
close=True,
)
@patch("pkgmgr.cli_core.commands.release.os.path.isdir", return_value=True)
@patch("pkgmgr.cli_core.commands.release.run_release")
@patch("pkgmgr.cli_core.commands.release.get_repo_dir")
@patch("pkgmgr.cli_core.commands.release.get_repo_identifier")
@patch("pkgmgr.cli_core.commands.release.os.chdir")
@patch("pkgmgr.cli_core.commands.release.os.getcwd", return_value="/cwd")
@patch("pkgmgr.cli.commands.release.os.path.isdir", return_value=True)
@patch("pkgmgr.cli.commands.release.run_release")
@patch("pkgmgr.cli.commands.release.get_repo_dir")
@patch("pkgmgr.cli.commands.release.get_repo_identifier")
@patch("pkgmgr.cli.commands.release.os.chdir")
@patch("pkgmgr.cli.commands.release.os.getcwd", return_value="/cwd")
def test_release_preview_mode(
self,
mock_getcwd,
@@ -135,7 +135,7 @@ class TestReleaseCommand(unittest.TestCase):
In preview mode, the handler should pass preview=True to the
release helper and force=False by default.
"""
from pkgmgr.cli_core.commands.release import handle_release
from pkgmgr.cli.commands.release import handle_release
repo = {"name": "dummy-repo"}
selected = [repo]
@@ -164,9 +164,9 @@ class TestReleaseCommand(unittest.TestCase):
close=False,
)
@patch("pkgmgr.cli_core.commands.release.run_release")
@patch("pkgmgr.cli_core.commands.release.get_repo_dir")
@patch("pkgmgr.cli_core.commands.release.get_repo_identifier")
@patch("pkgmgr.cli.commands.release.run_release")
@patch("pkgmgr.cli.commands.release.get_repo_dir")
@patch("pkgmgr.cli.commands.release.get_repo_identifier")
def test_release_list_mode_does_not_invoke_helper(
self,
mock_get_repo_identifier,
@@ -177,7 +177,7 @@ class TestReleaseCommand(unittest.TestCase):
When --list is provided, the handler should print the list of affected
repositories and must NOT invoke run_release().
"""
from pkgmgr.cli_core.commands.release import handle_release
from pkgmgr.cli.commands.release import handle_release
repo1 = {"name": "repo-1"}
repo2 = {"name": "repo-2"}

View File

@@ -16,8 +16,8 @@ from __future__ import annotations
import unittest
from unittest.mock import patch
from pkgmgr.cli_core.parser import create_parser
from pkgmgr.cli_core.commands.branch import handle_branch
from pkgmgr.cli.parser import create_parser
from pkgmgr.cli.commands.branch import handle_branch
class TestBranchCLI(unittest.TestCase):
@@ -31,7 +31,7 @@ class TestBranchCLI(unittest.TestCase):
"""
return create_parser("pkgmgr test parser")
@patch("pkgmgr.cli_core.commands.branch.open_branch")
@patch("pkgmgr.cli.commands.branch.open_branch")
def test_branch_open_with_name_and_base(self, mock_open_branch):
"""
Ensure that `pkgmgr branch open <name> --base <branch>` calls
@@ -58,7 +58,7 @@ class TestBranchCLI(unittest.TestCase):
self.assertEqual(kwargs.get("base_branch"), "develop")
self.assertEqual(kwargs.get("cwd"), ".")
@patch("pkgmgr.cli_core.commands.branch.close_branch")
@patch("pkgmgr.cli.commands.branch.close_branch")
def test_branch_close_with_name_and_base(self, mock_close_branch):
"""
Ensure that `pkgmgr branch close <name> --base <branch>` calls
@@ -84,7 +84,7 @@ class TestBranchCLI(unittest.TestCase):
self.assertEqual(kwargs.get("base_branch"), "main")
self.assertEqual(kwargs.get("cwd"), ".")
@patch("pkgmgr.cli_core.commands.branch.close_branch")
@patch("pkgmgr.cli.commands.branch.close_branch")
def test_branch_close_without_name_uses_none(self, mock_close_branch):
"""
Ensure that `pkgmgr branch close` without a name passes name=None

View File

@@ -4,8 +4,8 @@ import os
import unittest
from unittest.mock import patch
from pkgmgr.context import RepoContext
from pkgmgr.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.os_packages.arch_pkgbuild import ArchPkgbuildInstaller
class TestArchPkgbuildInstaller(unittest.TestCase):
@@ -26,7 +26,7 @@ class TestArchPkgbuildInstaller(unittest.TestCase):
)
self.installer = ArchPkgbuildInstaller()
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
@patch("pkgmgr.actions.repository.install.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
@patch("os.path.exists", return_value=True)
@patch("shutil.which")
def test_supports_true_when_tools_and_pkgbuild_exist(
@@ -46,7 +46,7 @@ class TestArchPkgbuildInstaller(unittest.TestCase):
self.assertIn("makepkg", calls)
mock_exists.assert_called_with(os.path.join(self.ctx.repo_dir, "PKGBUILD"))
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=0)
@patch("pkgmgr.actions.repository.install.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=0)
@patch("os.path.exists", return_value=True)
@patch("shutil.which")
def test_supports_false_when_running_as_root(
@@ -55,7 +55,7 @@ class TestArchPkgbuildInstaller(unittest.TestCase):
mock_which.return_value = "/usr/bin/pacman"
self.assertFalse(self.installer.supports(self.ctx))
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
@patch("pkgmgr.actions.repository.install.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
@patch("os.path.exists", return_value=False)
@patch("shutil.which")
def test_supports_false_when_pkgbuild_missing(
@@ -64,8 +64,8 @@ class TestArchPkgbuildInstaller(unittest.TestCase):
mock_which.return_value = "/usr/bin/pacman"
self.assertFalse(self.installer.supports(self.ctx))
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.run_command")
@patch("pkgmgr.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
@patch("pkgmgr.actions.repository.install.installers.os_packages.arch_pkgbuild.run_command")
@patch("pkgmgr.actions.repository.install.installers.os_packages.arch_pkgbuild.os.geteuid", return_value=1000)
@patch("os.path.exists", return_value=True)
@patch("shutil.which")
def test_run_builds_and_installs_with_makepkg(

View File

@@ -4,8 +4,8 @@ import os
import unittest
from unittest.mock import patch
from pkgmgr.context import RepoContext
from pkgmgr.installers.os_packages.debian_control import DebianControlInstaller
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.os_packages.debian_control import DebianControlInstaller
class TestDebianControlInstaller(unittest.TestCase):
@@ -36,7 +36,7 @@ class TestDebianControlInstaller(unittest.TestCase):
def test_supports_false_without_dpkg_buildpackage(self, mock_which, mock_exists):
self.assertFalse(self.installer.supports(self.ctx))
@patch("pkgmgr.installers.os_packages.debian_control.run_command")
@patch("pkgmgr.actions.repository.install.installers.os_packages.debian_control.run_command")
@patch("glob.glob", return_value=["/tmp/package-manager_0.1.1_all.deb"])
@patch("os.path.exists", return_value=True)
@patch("shutil.which")

View File

@@ -3,8 +3,8 @@
import unittest
from unittest.mock import patch
from pkgmgr.context import RepoContext
from pkgmgr.installers.os_packages.rpm_spec import RpmSpecInstaller
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.os_packages.rpm_spec import RpmSpecInstaller
class TestRpmSpecInstaller(unittest.TestCase):
@@ -45,7 +45,7 @@ class TestRpmSpecInstaller(unittest.TestCase):
mock_which.return_value = "/usr/bin/rpmbuild"
self.assertFalse(self.installer.supports(self.ctx))
@patch("pkgmgr.installers.os_packages.rpm_spec.run_command")
@patch("pkgmgr.actions.repository.install.installers.os_packages.rpm_spec.run_command")
@patch("glob.glob")
@patch("shutil.which")
def test_run_builds_and_installs_rpms(

View File

@@ -1,8 +1,8 @@
# tests/unit/pkgmgr/installers/test_base.py
import unittest
from pkgmgr.installers.base import BaseInstaller
from pkgmgr.context import RepoContext
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
from pkgmgr.actions.repository.install.context import RepoContext
class DummyInstaller(BaseInstaller):

View File

@@ -4,8 +4,8 @@ import os
import unittest
from unittest.mock import patch, mock_open
from pkgmgr.context import RepoContext
from pkgmgr.installers.makefile import MakefileInstaller
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.makefile import MakefileInstaller
class TestMakefileInstaller(unittest.TestCase):
@@ -35,7 +35,7 @@ class TestMakefileInstaller(unittest.TestCase):
def test_supports_false_when_makefile_missing(self, mock_exists):
self.assertFalse(self.installer.supports(self.ctx))
@patch("pkgmgr.installers.makefile.run_command")
@patch("pkgmgr.actions.repository.install.installers.makefile.run_command")
@patch(
"builtins.open",
new_callable=mock_open,
@@ -62,7 +62,7 @@ class TestMakefileInstaller(unittest.TestCase):
self.ctx.repo_dir,
)
@patch("pkgmgr.installers.makefile.run_command")
@patch("pkgmgr.actions.repository.install.installers.makefile.run_command")
@patch(
"builtins.open",
new_callable=mock_open,

View File

@@ -3,8 +3,8 @@ import unittest
from unittest import mock
from unittest.mock import patch
from pkgmgr.context import RepoContext
from pkgmgr.installers.nix_flake import NixFlakeInstaller
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.nix_flake import NixFlakeInstaller
class TestNixFlakeInstaller(unittest.TestCase):
@@ -39,7 +39,7 @@ class TestNixFlakeInstaller(unittest.TestCase):
@patch("os.path.exists", return_value=True)
@patch("shutil.which", return_value="/usr/bin/nix")
@mock.patch("pkgmgr.installers.nix_flake.run_command")
@mock.patch("pkgmgr.actions.repository.install.installers.nix_flake.run_command")
def test_run_removes_old_profile_and_installs_outputs(
self,
mock_run_command,
@@ -74,7 +74,7 @@ class TestNixFlakeInstaller(unittest.TestCase):
self.assertEqual(cmds[0], remove_cmd)
@patch("shutil.which", return_value="/usr/bin/nix")
@mock.patch("pkgmgr.installers.nix_flake.run_command")
@mock.patch("pkgmgr.actions.repository.install.installers.nix_flake.run_command")
def test_ensure_old_profile_removed_ignores_systemexit(
self,
mock_run_command,

View File

@@ -4,8 +4,8 @@ import os
import unittest
from unittest.mock import patch
from pkgmgr.context import RepoContext
from pkgmgr.installers.python import PythonInstaller
from pkgmgr.actions.repository.install.context import RepoContext
from pkgmgr.actions.repository.install.installers.python import PythonInstaller
class TestPythonInstaller(unittest.TestCase):
@@ -34,7 +34,7 @@ class TestPythonInstaller(unittest.TestCase):
def test_supports_false_when_no_pyproject(self, mock_exists):
self.assertFalse(self.installer.supports(self.ctx))
@patch("pkgmgr.installers.python.run_command")
@patch("pkgmgr.actions.repository.install.installers.python.run_command")
@patch("os.path.exists", side_effect=lambda path: path.endswith("pyproject.toml"))
def test_run_installs_project_from_pyproject(self, mock_exists, mock_run_command):
self.installer.run(self.ctx)

View File

@@ -4,12 +4,12 @@ import unittest
from types import SimpleNamespace
from unittest.mock import patch
from pkgmgr.branch_commands import open_branch
from pkgmgr.git_utils import GitError
from pkgmgr.actions.branch import open_branch
from pkgmgr.core.git import GitError
class TestOpenBranch(unittest.TestCase):
@patch("pkgmgr.branch_commands.run_git")
@patch("pkgmgr.actions.branch.run_git")
def test_open_branch_with_explicit_name_and_default_base(self, mock_run_git) -> None:
"""
open_branch(name, base='main') should:
@@ -42,7 +42,7 @@ class TestOpenBranch(unittest.TestCase):
self.assertEqual(kwargs.get("cwd"), cwd_expected)
@patch("builtins.input", return_value="feature/interactive")
@patch("pkgmgr.branch_commands.run_git")
@patch("pkgmgr.actions.branch.run_git")
def test_open_branch_prompts_for_name_if_missing(
self,
mock_run_git,
@@ -75,7 +75,7 @@ class TestOpenBranch(unittest.TestCase):
self.assertEqual(args[0], args_expected)
self.assertEqual(kwargs.get("cwd"), cwd_expected)
@patch("pkgmgr.branch_commands.run_git")
@patch("pkgmgr.actions.branch.run_git")
def test_open_branch_raises_runtimeerror_on_git_failure(self, mock_run_git) -> None:
"""
If a GitError occurs (e.g. fetch fails), open_branch should

View File

@@ -4,7 +4,7 @@ import os
import unittest
from unittest.mock import patch, mock_open
from pkgmgr.capabilities import (
from pkgmgr.actions.repository.install.capabilities import (
PythonRuntimeCapability,
MakeInstallCapability,
NixFlakeCapability,
@@ -31,7 +31,7 @@ class TestCapabilitiesDetectors(unittest.TestCase):
def setUp(self):
self.ctx = DummyCtx("/tmp/repo")
@patch("pkgmgr.capabilities.os.path.exists")
@patch("pkgmgr.actions.repository.install.capabilities.os.path.exists")
def test_python_runtime_python_layer_pyproject(self, mock_exists):
"""PythonRuntimeCapability: python layer is provided if pyproject.toml exists."""
cap = PythonRuntimeCapability()
@@ -47,8 +47,8 @@ class TestCapabilitiesDetectors(unittest.TestCase):
self.assertFalse(cap.is_provided(self.ctx, "nix"))
self.assertFalse(cap.is_provided(self.ctx, "os-packages"))
@patch("pkgmgr.capabilities._read_text_if_exists")
@patch("pkgmgr.capabilities.os.path.exists")
@patch("pkgmgr.actions.repository.install.capabilities._read_text_if_exists")
@patch("pkgmgr.actions.repository.install.capabilities.os.path.exists")
def test_python_runtime_nix_layer_flake(self, mock_exists, mock_read):
"""
PythonRuntimeCapability: nix layer is provided if flake.nix contains
@@ -65,7 +65,7 @@ class TestCapabilitiesDetectors(unittest.TestCase):
self.assertTrue(cap.applies_to_layer("nix"))
self.assertTrue(cap.is_provided(self.ctx, "nix"))
@patch("pkgmgr.capabilities.os.path.exists", return_value=True)
@patch("pkgmgr.actions.repository.install.capabilities.os.path.exists", return_value=True)
@patch(
"builtins.open",
new_callable=mock_open,
@@ -78,7 +78,7 @@ class TestCapabilitiesDetectors(unittest.TestCase):
self.assertTrue(cap.applies_to_layer("makefile"))
self.assertTrue(cap.is_provided(self.ctx, "makefile"))
@patch("pkgmgr.capabilities.os.path.exists")
@patch("pkgmgr.actions.repository.install.capabilities.os.path.exists")
def test_nix_flake_capability_on_nix_layer(self, mock_exists):
"""NixFlakeCapability: nix layer is provided if flake.nix exists."""
cap = NixFlakeCapability()
@@ -153,7 +153,7 @@ class TestDetectCapabilities(unittest.TestCase):
},
)
with patch("pkgmgr.capabilities.CAPABILITY_MATCHERS", [dummy1, dummy2]):
with patch("pkgmgr.actions.repository.install.capabilities.CAPABILITY_MATCHERS", [dummy1, dummy2]):
caps = detect_capabilities(self.ctx, layers)
self.assertEqual(
@@ -221,7 +221,7 @@ class TestResolveEffectiveCapabilities(unittest.TestCase):
)
with patch(
"pkgmgr.capabilities.CAPABILITY_MATCHERS",
"pkgmgr.actions.repository.install.capabilities.CAPABILITY_MATCHERS",
[cap_make_install, cap_python_runtime, cap_nix_flake],
):
effective = resolve_effective_capabilities(self.ctx, layers)
@@ -258,7 +258,7 @@ class TestResolveEffectiveCapabilities(unittest.TestCase):
)
with patch(
"pkgmgr.capabilities.CAPABILITY_MATCHERS",
"pkgmgr.actions.repository.install.capabilities.CAPABILITY_MATCHERS",
[cap_python_runtime],
):
effective = resolve_effective_capabilities(self.ctx, layers)
@@ -283,7 +283,7 @@ class TestResolveEffectiveCapabilities(unittest.TestCase):
},
)
with patch("pkgmgr.capabilities.CAPABILITY_MATCHERS", [cap_only_make]):
with patch("pkgmgr.actions.repository.install.capabilities.CAPABILITY_MATCHERS", [cap_only_make]):
effective = resolve_effective_capabilities(self.ctx, layers)
self.assertEqual(effective["makefile"], {"make-install"})
@@ -306,7 +306,7 @@ class TestResolveEffectiveCapabilities(unittest.TestCase):
},
)
with patch("pkgmgr.capabilities.CAPABILITY_MATCHERS", [cap_only_nix]):
with patch("pkgmgr.actions.repository.install.capabilities.CAPABILITY_MATCHERS", [cap_only_nix]):
effective = resolve_effective_capabilities(self.ctx, layers)
self.assertEqual(effective["makefile"], set())
@@ -337,7 +337,7 @@ class TestResolveEffectiveCapabilities(unittest.TestCase):
)
with patch(
"pkgmgr.capabilities.CAPABILITY_MATCHERS",
"pkgmgr.actions.repository.install.capabilities.CAPABILITY_MATCHERS",
[cap_python_runtime],
):
effective = resolve_effective_capabilities(self.ctx, layers)
@@ -359,7 +359,7 @@ class TestResolveEffectiveCapabilities(unittest.TestCase):
)
with patch(
"pkgmgr.capabilities.CAPABILITY_MATCHERS",
"pkgmgr.actions.repository.install.capabilities.CAPABILITY_MATCHERS",
[cap_dummy],
):
effective = resolve_effective_capabilities(self.ctx)

View File

@@ -3,13 +3,12 @@ from __future__ import annotations
import unittest
from unittest.mock import patch
from pkgmgr.changelog import generate_changelog
from pkgmgr.git_utils import GitError
from pkgmgr.cli_core.commands.changelog import _find_previous_and_current_tag
from pkgmgr.actions.changelog import generate_changelog
from pkgmgr.core.git import GitError
from pkgmgr.cli.commands.changelog import _find_previous_and_current_tag
class TestGenerateChangelog(unittest.TestCase):
@patch("pkgmgr.changelog.run_git")
@patch("pkgmgr.actions.changelog.run_git")
def test_generate_changelog_default_range_no_merges(self, mock_run_git) -> None:
"""
Default behaviour:
@@ -35,7 +34,7 @@ class TestGenerateChangelog(unittest.TestCase):
self.assertIn("HEAD", args[0])
self.assertEqual(kwargs.get("cwd"), "/repo")
@patch("pkgmgr.changelog.run_git")
@patch("pkgmgr.actions.changelog.run_git")
def test_generate_changelog_with_range_and_merges(self, mock_run_git) -> None:
"""
Explicit range and include_merges=True:
@@ -64,7 +63,7 @@ class TestGenerateChangelog(unittest.TestCase):
self.assertIn("v1.0.0..v1.1.0", cmd)
self.assertEqual(kwargs.get("cwd"), "/repo")
@patch("pkgmgr.changelog.run_git")
@patch("pkgmgr.actions.changelog.run_git")
def test_generate_changelog_giterror_returns_error_message(self, mock_run_git) -> None:
"""
If Git fails, we do NOT raise; instead we return a human readable error string.
@@ -77,7 +76,7 @@ class TestGenerateChangelog(unittest.TestCase):
self.assertIn("simulated git failure", result)
self.assertIn("v0.1.0..v0.2.0", result)
@patch("pkgmgr.changelog.run_git")
@patch("pkgmgr.actions.changelog.run_git")
def test_generate_changelog_empty_output_returns_info(self, mock_run_git) -> None:
"""
Empty git log output -> informational message instead of empty string.

View File

@@ -83,7 +83,7 @@ class TestCliVersion(unittest.TestCase):
# This matches the new behaviour: without explicit identifiers,
# version uses _select_repo_for_current_directory(ctx).
self._patch_select_repo_for_current_directory = mock.patch(
"pkgmgr.cli_core.dispatch._select_repo_for_current_directory",
"pkgmgr.cli.dispatch._select_repo_for_current_directory",
return_value=[self._fake_repo],
)
self.mock_select_repo_for_current_directory = (
@@ -166,7 +166,7 @@ class TestCliVersion(unittest.TestCase):
# Arrange: mock git tags used by handle_version
with mock.patch(
"pkgmgr.cli_core.commands.version.get_tags",
"pkgmgr.cli.commands.version.get_tags",
return_value=["v1.2.0", "v1.2.3", "v1.0.0"],
):
# Act
@@ -198,7 +198,7 @@ class TestCliVersion(unittest.TestCase):
# Arrange: mock git tags (latest is 1.2.3)
with mock.patch(
"pkgmgr.cli_core.commands.version.get_tags",
"pkgmgr.cli.commands.version.get_tags",
return_value=["v1.2.3"],
):
stdout = self._run_cli_version_and_capture()
@@ -226,7 +226,7 @@ class TestCliVersion(unittest.TestCase):
# Arrange: no tags returned
with mock.patch(
"pkgmgr.cli_core.commands.version.get_tags",
"pkgmgr.cli.commands.version.get_tags",
return_value=[],
):
stdout = self._run_cli_version_and_capture()

View File

@@ -4,8 +4,8 @@ import unittest
from types import SimpleNamespace
from unittest.mock import patch
from pkgmgr.cli_core.commands.branch import handle_branch
from pkgmgr.cli_core.context import CLIContext
from pkgmgr.cli.commands.branch import handle_branch
from pkgmgr.cli.context import CLIContext
class TestCliBranch(unittest.TestCase):
@@ -22,7 +22,7 @@ class TestCliBranch(unittest.TestCase):
user_config_path="/tmp/config.yaml",
)
@patch("pkgmgr.cli_core.commands.branch.open_branch")
@patch("pkgmgr.cli.commands.branch.open_branch")
def test_handle_branch_open_forwards_args_to_open_branch(self, mock_open_branch) -> None:
"""
handle_branch('open') should call open_branch with name, base and cwd='.'.
@@ -44,7 +44,7 @@ class TestCliBranch(unittest.TestCase):
self.assertEqual(call_kwargs.get("base_branch"), "develop")
self.assertEqual(call_kwargs.get("cwd"), ".")
@patch("pkgmgr.cli_core.commands.branch.open_branch")
@patch("pkgmgr.cli.commands.branch.open_branch")
def test_handle_branch_open_uses_default_base_when_not_set(self, mock_open_branch) -> None:
"""
If --base is not passed, argparse gives base='main' (default),
@@ -70,7 +70,7 @@ class TestCliBranch(unittest.TestCase):
# close subcommand
# ------------------------------------------------------------------
@patch("pkgmgr.cli_core.commands.branch.close_branch")
@patch("pkgmgr.cli.commands.branch.close_branch")
def test_handle_branch_close_forwards_args_to_close_branch(self, mock_close_branch) -> None:
"""
handle_branch('close') should call close_branch with name, base and cwd='.'.
@@ -92,7 +92,7 @@ class TestCliBranch(unittest.TestCase):
self.assertEqual(call_kwargs.get("base_branch"), "develop")
self.assertEqual(call_kwargs.get("cwd"), ".")
@patch("pkgmgr.cli_core.commands.branch.close_branch")
@patch("pkgmgr.cli.commands.branch.close_branch")
def test_handle_branch_close_uses_default_base_when_not_set(self, mock_close_branch) -> None:
"""
If --base is not passed for 'close', argparse gives base='main'

View File

@@ -2,7 +2,7 @@
import unittest
from unittest.mock import patch, MagicMock
from pkgmgr.clone_repos import clone_repos
from pkgmgr.actions.repository.clone import clone_repos
class TestCloneRepos(unittest.TestCase):
@@ -16,12 +16,12 @@ class TestCloneRepos(unittest.TestCase):
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")
@patch("pkgmgr.actions.repository.clone.verify_repository")
@patch("pkgmgr.actions.repository.clone.subprocess.run")
@patch("pkgmgr.actions.repository.clone.os.makedirs")
@patch("pkgmgr.actions.repository.clone.os.path.exists")
@patch("pkgmgr.actions.repository.clone.get_repo_dir")
@patch("pkgmgr.actions.repository.clone.get_repo_identifier")
def test_clone_ssh_mode_uses_ssh_url(
self,
mock_get_repo_identifier,
@@ -55,12 +55,12 @@ class TestCloneRepos(unittest.TestCase):
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")
@patch("pkgmgr.actions.repository.clone.verify_repository")
@patch("pkgmgr.actions.repository.clone.subprocess.run")
@patch("pkgmgr.actions.repository.clone.os.makedirs")
@patch("pkgmgr.actions.repository.clone.os.path.exists")
@patch("pkgmgr.actions.repository.clone.get_repo_dir")
@patch("pkgmgr.actions.repository.clone.get_repo_identifier")
def test_clone_https_mode_uses_https_url(
self,
mock_get_repo_identifier,
@@ -93,12 +93,12 @@ class TestCloneRepos(unittest.TestCase):
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")
@patch("pkgmgr.actions.repository.clone.verify_repository")
@patch("pkgmgr.actions.repository.clone.subprocess.run")
@patch("pkgmgr.actions.repository.clone.os.makedirs")
@patch("pkgmgr.actions.repository.clone.os.path.exists")
@patch("pkgmgr.actions.repository.clone.get_repo_dir")
@patch("pkgmgr.actions.repository.clone.get_repo_identifier")
def test_clone_shallow_mode_uses_https_with_depth(
self,
mock_get_repo_identifier,
@@ -131,12 +131,12 @@ class TestCloneRepos(unittest.TestCase):
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")
@patch("pkgmgr.actions.repository.clone.verify_repository")
@patch("pkgmgr.actions.repository.clone.subprocess.run")
@patch("pkgmgr.actions.repository.clone.os.makedirs")
@patch("pkgmgr.actions.repository.clone.os.path.exists")
@patch("pkgmgr.actions.repository.clone.get_repo_dir")
@patch("pkgmgr.actions.repository.clone.get_repo_identifier")
def test_preview_mode_does_not_call_subprocess_run(
self,
mock_get_repo_identifier,

View File

@@ -1,5 +1,5 @@
import unittest
from pkgmgr.context import RepoContext
from pkgmgr.actions.repository.install.context import RepoContext
class TestRepoContext(unittest.TestCase):

View File

@@ -1,32 +1,32 @@
# tests/unit/pkgmgr/test_create_ink.py
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import unittest
from unittest.mock import patch
import pkgmgr.create_ink as create_ink_module
import pkgmgr.core.command.ink as create_ink_module
class TestCreateInk(unittest.TestCase):
@patch("pkgmgr.create_ink.get_repo_dir")
@patch("pkgmgr.create_ink.get_repo_identifier")
@patch("pkgmgr.core.command.ink.get_repo_dir")
@patch("pkgmgr.core.command.ink.get_repo_identifier")
def test_create_ink_skips_when_no_command(
self,
mock_get_repo_identifier,
mock_get_repo_dir,
):
repo = {} # no 'command' key
repo = {"name": "test-repo", "command": None}
mock_get_repo_identifier.return_value = "test-id"
mock_get_repo_dir.return_value = "/repos/test-id"
with patch("pkgmgr.create_ink.os.makedirs") as mock_makedirs, \
patch("pkgmgr.create_ink.os.symlink") as mock_symlink, \
patch("pkgmgr.create_ink.os.chmod") as mock_chmod:
with patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, \
patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, \
patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod:
create_ink_module.create_ink(
repo=repo,
repositories_base_dir="/repos",
bin_dir="/bin",
all_repos=[repo],
quiet=True,
preview=False,
)
@@ -34,26 +34,25 @@ class TestCreateInk(unittest.TestCase):
mock_symlink.assert_not_called()
mock_chmod.assert_not_called()
@patch("pkgmgr.create_ink.get_repo_dir")
@patch("pkgmgr.create_ink.get_repo_identifier")
@patch("pkgmgr.core.command.ink.get_repo_dir")
@patch("pkgmgr.core.command.ink.get_repo_identifier")
def test_create_ink_preview_only(
self,
mock_get_repo_identifier,
mock_get_repo_dir,
):
repo = {"command": "/repos/test-id/main.py"}
repo = {"name": "test-repo", "command": "repo-cmd"}
mock_get_repo_identifier.return_value = "test-id"
mock_get_repo_dir.return_value = "/repos/test-id"
with patch("pkgmgr.create_ink.os.makedirs") as mock_makedirs, \
patch("pkgmgr.create_ink.os.symlink") as mock_symlink, \
patch("pkgmgr.create_ink.os.chmod") as mock_chmod:
with patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, \
patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, \
patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod:
create_ink_module.create_ink(
repo=repo,
repositories_base_dir="/repos",
bin_dir="/bin",
all_repos=[repo],
quiet=True,
preview=True,
)
@@ -61,8 +60,8 @@ class TestCreateInk(unittest.TestCase):
mock_symlink.assert_not_called()
mock_chmod.assert_not_called()
@patch("pkgmgr.create_ink.get_repo_dir")
@patch("pkgmgr.create_ink.get_repo_identifier")
@patch("pkgmgr.core.command.ink.get_repo_dir")
@patch("pkgmgr.core.command.ink.get_repo_identifier")
def test_create_ink_creates_symlink_and_alias(
self,
mock_get_repo_identifier,
@@ -75,19 +74,18 @@ class TestCreateInk(unittest.TestCase):
mock_get_repo_identifier.return_value = "test-id"
mock_get_repo_dir.return_value = "/repos/test-id"
with patch("pkgmgr.create_ink.os.makedirs") as mock_makedirs, \
patch("pkgmgr.create_ink.os.symlink") as mock_symlink, \
patch("pkgmgr.create_ink.os.chmod") as mock_chmod, \
patch("pkgmgr.create_ink.os.path.exists", return_value=False), \
patch("pkgmgr.create_ink.os.path.islink", return_value=False), \
patch("pkgmgr.create_ink.os.remove") as mock_remove, \
patch("pkgmgr.create_ink.os.path.realpath", side_effect=lambda p: p):
with patch("pkgmgr.core.command.ink.os.makedirs") as mock_makedirs, \
patch("pkgmgr.core.command.ink.os.symlink") as mock_symlink, \
patch("pkgmgr.core.command.ink.os.chmod") as mock_chmod, \
patch("pkgmgr.core.command.ink.os.path.exists", return_value=False), \
patch("pkgmgr.core.command.ink.os.path.islink", return_value=False), \
patch("pkgmgr.core.command.ink.os.remove") as mock_remove, \
patch("pkgmgr.core.command.ink.os.path.realpath", side_effect=lambda p: p):
create_ink_module.create_ink(
repo=repo,
repositories_base_dir="/repos",
bin_dir="/bin",
all_repos=[repo],
quiet=True,
preview=False,
)
@@ -95,8 +93,6 @@ class TestCreateInk(unittest.TestCase):
self.assertEqual(mock_symlink.call_count, 2)
mock_makedirs.assert_called_once()
mock_chmod.assert_called_once()
mock_remove.assert_not_called()
if __name__ == "__main__":
unittest.main()

View File

@@ -6,7 +6,7 @@ import unittest
from types import SimpleNamespace
from unittest.mock import patch
from pkgmgr.git_utils import (
from pkgmgr.core.git import (
GitError,
run_git,
get_tags,
@@ -16,7 +16,7 @@ from pkgmgr.git_utils import (
class TestGitUtils(unittest.TestCase):
@patch("pkgmgr.git_utils.subprocess.run")
@patch("pkgmgr.core.git.subprocess.run")
def test_run_git_success(self, mock_run):
mock_run.return_value = SimpleNamespace(
stdout="ok\n",
@@ -33,7 +33,7 @@ class TestGitUtils(unittest.TestCase):
self.assertEqual(args[0][0], "git")
self.assertEqual(kwargs.get("cwd"), "/tmp/repo")
@patch("pkgmgr.git_utils.subprocess.run")
@patch("pkgmgr.core.git.subprocess.run")
def test_run_git_failure_raises_giterror(self, mock_run):
mock_run.side_effect = subprocess.CalledProcessError(
returncode=1,
@@ -51,7 +51,7 @@ class TestGitUtils(unittest.TestCase):
self.assertIn("bad", msg)
self.assertIn("error", msg)
@patch("pkgmgr.git_utils.subprocess.run")
@patch("pkgmgr.core.git.subprocess.run")
def test_get_tags_empty(self, mock_run):
mock_run.return_value = SimpleNamespace(
stdout="",
@@ -62,7 +62,7 @@ class TestGitUtils(unittest.TestCase):
tags = get_tags(cwd="/tmp/repo")
self.assertEqual(tags, [])
@patch("pkgmgr.git_utils.subprocess.run")
@patch("pkgmgr.core.git.subprocess.run")
def test_get_tags_non_empty(self, mock_run):
mock_run.return_value = SimpleNamespace(
stdout="v1.0.0\nv1.1.0\n",
@@ -73,7 +73,7 @@ class TestGitUtils(unittest.TestCase):
tags = get_tags(cwd="/tmp/repo")
self.assertEqual(tags, ["v1.0.0", "v1.1.0"])
@patch("pkgmgr.git_utils.subprocess.run")
@patch("pkgmgr.core.git.subprocess.run")
def test_get_head_commit_success(self, mock_run):
mock_run.return_value = SimpleNamespace(
stdout="abc123\n",
@@ -84,7 +84,7 @@ class TestGitUtils(unittest.TestCase):
commit = get_head_commit(cwd="/tmp/repo")
self.assertEqual(commit, "abc123")
@patch("pkgmgr.git_utils.subprocess.run")
@patch("pkgmgr.core.git.subprocess.run")
def test_get_head_commit_failure_returns_none(self, mock_run):
mock_run.side_effect = subprocess.CalledProcessError(
returncode=1,
@@ -96,7 +96,7 @@ class TestGitUtils(unittest.TestCase):
commit = get_head_commit(cwd="/tmp/repo")
self.assertIsNone(commit)
@patch("pkgmgr.git_utils.subprocess.run")
@patch("pkgmgr.core.git.subprocess.run")
def test_get_current_branch_success(self, mock_run):
mock_run.return_value = SimpleNamespace(
stdout="main\n",
@@ -107,7 +107,7 @@ class TestGitUtils(unittest.TestCase):
branch = get_current_branch(cwd="/tmp/repo")
self.assertEqual(branch, "main")
@patch("pkgmgr.git_utils.subprocess.run")
@patch("pkgmgr.core.git.subprocess.run")
def test_get_current_branch_failure_returns_none(self, mock_run):
mock_run.side_effect = subprocess.CalledProcessError(
returncode=1,

View File

@@ -3,9 +3,9 @@
import unittest
from unittest.mock import patch, MagicMock
from pkgmgr.context import RepoContext
import pkgmgr.install_repos as install_module
from pkgmgr.installers.base import BaseInstaller
from pkgmgr.actions.repository.install.context import RepoContext
import pkgmgr.actions.repository.install as install_module
from pkgmgr.actions.repository.install.installers.base import BaseInstaller
class DummyInstaller(BaseInstaller):
@@ -25,12 +25,12 @@ class DummyInstaller(BaseInstaller):
class TestInstallReposOrchestration(unittest.TestCase):
@patch("pkgmgr.install_repos.create_ink")
@patch("pkgmgr.install_repos.resolve_command_for_repo")
@patch("pkgmgr.install_repos.verify_repository")
@patch("pkgmgr.install_repos.get_repo_dir")
@patch("pkgmgr.install_repos.get_repo_identifier")
@patch("pkgmgr.install_repos.clone_repos")
@patch("pkgmgr.actions.repository.install.create_ink")
@patch("pkgmgr.actions.repository.install.resolve_command_for_repo")
@patch("pkgmgr.actions.repository.install.verify_repository")
@patch("pkgmgr.actions.repository.install.get_repo_dir")
@patch("pkgmgr.actions.repository.install.get_repo_identifier")
@patch("pkgmgr.actions.repository.install.clone_repos")
def test_install_repos_runs_pipeline_for_each_repo(
self,
mock_clone_repos,
@@ -82,10 +82,10 @@ class TestInstallReposOrchestration(unittest.TestCase):
self.assertEqual(mock_verify_repository.call_count, 2)
self.assertEqual(mock_resolve_command_for_repo.call_count, 2)
@patch("pkgmgr.install_repos.verify_repository")
@patch("pkgmgr.install_repos.get_repo_dir")
@patch("pkgmgr.install_repos.get_repo_identifier")
@patch("pkgmgr.install_repos.clone_repos")
@patch("pkgmgr.actions.repository.install.verify_repository")
@patch("pkgmgr.actions.repository.install.get_repo_dir")
@patch("pkgmgr.actions.repository.install.get_repo_identifier")
@patch("pkgmgr.actions.repository.install.clone_repos")
def test_install_repos_skips_on_failed_verification(
self,
mock_clone_repos,
@@ -104,10 +104,10 @@ class TestInstallReposOrchestration(unittest.TestCase):
mock_verify_repository.return_value = (False, ["sig error"], None, None)
dummy_installer = DummyInstaller()
with patch("os.path.exists", return_value=True), \
patch("pkgmgr.install_repos.create_ink") as mock_create_ink, \
patch("pkgmgr.install_repos.resolve_command_for_repo") as mock_resolve_cmd, \
patch("builtins.input", return_value="n"):
with patch("pkgmgr.actions.repository.install.create_ink") as mock_create_ink, \
patch("pkgmgr.actions.repository.install.resolve_command_for_repo") as mock_resolve_cmd, \
patch("os.path.exists", return_value=True), \
patch("builtins.input", return_value="n"):
old_installers = install_module.INSTALLERS
install_module.INSTALLERS = [dummy_installer]
try:

View File

@@ -6,8 +6,8 @@ import textwrap
import unittest
from unittest.mock import patch
from pkgmgr.versioning import SemVer
from pkgmgr.release import (
from pkgmgr.core.version.semver import SemVer
from pkgmgr.actions.release import (
_determine_current_version,
_bump_semver,
update_pyproject_version,
@@ -21,7 +21,7 @@ from pkgmgr.release import (
class TestDetermineCurrentVersion(unittest.TestCase):
@patch("pkgmgr.release.get_tags", return_value=[])
@patch("pkgmgr.actions.release.get_tags", return_value=[])
def test_determine_current_version_no_tags_returns_zero(
self,
mock_get_tags,
@@ -31,8 +31,8 @@ class TestDetermineCurrentVersion(unittest.TestCase):
self.assertEqual((ver.major, ver.minor, ver.patch), (0, 0, 0))
mock_get_tags.assert_called_once()
@patch("pkgmgr.release.find_latest_version")
@patch("pkgmgr.release.get_tags")
@patch("pkgmgr.actions.release.find_latest_version")
@patch("pkgmgr.actions.release.get_tags")
def test_determine_current_version_uses_latest_semver_tag(
self,
mock_get_tags,
@@ -365,17 +365,17 @@ class TestUpdateDebianChangelog(unittest.TestCase):
class TestReleaseOrchestration(unittest.TestCase):
@patch("pkgmgr.release.sys.stdin.isatty", return_value=False)
@patch("pkgmgr.release._run_git_command")
@patch("pkgmgr.release.update_debian_changelog")
@patch("pkgmgr.release.update_spec_version")
@patch("pkgmgr.release.update_pkgbuild_version")
@patch("pkgmgr.release.update_flake_version")
@patch("pkgmgr.release.get_current_branch", return_value="develop")
@patch("pkgmgr.release.update_changelog")
@patch("pkgmgr.release.update_pyproject_version")
@patch("pkgmgr.release._bump_semver")
@patch("pkgmgr.release._determine_current_version")
@patch("pkgmgr.actions.release.sys.stdin.isatty", return_value=False)
@patch("pkgmgr.actions.release._run_git_command")
@patch("pkgmgr.actions.release.update_debian_changelog")
@patch("pkgmgr.actions.release.update_spec_version")
@patch("pkgmgr.actions.release.update_pkgbuild_version")
@patch("pkgmgr.actions.release.update_flake_version")
@patch("pkgmgr.actions.release.get_current_branch", return_value="develop")
@patch("pkgmgr.actions.release.update_changelog")
@patch("pkgmgr.actions.release.update_pyproject_version")
@patch("pkgmgr.actions.release._bump_semver")
@patch("pkgmgr.actions.release._determine_current_version")
def test_release_happy_path_uses_helpers_and_git(
self,
mock_determine_current_version,
@@ -451,17 +451,17 @@ class TestReleaseOrchestration(unittest.TestCase):
self.assertIn("git push origin develop", git_calls)
self.assertIn("git push origin --tags", git_calls)
@patch("pkgmgr.release.sys.stdin.isatty", return_value=False)
@patch("pkgmgr.release._run_git_command")
@patch("pkgmgr.release.update_debian_changelog")
@patch("pkgmgr.release.update_spec_version")
@patch("pkgmgr.release.update_pkgbuild_version")
@patch("pkgmgr.release.update_flake_version")
@patch("pkgmgr.release.get_current_branch", return_value="develop")
@patch("pkgmgr.release.update_changelog")
@patch("pkgmgr.release.update_pyproject_version")
@patch("pkgmgr.release._bump_semver")
@patch("pkgmgr.release._determine_current_version")
@patch("pkgmgr.actions.release.sys.stdin.isatty", return_value=False)
@patch("pkgmgr.actions.release._run_git_command")
@patch("pkgmgr.actions.release.update_debian_changelog")
@patch("pkgmgr.actions.release.update_spec_version")
@patch("pkgmgr.actions.release.update_pkgbuild_version")
@patch("pkgmgr.actions.release.update_flake_version")
@patch("pkgmgr.actions.release.get_current_branch", return_value="develop")
@patch("pkgmgr.actions.release.update_changelog")
@patch("pkgmgr.actions.release.update_pyproject_version")
@patch("pkgmgr.actions.release._bump_semver")
@patch("pkgmgr.actions.release._determine_current_version")
def test_release_preview_mode_skips_git_and_uses_preview_flag(
self,
mock_determine_current_version,

View File

@@ -3,7 +3,7 @@
import unittest
from unittest.mock import patch
import pkgmgr.resolve_command as resolve_command_module
import pkgmgr.core.command.resolve as resolve_command_module
class TestResolveCommandForRepo(unittest.TestCase):
@@ -16,7 +16,7 @@ class TestResolveCommandForRepo(unittest.TestCase):
)
self.assertEqual(result, "/custom/cmd")
@patch("pkgmgr.resolve_command.shutil.which", return_value="/usr/bin/tool")
@patch("pkgmgr.core.command.resolve.shutil.which", return_value="/usr/bin/tool")
def test_system_binary_returns_none_and_no_error(self, mock_which):
repo = {}
result = resolve_command_module.resolve_command_for_repo(
@@ -27,10 +27,10 @@ class TestResolveCommandForRepo(unittest.TestCase):
# System binary → no link
self.assertIsNone(result)
@patch("pkgmgr.resolve_command.os.access")
@patch("pkgmgr.resolve_command.os.path.exists")
@patch("pkgmgr.resolve_command.shutil.which", return_value=None)
@patch("pkgmgr.resolve_command.os.path.expanduser", return_value="/fakehome")
@patch("pkgmgr.core.command.resolve.os.access")
@patch("pkgmgr.core.command.resolve.os.path.exists")
@patch("pkgmgr.core.command.resolve.shutil.which", return_value=None)
@patch("pkgmgr.core.command.resolve.os.path.expanduser", return_value="/fakehome")
def test_nix_profile_binary(
self,
mock_expanduser,
@@ -64,10 +64,10 @@ class TestResolveCommandForRepo(unittest.TestCase):
)
self.assertEqual(result, nix_path)
@patch("pkgmgr.resolve_command.os.access")
@patch("pkgmgr.resolve_command.os.path.exists")
@patch("pkgmgr.resolve_command.os.path.expanduser", return_value="/home/user")
@patch("pkgmgr.resolve_command.shutil.which", return_value="/home/user/.local/bin/tool")
@patch("pkgmgr.core.command.resolve.os.access")
@patch("pkgmgr.core.command.resolve.os.path.exists")
@patch("pkgmgr.core.command.resolve.os.path.expanduser", return_value="/home/user")
@patch("pkgmgr.core.command.resolve.shutil.which", return_value="/home/user/.local/bin/tool")
def test_non_system_binary_on_path(
self,
mock_which,
@@ -102,10 +102,10 @@ class TestResolveCommandForRepo(unittest.TestCase):
)
self.assertEqual(result, non_system_path)
@patch("pkgmgr.resolve_command.os.access")
@patch("pkgmgr.resolve_command.os.path.exists")
@patch("pkgmgr.resolve_command.shutil.which", return_value=None)
@patch("pkgmgr.resolve_command.os.path.expanduser", return_value="/fakehome")
@patch("pkgmgr.core.command.resolve.os.access")
@patch("pkgmgr.core.command.resolve.os.path.exists")
@patch("pkgmgr.core.command.resolve.shutil.which", return_value=None)
@patch("pkgmgr.core.command.resolve.os.path.expanduser", return_value="/fakehome")
def test_fallback_to_main_py(
self,
mock_expanduser,
@@ -136,10 +136,10 @@ class TestResolveCommandForRepo(unittest.TestCase):
)
self.assertEqual(result, main_py)
@patch("pkgmgr.resolve_command.os.access", return_value=False)
@patch("pkgmgr.resolve_command.os.path.exists", return_value=False)
@patch("pkgmgr.resolve_command.shutil.which", return_value=None)
@patch("pkgmgr.resolve_command.os.path.expanduser", return_value="/fakehome")
@patch("pkgmgr.core.command.resolve.os.access", return_value=False)
@patch("pkgmgr.core.command.resolve.os.path.exists", return_value=False)
@patch("pkgmgr.core.command.resolve.shutil.which", return_value=None)
@patch("pkgmgr.core.command.resolve.os.path.expanduser", return_value="/fakehome")
def test_no_command_results_in_system_exit(
self,
mock_expanduser,

View File

@@ -3,7 +3,7 @@
import unittest
from pkgmgr.versioning import (
from pkgmgr.core.version.semver import (
SemVer,
is_semver_tag,
extract_semver_from_tags,