From e909e4f5701a11b98f4d76ae963e5e08ac9feea1 Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Fri, 1 Apr 2022 18:30:11 +0300 Subject: [PATCH] add configurable exit codes to some commands (#55) --- docker/entrypoint.sh | 1 + src/ahriman/application/ahriman.py | 8 ++++ .../application/application/repository.py | 7 ++- src/ahriman/application/handlers/add.py | 3 +- src/ahriman/application/handlers/handler.py | 10 +++++ src/ahriman/application/handlers/patch.py | 7 ++- src/ahriman/application/handlers/rebuild.py | 5 ++- .../application/handlers/remove_unknown.py | 1 + src/ahriman/application/handlers/search.py | 4 +- src/ahriman/application/handlers/status.py | 2 + src/ahriman/application/handlers/update.py | 4 +- src/ahriman/application/handlers/user.py | 6 ++- src/ahriman/models/result.py | 7 +++ .../application/handlers/test_handler.py | 11 +++++ .../application/handlers/test_handler_add.py | 25 ++++++++++- .../handlers/test_handler_patch.py | 22 +++++----- .../handlers/test_handler_rebuild.py | 44 ++++++++++++++++++- .../handlers/test_handler_search.py | 19 +++++++- .../handlers/test_handler_status.py | 18 ++++++++ .../handlers/test_handler_update.py | 44 ++++++++++++++++++- .../application/handlers/test_handler_user.py | 22 +++++++++- tests/ahriman/models/test_result.py | 37 ++++++++++++++++ 22 files changed, 279 insertions(+), 28 deletions(-) diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh index ff571f92..1d3b6eef 100755 --- a/docker/entrypoint.sh +++ b/docker/entrypoint.sh @@ -36,6 +36,7 @@ if [ -n "$AHRIMAN_API_USER" ]; then # python getpass does not read from stdin # see thread https://mail.python.org/pipermail/python-dev/2008-February/077235.html # WARNING with debug mode password will be put to stdout + ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" user-list --error-on-empty "$AHRIMAN_API_USER" > /dev/null || ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" user-add --as-service --role write --secure "$AHRIMAN_API_USER" -p "$(openssl rand -base64 20)" fi diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index ab69d1ab..6c5a6cc5 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -112,6 +112,7 @@ def _set_aur_search_parser(root: SubParserAction) -> argparse.ArgumentParser: description="search for package in AUR using API", formatter_class=_formatter) parser.add_argument("search", help="search terms, can be specified multiple times, result will match all terms", nargs="+") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-i", "--info", help="show additional package information", action="store_true") parser.add_argument("--sort-by", help="sort field by this field. In case if two packages have the same value of " "the specified field, they will be always sorted by name", @@ -189,6 +190,7 @@ def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser: "5) and finally you can add package from AUR.", formatter_class=_formatter) parser.add_argument("package", help="package source (base name, path to local files, remote URL)", nargs="+") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-n", "--now", help="run update function after", action="store_true") parser.add_argument("-s", "--source", help="explicitly specify the package source for this command", type=PackageSource, choices=PackageSource, default=PackageSource.Auto) @@ -222,6 +224,7 @@ def _set_package_status_parser(root: SubParserAction) -> argparse.ArgumentParser formatter_class=_formatter) parser.add_argument("package", help="filter status by package base", nargs="*") parser.add_argument("--ahriman", help="get service status itself", action="store_true") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-i", "--info", help="show additional package information", action="store_true") parser.add_argument("-s", "--status", help="filter packages by status", type=BuildStatusEnum, choices=BuildStatusEnum) @@ -293,6 +296,7 @@ def _set_patch_list_parser(root: SubParserAction) -> argparse.ArgumentParser: parser = root.add_parser("patch-list", help="list patch sets", description="list available patches for the package", formatter_class=_formatter) parser.add_argument("package", help="package base", nargs="?") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.set_defaults(handler=handlers.Patch, action=Action.List, architecture=[""], lock=None, no_report=True) return parser @@ -320,6 +324,7 @@ def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser: description="check for packages updates. Same as update --dry-run --no-manual", formatter_class=_formatter) parser.add_argument("package", help="filter check by package base", nargs="*") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("--no-vcs", help="do not check VCS packages", action="store_true") parser.set_defaults(handler=handlers.Update, dry_run=True, no_aur=False, no_local=False, no_manual=True) return parser @@ -369,6 +374,7 @@ def _set_repo_rebuild_parser(root: SubParserAction) -> argparse.ArgumentParser: parser.add_argument("--depends-on", help="only rebuild packages that depend on specified package", action="append") parser.add_argument("--dry-run", help="just perform check for packages without rebuild process itself", action="store_true") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.set_defaults(handler=handlers.Rebuild) return parser @@ -484,6 +490,7 @@ def _set_repo_update_parser(root: SubParserAction) -> argparse.ArgumentParser: formatter_class=_formatter) parser.add_argument("package", help="filter check by package base", nargs="*") parser.add_argument("--dry-run", help="just perform check for updates, same as check command", action="store_true") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("--no-aur", help="do not check for AUR updates. Implies --no-vcs", action="store_true") parser.add_argument("--no-local", help="do not check local packages for updates", action="store_true") parser.add_argument("--no-manual", help="do not include manual updates", action="store_true") @@ -524,6 +531,7 @@ def _set_user_list_parser(root: SubParserAction) -> argparse.ArgumentParser: description="list users from the user mapping and their roles", formatter_class=_formatter) parser.add_argument("username", help="filter users by username", nargs="?") + parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-r", "--role", help="filter users by role", type=UserAccess, choices=UserAccess) parser.set_defaults(handler=handlers.User, action=Action.List, architecture=[""], lock=None, no_report=True, # nosec password="", quiet=True, unsafe=True) diff --git a/src/ahriman/application/application/repository.py b/src/ahriman/application/application/repository.py index 0fb0618f..d021cb23 100644 --- a/src/ahriman/application/application/repository.py +++ b/src/ahriman/application/application/repository.py @@ -132,7 +132,7 @@ class Repository(Properties): result.extend(unknown_aur(package)) # local package not found return result - def update(self, updates: Iterable[Package]) -> None: + def update(self, updates: Iterable[Package]) -> Result: """ run package updates :param updates: list of packages to update @@ -144,8 +144,9 @@ class Repository(Properties): self._finalize(result.merge(update_result)) # process built packages + build_result = Result() packages = self.repository.packages_built() - process_update(packages, Result()) + process_update(packages, build_result) # process manual packages tree = Tree.load(updates, self.database) @@ -155,6 +156,8 @@ class Repository(Properties): packages = self.repository.packages_built() process_update(packages, build_result) + return build_result + def updates(self, filter_packages: Iterable[str], no_aur: bool, no_local: bool, no_manual: bool, no_vcs: bool, log_fn: Callable[[str], None]) -> List[Package]: """ diff --git a/src/ahriman/application/handlers/add.py b/src/ahriman/application/handlers/add.py index 2dbe406d..48e4d0ca 100644 --- a/src/ahriman/application/handlers/add.py +++ b/src/ahriman/application/handlers/add.py @@ -48,4 +48,5 @@ class Add(Handler): return packages = application.updates(args.package, True, True, False, True, application.logger.info) - application.update(packages) + result = application.update(packages) + Add.check_if_empty(args.exit_code, result.is_empty) diff --git a/src/ahriman/application/handlers/handler.py b/src/ahriman/application/handlers/handler.py index eafa760c..001b92cb 100644 --- a/src/ahriman/application/handlers/handler.py +++ b/src/ahriman/application/handlers/handler.py @@ -119,3 +119,13 @@ class Handler: :param unsafe: if set no user check will be performed before path creation """ raise NotImplementedError + + @staticmethod + def check_if_empty(enabled: bool, predicate: bool) -> None: + """ + check condition and flag and raise ExitCode exception in case if it is enabled and condition match + :param enabled: if False no check will be performed + :param predicate: indicates condition on which exception should be thrown + """ + if enabled and predicate: + raise ExitCode() diff --git a/src/ahriman/application/handlers/patch.py b/src/ahriman/application/handlers/patch.py index 140f19e3..7b79223e 100644 --- a/src/ahriman/application/handlers/patch.py +++ b/src/ahriman/application/handlers/patch.py @@ -51,7 +51,7 @@ class Patch(Handler): application = Application(architecture, configuration, no_report, unsafe) if args.action == Action.List: - Patch.patch_set_list(application, args.package) + Patch.patch_set_list(application, args.package, args.exit_code) elif args.action == Action.Remove: Patch.patch_set_remove(application, args.package) elif args.action == Action.Update: @@ -71,13 +71,16 @@ class Patch(Handler): application.database.patches_insert(package.base, patch) @staticmethod - def patch_set_list(application: Application, package_base: Optional[str]) -> None: + def patch_set_list(application: Application, package_base: Optional[str], exit_code: bool) -> None: """ list patches available for the package base :param application: application instance :param package_base: package base + :param exit_code: raise ExitCode on empty search result """ patches = application.database.patches_list(package_base) + Patch.check_if_empty(exit_code, not patches) + for base, patch in patches.items(): content = base if package_base is None else patch StringPrinter(content).print(verbose=True) diff --git a/src/ahriman/application/handlers/rebuild.py b/src/ahriman/application/handlers/rebuild.py index f662197b..be686806 100644 --- a/src/ahriman/application/handlers/rebuild.py +++ b/src/ahriman/application/handlers/rebuild.py @@ -47,9 +47,12 @@ class Rebuild(Handler): application = Application(architecture, configuration, no_report, unsafe) updates = application.repository.packages_depends_on(depends_on) + + Rebuild.check_if_empty(args.exit_code, not updates) if args.dry_run: for package in updates: UpdatePrinter(package, package.version).print(verbose=True) return - application.update(updates) + result = application.update(updates) + Rebuild.check_if_empty(args.exit_code, result.is_empty) diff --git a/src/ahriman/application/handlers/remove_unknown.py b/src/ahriman/application/handlers/remove_unknown.py index 0caa6383..c10a1f2d 100644 --- a/src/ahriman/application/handlers/remove_unknown.py +++ b/src/ahriman/application/handlers/remove_unknown.py @@ -45,6 +45,7 @@ class RemoveUnknown(Handler): """ application = Application(architecture, configuration, no_report, unsafe) unknown_packages = application.unknown() + if args.dry_run: for package in sorted(unknown_packages): StringPrinter(package).print(args.info) diff --git a/src/ahriman/application/handlers/search.py b/src/ahriman/application/handlers/search.py index a89a37c3..846b7eba 100644 --- a/src/ahriman/application/handlers/search.py +++ b/src/ahriman/application/handlers/search.py @@ -37,8 +37,7 @@ class Search(Handler): """ ALLOW_AUTO_ARCHITECTURE_RUN = False # it should be called only as "no-architecture" - # later we will have to remove some fields from here (lists) - SORT_FIELDS = {pair.name for pair in fields(AURPackage)} + SORT_FIELDS = {field.name for field in fields(AURPackage) if field.default_factory is not list} @classmethod def run(cls: Type[Handler], args: argparse.Namespace, architecture: str, @@ -52,6 +51,7 @@ class Search(Handler): :param unsafe: if set no user check will be performed before path creation """ packages_list = AUR.multisearch(*args.search) + Search.check_if_empty(args.exit_code, not packages_list) for package in Search.sort(packages_list, args.sort_by): AurPrinter(package).print(args.info) diff --git a/src/ahriman/application/handlers/status.py b/src/ahriman/application/handlers/status.py index 34715a95..7a20d222 100644 --- a/src/ahriman/application/handlers/status.py +++ b/src/ahriman/application/handlers/status.py @@ -60,6 +60,8 @@ class Status(Handler): else: packages = client.get(None) + Status.check_if_empty(args.exit_code, not packages) + comparator: Callable[[Tuple[Package, BuildStatus]], str] = lambda item: item[0].base filter_fn: Callable[[Tuple[Package, BuildStatus]], bool] =\ lambda item: args.status is None or item[1].status == args.status diff --git a/src/ahriman/application/handlers/update.py b/src/ahriman/application/handlers/update.py index 8d9d5950..216b4e88 100644 --- a/src/ahriman/application/handlers/update.py +++ b/src/ahriman/application/handlers/update.py @@ -45,10 +45,12 @@ class Update(Handler): application = Application(architecture, configuration, no_report, unsafe) packages = application.updates(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, Update.log_fn(application, args.dry_run)) + Update.check_if_empty(args.exit_code, not packages) if args.dry_run: return - application.update(packages) + result = application.update(packages) + Update.check_if_empty(args.exit_code, result.is_empty) @staticmethod def log_fn(application: Application, dry_run: bool) -> Callable[[str], None]: diff --git a/src/ahriman/application/handlers/user.py b/src/ahriman/application/handlers/user.py index ac684450..c65beabf 100644 --- a/src/ahriman/application/handlers/user.py +++ b/src/ahriman/application/handlers/user.py @@ -60,8 +60,10 @@ class User(Handler): User.configuration_create(auth_configuration, user, salt, args.as_service, args.secure) database.user_update(user.hash_password(salt)) elif args.action == Action.List: - for found_user in database.user_list(args.username, args.access): - UserPrinter(found_user).print(verbose=True) + users = database.user_list(args.username, args.role) + User.check_if_empty(args.exit_code, not users) + for user in users: + UserPrinter(user).print(verbose=True) elif args.action == Action.Remove: database.user_remove(args.username) diff --git a/src/ahriman/models/result.py b/src/ahriman/models/result.py index 3d8b5afd..cd38ed65 100644 --- a/src/ahriman/models/result.py +++ b/src/ahriman/models/result.py @@ -48,6 +48,13 @@ class Result: """ return list(self._failed.values()) + @property + def is_empty(self) -> bool: + """ + :return: True in case if success list is empty and False otherwise + """ + return not bool(self._success) + @property def success(self) -> List[Package]: """ diff --git a/tests/ahriman/application/handlers/test_handler.py b/tests/ahriman/application/handlers/test_handler.py index f8c6f842..83c62ca0 100644 --- a/tests/ahriman/application/handlers/test_handler.py +++ b/tests/ahriman/application/handlers/test_handler.py @@ -136,3 +136,14 @@ def test_run(args: argparse.Namespace, configuration: Configuration) -> None: """ with pytest.raises(NotImplementedError): Handler.run(args, "x86_64", configuration, True, True) + + +def test_check_if_empty() -> None: + """ + must raise exception in case if predicate is True and enabled + """ + Handler.check_if_empty(False, False) + Handler.check_if_empty(True, False) + Handler.check_if_empty(False, True) + with pytest.raises(ExitCode): + Handler.check_if_empty(True, True) diff --git a/tests/ahriman/application/handlers/test_handler_add.py b/tests/ahriman/application/handlers/test_handler_add.py index 3123771c..108ee9fc 100644 --- a/tests/ahriman/application/handlers/test_handler_add.py +++ b/tests/ahriman/application/handlers/test_handler_add.py @@ -7,6 +7,7 @@ from ahriman.application.handlers import Add from ahriman.core.configuration import Configuration from ahriman.models.package import Package from ahriman.models.package_source import PackageSource +from ahriman.models.result import Result def _default_args(args: argparse.Namespace) -> argparse.Namespace: @@ -16,6 +17,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: :return: generated arguments for these test cases """ args.package = [] + args.exit_code = False args.now = False args.source = PackageSource.Auto args.without_dependencies = False @@ -41,11 +43,32 @@ def test_run_with_updates(args: argparse.Namespace, configuration: Configuration """ args = _default_args(args) args.now = True + result = Result() + result.add_success(package_ahriman) mocker.patch("ahriman.application.application.Application.add") mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - application_mock = mocker.patch("ahriman.application.application.Application.update") + application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) Add.run(args, "x86_64", configuration, True, False) updates_mock.assert_called_once_with(args.package, True, True, False, True, pytest.helpers.anyvar(int)) application_mock.assert_called_once_with([package_ahriman]) + check_mock.assert_called_once_with(False, False) + + +def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must raise ExitCode exception on empty result + """ + args = _default_args(args) + args.now = True + args.exit_code = True + mocker.patch("ahriman.application.application.Application.add") + mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") + mocker.patch("ahriman.application.application.Application.update", return_value=Result()) + mocker.patch("ahriman.application.application.Application.updates") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") + + Add.run(args, "x86_64", configuration, True, False) + check_mock.assert_called_once_with(True, True) diff --git a/tests/ahriman/application/handlers/test_handler_patch.py b/tests/ahriman/application/handlers/test_handler_patch.py index 1357289d..219707b6 100644 --- a/tests/ahriman/application/handlers/test_handler_patch.py +++ b/tests/ahriman/application/handlers/test_handler_patch.py @@ -1,7 +1,6 @@ import argparse import pytest -from pathlib import Path from pytest_mock import MockerFixture from ahriman.application.application import Application @@ -18,6 +17,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: :return: generated arguments for these test cases """ args.package = "ahriman" + args.exit_code = False args.remove = False args.track = ["*.diff", "*.patch"] return args @@ -46,7 +46,7 @@ def test_run_list(args: argparse.Namespace, configuration: Configuration, mocker application_mock = mocker.patch("ahriman.application.handlers.patch.Patch.patch_set_list") Patch.run(args, "x86_64", configuration, True, False) - application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package) + application_mock.assert_called_once_with(pytest.helpers.anyvar(int), args.package, False) def test_run_remove(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: @@ -66,25 +66,25 @@ def test_patch_set_list(application: Application, mocker: MockerFixture) -> None """ must list available patches for the command """ - mocker.patch("pathlib.Path.is_dir", return_value=True) get_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.patches_list", return_value={"ahriman": "patch"}) print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") - Patch.patch_set_list(application, "ahriman") + Patch.patch_set_list(application, "ahriman", False) get_mock.assert_called_once_with("ahriman") print_mock.assert_called_once_with(verbose=True) + check_mock.assert_called_once_with(False, False) -def test_patch_set_list_no_patches(application: Application, mocker: MockerFixture) -> None: +def test_patch_set_list_empty_exception(application: Application, mocker: MockerFixture) -> None: """ - must not fail if no patches directory found + must raise ExitCode exception on empty patch list """ - mocker.patch("pathlib.Path.is_dir", return_value=False) - mocker.patch("ahriman.core.database.sqlite.SQLite.patches_get", return_value=None) - print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") + mocker.patch("ahriman.core.database.sqlite.SQLite.patches_list", return_value={}) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") - Patch.patch_set_list(application, "ahriman") - print_mock.assert_not_called() + Patch.patch_set_list(application, "ahriman", True) + check_mock.assert_called_once_with(True, True) def test_patch_set_create(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: diff --git a/tests/ahriman/application/handlers/test_handler_rebuild.py b/tests/ahriman/application/handlers/test_handler_rebuild.py index 6cd13889..ffeec4a2 100644 --- a/tests/ahriman/application/handlers/test_handler_rebuild.py +++ b/tests/ahriman/application/handlers/test_handler_rebuild.py @@ -1,10 +1,13 @@ import argparse +import pytest from pytest_mock import MockerFixture +from unittest import mock from ahriman.application.handlers import Rebuild from ahriman.core.configuration import Configuration from ahriman.models.package import Package +from ahriman.models.result import Result def _default_args(args: argparse.Namespace) -> argparse.Namespace: @@ -15,6 +18,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: """ args.depends_on = [] args.dry_run = False + args.exit_code = False return args @@ -24,14 +28,18 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, must run command """ args = _default_args(args) + result = Result() + result.add_success(package_ahriman) mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman]) - application_mock = mocker.patch("ahriman.application.application.Application.update") + application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") Rebuild.run(args, "x86_64", configuration, True, False) application_packages_mock.assert_called_once_with(None) application_mock.assert_called_once_with([package_ahriman]) + check_mock.assert_has_calls([mock.call(False, False), mock.call(False, False)]) def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, @@ -44,9 +52,11 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman]) application_mock = mocker.patch("ahriman.application.application.Application.update") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") Rebuild.run(args, "x86_64", configuration, True, False) application_mock.assert_not_called() + check_mock.assert_called_once_with(False, False) def test_run_filter(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: @@ -74,3 +84,35 @@ def test_run_without_filter(args: argparse.Namespace, configuration: Configurati Rebuild.run(args, "x86_64", configuration, True, False) application_packages_mock.assert_called_once_with(None) + + +def test_run_update_empty_exception(args: argparse.Namespace, configuration: Configuration, + mocker: MockerFixture) -> None: + """ + must raise ExitCode exception on empty update list + """ + args = _default_args(args) + args.exit_code = True + args.dry_run = True + mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") + mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[]) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") + + Rebuild.run(args, "x86_64", configuration, True, False) + check_mock.assert_called_once_with(True, True) + + +def test_run_build_empty_exception(args: argparse.Namespace, configuration: Configuration, + package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must raise ExitCode exception on empty update result + """ + args = _default_args(args) + args.exit_code = True + mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") + mocker.patch("ahriman.core.repository.repository.Repository.packages_depends_on", return_value=[package_ahriman]) + mocker.patch("ahriman.application.application.Application.update", return_value=Result()) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") + + Rebuild.run(args, "x86_64", configuration, True, False) + check_mock.assert_has_calls([mock.call(True, False), mock.call(True, True)]) diff --git a/tests/ahriman/application/handlers/test_handler_search.py b/tests/ahriman/application/handlers/test_handler_search.py index b3f33dd0..01792214 100644 --- a/tests/ahriman/application/handlers/test_handler_search.py +++ b/tests/ahriman/application/handlers/test_handler_search.py @@ -17,6 +17,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: :return: generated arguments for these test cases """ args.search = ["ahriman"] + args.exit_code = False args.info = False args.sort_by = "name" return args @@ -29,13 +30,29 @@ def test_run(args: argparse.Namespace, configuration: Configuration, aur_package """ args = _default_args(args) search_mock = mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[aur_package_ahriman]) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") Search.run(args, "x86_64", configuration, True, False) search_mock.assert_called_once_with("ahriman") + check_mock.assert_called_once_with(False, False) print_mock.assert_called_once_with(False) +def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must run command + """ + args = _default_args(args) + args.exit_code = True + mocker.patch("ahriman.core.alpm.aur.AUR.multisearch", return_value=[]) + mocker.patch("ahriman.core.formatters.printer.Printer.print") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") + + Search.run(args, "x86_64", configuration, True, False) + check_mock.assert_called_once_with(True, True) + + def test_run_sort(args: argparse.Namespace, configuration: Configuration, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None: """ @@ -95,5 +112,5 @@ def test_sort_fields() -> None: """ must store valid field list which are allowed to be used for sorting """ - expected = {pair.name for pair in dataclasses.fields(AURPackage)} + expected = {field.name for field in dataclasses.fields(AURPackage)} assert all(field in expected for field in Search.SORT_FIELDS) diff --git a/tests/ahriman/application/handlers/test_handler_status.py b/tests/ahriman/application/handlers/test_handler_status.py index 1f635396..fbba30a8 100644 --- a/tests/ahriman/application/handlers/test_handler_status.py +++ b/tests/ahriman/application/handlers/test_handler_status.py @@ -16,6 +16,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: :return: generated arguments for these test cases """ args.ahriman = True + args.exit_code = False args.info = False args.package = [] args.status = None @@ -33,14 +34,31 @@ def test_run(args: argparse.Namespace, configuration: Configuration, package_ahr packages_mock = mocker.patch("ahriman.core.status.client.Client.get", return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)), (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))]) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") print_mock = mocker.patch("ahriman.core.formatters.printer.Printer.print") Status.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with() packages_mock.assert_called_once_with(None) + check_mock.assert_called_once_with(False, False) print_mock.assert_has_calls([mock.call(False) for _ in range(3)]) +def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must raise ExitCode exception on empty status result + """ + args = _default_args(args) + args.exit_code = True + mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") + mocker.patch("ahriman.core.status.client.Client.get_self") + mocker.patch("ahriman.core.status.client.Client.get", return_value=[]) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") + + Status.run(args, "x86_64", configuration, True, False) + check_mock.assert_called_once_with(True, True) + + def test_run_verbose(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package, mocker: MockerFixture) -> None: """ diff --git a/tests/ahriman/application/handlers/test_handler_update.py b/tests/ahriman/application/handlers/test_handler_update.py index 586be0ea..5aaa09f5 100644 --- a/tests/ahriman/application/handlers/test_handler_update.py +++ b/tests/ahriman/application/handlers/test_handler_update.py @@ -2,11 +2,13 @@ import argparse import pytest from pytest_mock import MockerFixture +from unittest import mock from ahriman.application.application import Application from ahriman.application.handlers import Update from ahriman.core.configuration import Configuration from ahriman.models.package import Package +from ahriman.models.result import Result def _default_args(args: argparse.Namespace) -> argparse.Namespace: @@ -17,6 +19,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: """ args.package = [] args.dry_run = False + args.exit_code = False args.no_aur = False args.no_local = False args.no_manual = False @@ -30,14 +33,49 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, must run command """ args = _default_args(args) + result = Result() + result.add_success(package_ahriman) mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") - application_mock = mocker.patch("ahriman.application.application.Application.update") + application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) Update.run(args, "x86_64", configuration, True, False) application_mock.assert_called_once_with([package_ahriman]) updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, pytest.helpers.anyvar(int)) + check_mock.assert_has_calls([mock.call(False, False), mock.call(False, False)]) + + +def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: + """ + must raise ExitCode exception on empty update list + """ + args = _default_args(args) + args.exit_code = True + args.dry_run = True + mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") + mocker.patch("ahriman.application.application.Application.updates", return_value=[]) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") + + Update.run(args, "x86_64", configuration, True, False) + check_mock.assert_called_once_with(True, True) + + +def test_run_update_empty_exception(args: argparse.Namespace, package_ahriman: Package, + configuration: Configuration, mocker: MockerFixture) -> None: + """ + must raise ExitCode exception on empty build result + """ + args = _default_args(args) + args.exit_code = True + mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") + mocker.patch("ahriman.application.application.Application.update", return_value=Result()) + mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman]) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") + + Update.run(args, "x86_64", configuration, True, False) + check_mock.assert_has_calls([mock.call(True, False), mock.call(True, True)]) def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: @@ -47,11 +85,15 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, moc args = _default_args(args) args.dry_run = True mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_create") + application_mock = mocker.patch("ahriman.application.application.Application.update") + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") updates_mock = mocker.patch("ahriman.application.application.Application.updates") Update.run(args, "x86_64", configuration, True, False) updates_mock.assert_called_once_with(args.package, args.no_aur, args.no_local, args.no_manual, args.no_vcs, pytest.helpers.anyvar(int)) + application_mock.assert_not_called() + check_mock.assert_called_once_with(False, pytest.helpers.anyvar(int)) def test_log_fn(application: Application, mocker: MockerFixture) -> None: diff --git a/tests/ahriman/application/handlers/test_handler_user.py b/tests/ahriman/application/handlers/test_handler_user.py index 90c47fdc..b75ab760 100644 --- a/tests/ahriman/application/handlers/test_handler_user.py +++ b/tests/ahriman/application/handlers/test_handler_user.py @@ -22,6 +22,7 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace: args.username = "user" args.action = Action.Update args.as_service = False + args.exit_code = False args.password = "pa55w0rd" args.role = UserAccess.Read args.secure = False @@ -58,12 +59,29 @@ def test_run_list(args: argparse.Namespace, configuration: Configuration, databa """ args = _default_args(args) args.action = Action.List - args.access = None mocker.patch("ahriman.core.database.sqlite.SQLite.load", return_value=database) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") list_mock = mocker.patch("ahriman.core.database.sqlite.SQLite.user_list", return_value=[user]) User.run(args, "x86_64", configuration, True, False) - list_mock.assert_called_once_with("user", None) + list_mock.assert_called_once_with("user", args.role) + check_mock.assert_called_once_with(False, False) + + +def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, database: SQLite, + mocker: MockerFixture) -> None: + """ + must raise ExitCode exception on empty user list + """ + args = _default_args(args) + args.action = Action.List + args.exit_code = True + mocker.patch("ahriman.core.database.sqlite.SQLite.load", return_value=database) + mocker.patch("ahriman.core.database.sqlite.SQLite.user_list", return_value=[]) + check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_if_empty") + + User.run(args, "x86_64", configuration, True, False) + check_mock.assert_called_once_with(True, True) def test_run_remove(args: argparse.Namespace, configuration: Configuration, database: SQLite, diff --git a/tests/ahriman/models/test_result.py b/tests/ahriman/models/test_result.py index 14461bb7..52b68d9d 100644 --- a/tests/ahriman/models/test_result.py +++ b/tests/ahriman/models/test_result.py @@ -5,6 +5,43 @@ from ahriman.models.package import Package from ahriman.models.result import Result +def test_is_empty() -> None: + """ + must return is empty for empty builds + """ + result = Result() + assert result.is_empty + + +def test_non_empty_success(package_ahriman: Package) -> None: + """ + must be non-empty if there is success build + """ + result = Result() + result.add_success(package_ahriman) + assert not result.is_empty + + +def test_is_empty_failed(package_ahriman: Package) -> None: + """ + must be empty if there is only failed build + """ + result = Result() + result.add_failed(package_ahriman) + assert result.is_empty + + +def test_non_empty_full(package_ahriman: Package) -> None: + """ + must be non-empty if there are both failed and success builds + """ + result = Result() + result.add_failed(package_ahriman) + result.add_success(package_ahriman) + + assert not result.is_empty + + def test_add_failed(package_ahriman: Package) -> None: """ must add package to failed list