From c8afcbf36ab428105fc22006b4d4111c124b0977 Mon Sep 17 00:00:00 2001 From: Evgenii Alekseev Date: Tue, 21 May 2024 16:27:17 +0300 Subject: [PATCH] feat: implement local reporter mode (#126) * implement local reporter mode * simplify watcher class * review changes * do not update unknown status * allow empty key patches via api * fix some pylint warnings in tests --- src/ahriman/application/ahriman.py | 2 +- .../application/application/application.py | 3 +- .../application/application_packages.py | 4 +- .../application/application_properties.py | 11 + .../application/application_repository.py | 6 +- src/ahriman/application/handlers/add.py | 3 +- src/ahriman/application/handlers/change.py | 2 +- src/ahriman/application/handlers/patch.py | 21 +- src/ahriman/application/handlers/rebuild.py | 2 +- .../application/handlers/status_update.py | 8 +- src/ahriman/application/lock.py | 2 +- src/ahriman/core/alpm/pacman.py | 10 +- src/ahriman/core/build_tools/task.py | 7 +- .../database/operations/changes_operations.py | 24 - .../operations/dependencies_operations.py | 28 +- .../core/database/operations/operations.py | 4 +- .../database/operations/package_operations.py | 79 +-- src/ahriman/core/database/sqlite.py | 5 +- src/ahriman/core/gitremote/remote_push.py | 12 +- .../core/gitremote/remote_push_trigger.py | 6 +- src/ahriman/core/log/http_log_handler.py | 5 +- src/ahriman/core/repository/executor.py | 11 +- src/ahriman/core/repository/package_info.py | 7 +- src/ahriman/core/repository/repository.py | 2 + .../core/repository/repository_properties.py | 4 +- src/ahriman/core/repository/update_handler.py | 15 +- src/ahriman/core/status/__init__.py | 1 + src/ahriman/core/status/client.py | 203 +++++- src/ahriman/core/status/local_client.py | 214 +++++++ src/ahriman/core/status/watcher.py | 183 +++--- src/ahriman/core/status/web_client.py | 196 +++++- src/ahriman/core/support/package_creator.py | 51 +- src/ahriman/models/dependencies.py | 36 +- src/ahriman/models/package_archive.py | 6 +- src/ahriman/models/pkgbuild_patch.py | 19 +- src/ahriman/web/schemas/__init__.py | 2 + .../web/schemas/dependencies_schema.py | 31 + .../web/schemas/package_version_schema.py | 34 + src/ahriman/web/schemas/patch_schema.py | 4 +- src/ahriman/web/views/base.py | 8 +- src/ahriman/web/views/v1/packages/changes.py | 11 +- .../web/views/v1/packages/dependencies.py | 113 ++++ src/ahriman/web/views/v1/packages/logs.py | 13 +- src/ahriman/web/views/v1/packages/package.py | 5 +- src/ahriman/web/views/v1/packages/patch.py | 5 +- src/ahriman/web/views/v1/packages/patches.py | 6 +- src/ahriman/web/views/v2/packages/logs.py | 9 +- src/ahriman/web/web.py | 4 +- .../application/test_application.py | 8 +- .../application/test_application_packages.py | 8 +- .../test_application_properties.py | 14 +- .../test_application_repository.py | 11 +- .../application/handlers/test_handler_add.py | 4 +- .../handlers/test_handler_change.py | 6 +- .../handlers/test_handler_patch.py | 40 +- .../handlers/test_handler_rebuild.py | 4 +- .../handlers/test_handler_status.py | 14 +- .../handlers/test_handler_status_update.py | 13 +- tests/ahriman/application/test_lock.py | 14 +- tests/ahriman/conftest.py | 23 +- tests/ahriman/core/alpm/test_pacman.py | 28 +- tests/ahriman/core/build_tools/test_task.py | 17 +- .../operations/test_changes_operations.py | 10 - .../test_dependencies_operations.py | 58 +- .../operations/test_package_operations.py | 56 +- tests/ahriman/core/database/test_sqlite.py | 2 + .../core/gitremote/test_remote_push.py | 23 +- .../gitremote/test_remote_push_trigger.py | 8 +- .../ahriman/core/log/test_http_log_handler.py | 12 +- tests/ahriman/core/log/test_lazy_logging.py | 12 +- .../ahriman/core/repository/test_executor.py | 39 +- .../core/repository/test_package_info.py | 6 +- .../core/repository/test_repository.py | 2 + .../core/repository/test_update_handler.py | 42 +- tests/ahriman/core/status/conftest.py | 2 +- tests/ahriman/core/status/test_client.py | 173 +++-- .../ahriman/core/status/test_local_client.py | 182 ++++++ tests/ahriman/core/status/test_watcher.py | 242 +++---- tests/ahriman/core/status/test_web_client.py | 590 +++++++++++++++--- .../core/support/test_package_creator.py | 61 +- tests/ahriman/core/test_util.py | 1 - tests/ahriman/models/test_dependencies.py | 9 + tests/ahriman/models/test_package_archive.py | 10 +- tests/ahriman/models/test_pkgbuild_patch.py | 15 +- tests/ahriman/models/test_repository_id.py | 2 +- .../web/schemas/test_dependencies_schema.py | 1 + .../schemas/test_package_version_schema.py | 1 + tests/ahriman/web/views/test_view_base.py | 9 + .../test_view_v1_packages_dependencies.py | 97 +++ .../v1/packages/test_view_v1_packages_logs.py | 16 +- .../packages/test_view_v1_packages_patches.py | 18 + .../v1/status/test_view_v1_status_info.py | 2 +- .../test_view_v1_status_repositories.py | 2 +- 93 files changed, 2409 insertions(+), 935 deletions(-) create mode 100644 src/ahriman/core/status/local_client.py create mode 100644 src/ahriman/web/schemas/dependencies_schema.py create mode 100644 src/ahriman/web/schemas/package_version_schema.py create mode 100644 src/ahriman/web/views/v1/packages/dependencies.py create mode 100644 tests/ahriman/core/status/test_local_client.py create mode 100644 tests/ahriman/web/schemas/test_dependencies_schema.py create mode 100644 tests/ahriman/web/schemas/test_package_version_schema.py create mode 100644 tests/ahriman/web/views/v1/packages/test_view_v1_packages_dependencies.py diff --git a/src/ahriman/application/ahriman.py b/src/ahriman/application/ahriman.py index 1f4b5477..4713fdf8 100644 --- a/src/ahriman/application/ahriman.py +++ b/src/ahriman/application/ahriman.py @@ -446,7 +446,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("package", help="package base") parser.add_argument("-e", "--exit-code", help="return non-zero exit status if result is empty", action="store_true") parser.add_argument("-v", "--variable", help="if set, show only patches for specified PKGBUILD variables", action="append") diff --git a/src/ahriman/application/application/application.py b/src/ahriman/application/application/application.py index 4ec45a61..11f1d758 100644 --- a/src/ahriman/application/application/application.py +++ b/src/ahriman/application/application/application.py @@ -161,8 +161,7 @@ class Application(ApplicationPackages, ApplicationRepository): package = Package.from_aur(package_name, username) with_dependencies[package.base] = package - # register package in local database - self.database.package_base_update(package) + # register package in the database self.repository.reporter.set_unknown(package) return list(with_dependencies.values()) diff --git a/src/ahriman/application/application/application_packages.py b/src/ahriman/application/application/application_packages.py index 5ff74826..45e62a89 100644 --- a/src/ahriman/application/application/application_packages.py +++ b/src/ahriman/application/application/application_packages.py @@ -65,7 +65,7 @@ class ApplicationPackages(ApplicationProperties): """ package = Package.from_aur(source, username) self.database.build_queue_insert(package) - self.database.package_base_update(package) + self.reporter.set_unknown(package) def _add_directory(self, source: str, *_: Any) -> None: """ @@ -139,7 +139,7 @@ class ApplicationPackages(ApplicationProperties): """ package = Package.from_official(source, self.repository.pacman, username) self.database.build_queue_insert(package) - self.database.package_base_update(package) + self.reporter.set_unknown(package) def add(self, names: Iterable[str], source: PackageSource, username: str | None = None) -> None: """ diff --git a/src/ahriman/application/application/application_properties.py b/src/ahriman/application/application/application_properties.py index 9d5ba208..4b9c5a59 100644 --- a/src/ahriman/application/application/application_properties.py +++ b/src/ahriman/application/application/application_properties.py @@ -21,6 +21,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.log import LazyLogging from ahriman.core.repository import Repository +from ahriman.core.status import Client from ahriman.models.pacman_synchronization import PacmanSynchronization from ahriman.models.repository_id import RepositoryId @@ -63,3 +64,13 @@ class ApplicationProperties(LazyLogging): str: repository architecture """ return self.repository_id.architecture + + @property + def reporter(self) -> Client: + """ + instance of the web/database client + + Returns: + Client: repository reposter + """ + return self.repository.reporter diff --git a/src/ahriman/application/application/application_repository.py b/src/ahriman/application/application/application_repository.py index f4547c75..6ac306db 100644 --- a/src/ahriman/application/application/application_repository.py +++ b/src/ahriman/application/application/application_repository.py @@ -39,15 +39,13 @@ class ApplicationRepository(ApplicationProperties): Args: packages(Iterable[Package]): list of packages to retrieve changes """ - last_commit_hashes = self.database.hashes_get() - for package in packages: - last_commit_sha = last_commit_hashes.get(package.base) + last_commit_sha = self.reporter.package_changes_get(package.base).last_commit_sha if last_commit_sha is None: continue # skip check in case if we can't calculate diff changes = self.repository.package_changes(package, last_commit_sha) - self.repository.reporter.package_changes_set(package.base, changes) + self.repository.reporter.package_changes_update(package.base, changes) def clean(self, *, cache: bool, chroot: bool, manual: bool, packages: bool, pacman: bool) -> None: """ diff --git a/src/ahriman/application/handlers/add.py b/src/ahriman/application/handlers/add.py index ac185e1c..e567c4aa 100644 --- a/src/ahriman/application/handlers/add.py +++ b/src/ahriman/application/handlers/add.py @@ -50,7 +50,8 @@ class Add(Handler): application.add(args.package, args.source, args.username) patches = [PkgbuildPatch.from_env(patch) for patch in args.variable] if args.variable is not None else [] for package in args.package: # for each requested package insert patch - application.database.patches_insert(package, patches) + for patch in patches: + application.reporter.package_patches_update(package, patch) if not args.now: return diff --git a/src/ahriman/application/handlers/change.py b/src/ahriman/application/handlers/change.py index cac737ea..1764b133 100644 --- a/src/ahriman/application/handlers/change.py +++ b/src/ahriman/application/handlers/change.py @@ -56,4 +56,4 @@ class Change(Handler): ChangesPrinter(changes)(verbose=True, separator="") Change.check_if_empty(args.exit_code, changes.is_empty) case Action.Remove: - client.package_changes_set(args.package, Changes()) + client.package_changes_update(args.package, Changes()) diff --git a/src/ahriman/application/handlers/patch.py b/src/ahriman/application/handlers/patch.py index 0f131257..ef58e068 100644 --- a/src/ahriman/application/handlers/patch.py +++ b/src/ahriman/application/handlers/patch.py @@ -116,25 +116,28 @@ class Patch(Handler): package_base(str): package base patch(PkgbuildPatch): patch descriptor """ - application.database.patches_insert(package_base, [patch]) + application.reporter.package_patches_update(package_base, patch) @staticmethod - def patch_set_list(application: Application, package_base: str | None, variables: list[str] | None, + def patch_set_list(application: Application, package_base: str, variables: list[str] | None, exit_code: bool) -> None: """ list patches available for the package base Args: application(Application): application instance - package_base(str | None): package base + package_base(str): package base variables(list[str] | None): extract patches only for specified PKGBUILD variables exit_code(bool): exit with error on empty search result """ - patches = application.database.patches_list(package_base, variables) + patches = [ + patch + for patch in application.reporter.package_patches_get(package_base, None) + if variables is None or patch.key in variables + ] Patch.check_if_empty(exit_code, not patches) - for base, patch in patches.items(): - PatchPrinter(base, patch)(verbose=True, separator=" = ") + PatchPrinter(package_base, patches)(verbose=True, separator=" = ") @staticmethod def patch_set_remove(application: Application, package_base: str, variables: list[str] | None) -> None: @@ -146,4 +149,8 @@ class Patch(Handler): package_base(str): package base variables(list[str] | None): remove patches only for specified PKGBUILD variables """ - application.database.patches_remove(package_base, variables) + if variables is not None: + for variable in variables: # iterate over single variable + application.reporter.package_patches_remove(package_base, variable) + else: + application.reporter.package_patches_remove(package_base, None) # just pass as is diff --git a/src/ahriman/application/handlers/rebuild.py b/src/ahriman/application/handlers/rebuild.py index 5313c5b0..344b18a0 100644 --- a/src/ahriman/application/handlers/rebuild.py +++ b/src/ahriman/application/handlers/rebuild.py @@ -76,7 +76,7 @@ class Rebuild(Handler): if from_database: return [ package - for (package, last_status) in application.database.packages_get() + for (package, last_status) in application.reporter.package_get(None) if status is None or last_status.status == status ] diff --git a/src/ahriman/application/handlers/status_update.py b/src/ahriman/application/handlers/status_update.py index 3b1637b0..085060fe 100644 --- a/src/ahriman/application/handlers/status_update.py +++ b/src/ahriman/application/handlers/status_update.py @@ -51,12 +51,8 @@ class StatusUpdate(Handler): match args.action: case Action.Update if args.package: # update packages statuses - packages = application.repository.packages() - for base in args.package: - if (local := next((package for package in packages if package.base == base), None)) is not None: - client.package_add(local, args.status) - else: - client.package_update(base, args.status) + for package in args.package: + client.package_update(package, args.status) case Action.Update: # update service status client.status_update(args.status) diff --git a/src/ahriman/application/lock.py b/src/ahriman/application/lock.py index a0d3610f..58b0cdc3 100644 --- a/src/ahriman/application/lock.py +++ b/src/ahriman/application/lock.py @@ -27,7 +27,7 @@ from ahriman import __version__ from ahriman.core.configuration import Configuration from ahriman.core.exceptions import DuplicateRunError from ahriman.core.log import LazyLogging -from ahriman.core.status.client import Client +from ahriman.core.status import Client from ahriman.core.util import check_user from ahriman.models.build_status import BuildStatusEnum from ahriman.models.repository_id import RepositoryId diff --git a/src/ahriman/core/alpm/pacman.py b/src/ahriman/core/alpm/pacman.py index 66b52269..8f717093 100644 --- a/src/ahriman/core/alpm/pacman.py +++ b/src/ahriman/core/alpm/pacman.py @@ -177,7 +177,7 @@ class Pacman(LazyLogging): PacmanDatabase(database, self.configuration).sync(force=force) transaction.release() - def files(self, packages: Iterable[str] | None = None) -> dict[str, set[Path]]: + def files(self, packages: Iterable[str] | None = None) -> dict[str, set[str]]: """ extract list of known packages from the databases @@ -185,11 +185,11 @@ class Pacman(LazyLogging): packages(Iterable[str] | None, optional): filter by package names (Default value = None) Returns: - dict[str, set[Path]]: map of package name to its list of files + dict[str, set[str]]: map of package name to its list of files """ packages = packages or [] - def extract(tar: tarfile.TarFile) -> Generator[tuple[str, set[Path]], None, None]: + def extract(tar: tarfile.TarFile) -> Generator[tuple[str, set[str]], None, None]: for descriptor in filter(lambda info: info.path.endswith("/files"), tar.getmembers()): package, *_ = str(Path(descriptor.path).parent).rsplit("-", 2) if packages and package not in packages: @@ -197,11 +197,11 @@ class Pacman(LazyLogging): content = tar.extractfile(descriptor) if content is None: continue - files = {Path(filename.decode("utf8").rstrip()) for filename in content.readlines()} + files = {filename.decode("utf8").rstrip() for filename in content.readlines()} yield package, files - result: dict[str, set[Path]] = {} + result: dict[str, set[str]] = {} for database in self.handle.get_syncdbs(): database_file = self.repository_paths.pacman / "sync" / f"{database.name}.files.tar.gz" if not database_file.is_file(): diff --git a/src/ahriman/core/build_tools/task.py b/src/ahriman/core/build_tools/task.py index ea5f05bf..12bf1aa6 100644 --- a/src/ahriman/core/build_tools/task.py +++ b/src/ahriman/core/build_tools/task.py @@ -21,7 +21,6 @@ from pathlib import Path from ahriman.core.build_tools.sources import Sources from ahriman.core.configuration import Configuration -from ahriman.core.database import SQLite from ahriman.core.exceptions import BuildError from ahriman.core.log import LazyLogging from ahriman.core.util import check_output @@ -116,20 +115,20 @@ class Task(LazyLogging): # e.g. in some cases packagelist command produces debug packages which were not actually built return list(filter(lambda path: path.is_file(), map(Path, packages))) - def init(self, sources_dir: Path, database: SQLite, local_version: str | None) -> str | None: + def init(self, sources_dir: Path, patches: list[PkgbuildPatch], local_version: str | None) -> str | None: """ fetch package from git Args: sources_dir(Path): local path to fetch - database(SQLite): database instance + patches(list[PkgbuildPatch]): list of patches for the package local_version(str | None): local version of the package. If set and equal to current version, it will automatically bump pkgrel Returns: str | None: current commit sha if available """ - last_commit_sha = Sources.load(sources_dir, self.package, database.patches_get(self.package.base), self.paths) + last_commit_sha = Sources.load(sources_dir, self.package, patches, self.paths) if local_version is None: return last_commit_sha # there is no local package or pkgrel increment is disabled diff --git a/src/ahriman/core/database/operations/changes_operations.py b/src/ahriman/core/database/operations/changes_operations.py index 53fe6495..cba29cf3 100644 --- a/src/ahriman/core/database/operations/changes_operations.py +++ b/src/ahriman/core/database/operations/changes_operations.py @@ -117,27 +117,3 @@ class ChangesOperations(Operations): }) return self.with_connection(run, commit=True) - - def hashes_get(self, repository_id: RepositoryId | None = None) -> dict[str, str]: - """ - extract last commit hashes if available - - Args: - repository_id(RepositoryId, optional): repository unique identifier override (Default value = None) - - Returns: - dict[str, str]: map of package base to its last commit hash - """ - - repository_id = repository_id or self._repository_id - - def run(connection: Connection) -> dict[str, str]: - return { - row["package_base"]: row["last_commit_sha"] - for row in connection.execute( - """select package_base, last_commit_sha from package_changes where repository = :repository""", - {"repository": repository_id.id} - ) - } - - return self.with_connection(run) diff --git a/src/ahriman/core/database/operations/dependencies_operations.py b/src/ahriman/core/database/operations/dependencies_operations.py index e5ee99ac..1f2a7780 100644 --- a/src/ahriman/core/database/operations/dependencies_operations.py +++ b/src/ahriman/core/database/operations/dependencies_operations.py @@ -17,7 +17,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from pathlib import Path from sqlite3 import Connection from ahriman.core.database.operations.operations import Operations @@ -31,7 +30,7 @@ class DependenciesOperations(Operations): """ def dependencies_get(self, package_base: str | None = None, - repository_id: RepositoryId | None = None) -> list[Dependencies]: + repository_id: RepositoryId | None = None) -> dict[str, Dependencies]: """ get dependencies for the specific package base if available @@ -44,15 +43,9 @@ class DependenciesOperations(Operations): """ repository_id = repository_id or self._repository_id - def run(connection: Connection) -> list[Dependencies]: - return [ - Dependencies( - row["package_base"], - { - Path(path): packages - for path, packages in row["dependencies"].items() - } - ) + def run(connection: Connection) -> dict[str, Dependencies]: + return { + row["package_base"]: Dependencies(row["dependencies"]) for row in connection.execute( """ select package_base, dependencies from package_dependencies @@ -64,15 +57,17 @@ class DependenciesOperations(Operations): "repository": repository_id.id, } ) - ] + } return self.with_connection(run) - def dependencies_insert(self, dependencies: Dependencies, repository_id: RepositoryId | None = None) -> None: + def dependencies_insert(self, package_base: str, dependencies: Dependencies, + repository_id: RepositoryId | None = None) -> None: """ insert package dependencies Args: + package_base(str): package base dependencies(Dependencies): package dependencies repository_id(RepositoryId, optional): repository unique identifier override (Default value = None) """ @@ -89,12 +84,9 @@ class DependenciesOperations(Operations): dependencies = :dependencies """, { - "package_base": dependencies.package_base, + "package_base": package_base, "repository": repository_id.id, - "dependencies": { - str(path): packages - for path, packages in dependencies.paths.items() - } + "dependencies": dependencies.paths, }) return self.with_connection(run, commit=True) diff --git a/src/ahriman/core/database/operations/operations.py b/src/ahriman/core/database/operations/operations.py index 09dbee59..b2d0631c 100644 --- a/src/ahriman/core/database/operations/operations.py +++ b/src/ahriman/core/database/operations/operations.py @@ -25,6 +25,7 @@ from typing import Any, TypeVar from ahriman.core.log import LazyLogging from ahriman.models.repository_id import RepositoryId +from ahriman.models.repository_paths import RepositoryPaths T = TypeVar("T") @@ -38,7 +39,7 @@ class Operations(LazyLogging): path(Path): path to the database file """ - def __init__(self, path: Path, repository_id: RepositoryId) -> None: + def __init__(self, path: Path, repository_id: RepositoryId, repository_paths: RepositoryPaths) -> None: """ default constructor @@ -48,6 +49,7 @@ class Operations(LazyLogging): """ self.path = path self._repository_id = repository_id + self._repository_paths = repository_paths @staticmethod def factory(cursor: sqlite3.Cursor, row: tuple[Any, ...]) -> dict[str, Any]: diff --git a/src/ahriman/core/database/operations/package_operations.py b/src/ahriman/core/database/operations/package_operations.py index 812d0009..1aeffda3 100644 --- a/src/ahriman/core/database/operations/package_operations.py +++ b/src/ahriman/core/database/operations/package_operations.py @@ -150,34 +150,6 @@ class PackageOperations(Operations): """, package_list) - @staticmethod - def _package_update_insert_status(connection: Connection, package_base: str, status: BuildStatus, - repository_id: RepositoryId) -> None: - """ - insert base package status into table - - Args: - connection(Connection): database connection - package_base(str): package base name - status(BuildStatus): new build status - repository_id(RepositoryId): repository unique identifier - """ - connection.execute( - """ - insert into package_statuses - (package_base, status, last_updated, repository) - values - (:package_base, :status, :last_updated, :repository) - on conflict (package_base, repository) do update set - status = :status, last_updated = :last_updated - """, - { - "package_base": package_base, - "status": status.status.value, - "last_updated": status.timestamp, - "repository": repository_id.id, - }) - @staticmethod def _packages_get_select_package_bases(connection: Connection, repository_id: RepositoryId) -> dict[str, Package]: """ @@ -246,21 +218,6 @@ class PackageOperations(Operations): ) } - def package_base_update(self, package: Package, repository_id: RepositoryId | None = None) -> None: - """ - update package base only - - Args: - package(Package): package properties - repository_id(RepositoryId, optional): repository unique identifier override (Default value = None) - """ - repository_id = repository_id or self._repository_id - - def run(connection: Connection) -> None: - self._package_update_insert_base(connection, package, repository_id) - - return self.with_connection(run, commit=True) - def package_remove(self, package_base: str, repository_id: RepositoryId | None = None) -> None: """ remove package from database @@ -277,20 +234,18 @@ class PackageOperations(Operations): return self.with_connection(run, commit=True) - def package_update(self, package: Package, status: BuildStatus, repository_id: RepositoryId | None = None) -> None: + def package_update(self, package: Package, repository_id: RepositoryId | None = None) -> None: """ update package status Args: package(Package): package properties - status(BuildStatus): new build status repository_id(RepositoryId, optional): repository unique identifier override (Default value = None) """ repository_id = repository_id or self._repository_id def run(connection: Connection) -> None: self._package_update_insert_base(connection, package, repository_id) - self._package_update_insert_status(connection, package.base, status, repository_id) self._package_update_insert_packages(connection, package, repository_id) self._package_remove_packages(connection, package.base, package.packages.keys(), repository_id) @@ -317,22 +272,32 @@ class PackageOperations(Operations): return self.with_connection(lambda connection: list(run(connection))) - def remotes_get(self, repository_id: RepositoryId | None = None) -> dict[str, RemoteSource]: + def status_update(self, package_base: str, status: BuildStatus, repository_id: RepositoryId | None = None) -> None: """ - get packages remotes based on current settings + insert base package status into table Args: + package_base(str): package base name + status(BuildStatus): new build status repository_id(RepositoryId, optional): repository unique identifier override (Default value = None) - - Returns: - dict[str, RemoteSource]: map of package base to its remote sources """ repository_id = repository_id or self._repository_id - def run(connection: Connection) -> dict[str, Package]: - return self._packages_get_select_package_bases(connection, repository_id) + def run(connection: Connection) -> None: + connection.execute( + """ + insert into package_statuses + (package_base, status, last_updated, repository) + values + (:package_base, :status, :last_updated, :repository) + on conflict (package_base, repository) do update set + status = :status, last_updated = :last_updated + """, + { + "package_base": package_base, + "status": status.status.value, + "last_updated": status.timestamp, + "repository": repository_id.id, + }) - return { - package_base: package.remote - for package_base, package in self.with_connection(run).items() - } + return self.with_connection(run, commit=True) diff --git a/src/ahriman/core/database/sqlite.py b/src/ahriman/core/database/sqlite.py index 06bcd2bb..e6d26d7e 100644 --- a/src/ahriman/core/database/sqlite.py +++ b/src/ahriman/core/database/sqlite.py @@ -66,7 +66,7 @@ class SQLite( path = cls.database_path(configuration) _, repository_id = configuration.check_loaded() - database = cls(path, repository_id) + database = cls(path, repository_id, configuration.repository_paths) database.init(configuration) return database @@ -119,3 +119,6 @@ class SQLite( self.logs_remove(package_base, None) self.changes_remove(package_base) self.dependencies_remove(package_base) + + # remove local cache too + self._repository_paths.tree_clear(package_base) diff --git a/src/ahriman/core/gitremote/remote_push.py b/src/ahriman/core/gitremote/remote_push.py index d51cc913..25ef0fb5 100644 --- a/src/ahriman/core/gitremote/remote_push.py +++ b/src/ahriman/core/gitremote/remote_push.py @@ -25,9 +25,9 @@ from tempfile import TemporaryDirectory from ahriman.core.build_tools.sources import Sources from ahriman.core.configuration import Configuration -from ahriman.core.database import SQLite from ahriman.core.exceptions import GitRemoteError from ahriman.core.log import LazyLogging +from ahriman.core.status import Client from ahriman.models.package import Package from ahriman.models.package_source import PackageSource from ahriman.models.remote_source import RemoteSource @@ -40,20 +40,20 @@ class RemotePush(LazyLogging): Attributes: commit_author(tuple[str, str] | None): optional commit author in form of git config - database(SQLite): database instance remote_source(RemoteSource): repository remote source (remote pull url and branch) + reporter(Client): reporter client used for additional information retrieval """ - def __init__(self, database: SQLite, configuration: Configuration, section: str) -> None: + def __init__(self, reporter: Client, configuration: Configuration, section: str) -> None: """ default constructor Args: - database(SQLite): database instance + reporter(Client): reporter client configuration(Configuration): configuration instance section(str): settings section name """ - self.database = database + self.reporter = reporter commit_email = configuration.get(section, "commit_email", fallback="ahriman@localhost") commit_user = configuration.get(section, "commit_user", fallback="ahriman") @@ -92,7 +92,7 @@ class RemotePush(LazyLogging): else: shutil.rmtree(git_file) # ...copy all patches... - for patch in self.database.patches_get(package.base): + for patch in self.reporter.package_patches_get(package.base, None): filename = f"ahriman-{package.base}.patch" if patch.key is None else f"ahriman-{patch.key}.patch" patch.write(package_target_dir / filename) # ...and finally return path to the copied directory diff --git a/src/ahriman/core/gitremote/remote_push_trigger.py b/src/ahriman/core/gitremote/remote_push_trigger.py index 44aa9166..3e45e678 100644 --- a/src/ahriman/core/gitremote/remote_push_trigger.py +++ b/src/ahriman/core/gitremote/remote_push_trigger.py @@ -19,8 +19,8 @@ # from ahriman.core import context from ahriman.core.configuration import Configuration -from ahriman.core.database import SQLite from ahriman.core.gitremote.remote_push import RemotePush +from ahriman.core.status import Client from ahriman.core.triggers import Trigger from ahriman.models.package import Package from ahriman.models.repository_id import RepositoryId @@ -110,10 +110,10 @@ class RemotePushTrigger(Trigger): GitRemoteError: if database is not set in context """ ctx = context.get() - database = ctx.get(SQLite) + reporter = ctx.get(Client) for target in self.targets: section, _ = self.configuration.gettype( target, self.repository_id, fallback=self.CONFIGURATION_SCHEMA_FALLBACK) - runner = RemotePush(database, self.configuration, section) + runner = RemotePush(reporter, self.configuration, section) runner.run(result) diff --git a/src/ahriman/core/log/http_log_handler.py b/src/ahriman/core/log/http_log_handler.py index f2b0fde3..86c3211d 100644 --- a/src/ahriman/core/log/http_log_handler.py +++ b/src/ahriman/core/log/http_log_handler.py @@ -22,6 +22,7 @@ import logging from typing import Self from ahriman.core.configuration import Configuration +from ahriman.core.status import Client from ahriman.models.repository_id import RepositoryId @@ -49,8 +50,6 @@ class HttpLogHandler(logging.Handler): # we don't really care about those parameters because they will be handled by the reporter logging.Handler.__init__(self) - # client has to be imported here because of circular imports - from ahriman.core.status.client import Client self.reporter = Client.load(repository_id, configuration, report=report) self.suppress_errors = suppress_errors @@ -92,7 +91,7 @@ class HttpLogHandler(logging.Handler): return # in case if no package base supplied we need just skip log message try: - self.reporter.package_logs(log_record_id, record) + self.reporter.package_logs_add(log_record_id, record.created, record.getMessage()) except Exception: if self.suppress_errors: return diff --git a/src/ahriman/core/repository/executor.py b/src/ahriman/core/repository/executor.py index 43033ab1..1037d107 100644 --- a/src/ahriman/core/repository/executor.py +++ b/src/ahriman/core/repository/executor.py @@ -58,7 +58,8 @@ class Executor(PackageInfo, Cleaner): self.reporter.set_building(package.base) task = Task(package, self.configuration, self.architecture, self.paths) local_version = local_versions.get(package.base) if bump_pkgrel else None - commit_sha = task.init(local_path, self.database, local_version) + patches = self.reporter.package_patches_get(package.base, None) + commit_sha = task.init(local_path, patches, local_version) built = task.build(local_path, PACKAGER=packager_id) for src in built: dst = self.paths.packages / src.name @@ -77,10 +78,10 @@ class Executor(PackageInfo, Cleaner): packager = self.packager(packagers, single.base) last_commit_sha = build_single(single, Path(dir_name), packager.packager_id) # clear changes and update commit hash - self.reporter.package_changes_set(single.base, Changes(last_commit_sha)) + self.reporter.package_changes_update(single.base, Changes(last_commit_sha)) # update dependencies list dependencies = PackageArchive(self.paths.build_directory, single).depends_on() - self.database.dependencies_insert(dependencies) + self.reporter.package_dependencies_update(single.base, dependencies) # update result set result.add_updated(single) except Exception: @@ -102,9 +103,7 @@ class Executor(PackageInfo, Cleaner): """ def remove_base(package_base: str) -> None: try: - self.paths.tree_clear(package_base) # remove all internal files - self.database.package_clear(package_base) - self.reporter.package_remove(package_base) # we only update status page in case of base removal + self.reporter.package_remove(package_base) except Exception: self.logger.exception("could not remove base %s", package_base) diff --git a/src/ahriman/core/repository/package_info.py b/src/ahriman/core/repository/package_info.py index 56a6fef1..28ba659f 100644 --- a/src/ahriman/core/repository/package_info.py +++ b/src/ahriman/core/repository/package_info.py @@ -43,14 +43,14 @@ class PackageInfo(RepositoryProperties): Returns: list[Package]: list of read packages """ - sources = self.database.remotes_get() + sources = {package.base: package.remote for package, _, in self.reporter.package_get(None)} result: dict[str, Package] = {} # we are iterating over bases, not single packages for full_path in packages: try: local = Package.from_archive(full_path, self.pacman) - if (source := sources.get(local.base)) is not None: + if (source := sources.get(local.base)) is not None: # update source with remote local.remote = source current = result.setdefault(local.base, local) @@ -78,7 +78,8 @@ class PackageInfo(RepositoryProperties): """ with TemporaryDirectory(ignore_cleanup_errors=True) as dir_name: dir_path = Path(dir_name) - current_commit_sha = Sources.load(dir_path, package, self.database.patches_get(package.base), self.paths) + patches = self.reporter.package_patches_get(package.base, None) + current_commit_sha = Sources.load(dir_path, package, patches, self.paths) changes: str | None = None if current_commit_sha != last_commit_sha: diff --git a/src/ahriman/core/repository/repository.py b/src/ahriman/core/repository/repository.py index f9c97907..44c76413 100644 --- a/src/ahriman/core/repository/repository.py +++ b/src/ahriman/core/repository/repository.py @@ -26,6 +26,7 @@ from ahriman.core.database import SQLite from ahriman.core.repository.executor import Executor from ahriman.core.repository.update_handler import UpdateHandler from ahriman.core.sign.gpg import GPG +from ahriman.core.status import Client from ahriman.models.pacman_synchronization import PacmanSynchronization from ahriman.models.repository_id import RepositoryId @@ -92,6 +93,7 @@ class Repository(Executor, UpdateHandler): ctx.set(Configuration, self.configuration) ctx.set(Pacman, self.pacman) ctx.set(GPG, self.sign) + ctx.set(Client, self.reporter) ctx.set(type(self), self) diff --git a/src/ahriman/core/repository/repository_properties.py b/src/ahriman/core/repository/repository_properties.py index 714b3c6d..67d84666 100644 --- a/src/ahriman/core/repository/repository_properties.py +++ b/src/ahriman/core/repository/repository_properties.py @@ -23,7 +23,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.log import LazyLogging from ahriman.core.sign.gpg import GPG -from ahriman.core.status.client import Client +from ahriman.core.status import Client from ahriman.core.triggers import TriggerLoader from ahriman.models.packagers import Packagers from ahriman.models.pacman_synchronization import PacmanSynchronization @@ -75,7 +75,7 @@ class RepositoryProperties(LazyLogging): self.pacman = Pacman(repository_id, configuration, refresh_database=refresh_pacman_database) self.sign = GPG(configuration) self.repo = Repo(self.name, self.paths, self.sign.repository_sign_args) - self.reporter = Client.load(repository_id, configuration, report=report) + self.reporter = Client.load(repository_id, configuration, database, report=report) self.triggers = TriggerLoader.load(repository_id, configuration) @property diff --git a/src/ahriman/core/repository/update_handler.py b/src/ahriman/core/repository/update_handler.py index ca79f8b4..9658cee5 100644 --- a/src/ahriman/core/repository/update_handler.py +++ b/src/ahriman/core/repository/update_handler.py @@ -18,7 +18,6 @@ # along with this program. If not, see . # from collections.abc import Iterable -from pathlib import Path from ahriman.core.build_tools.sources import Sources from ahriman.core.exceptions import UnknownPackageError @@ -89,27 +88,25 @@ class UpdateHandler(PackageInfo, Cleaner): Returns: list[Package]: list of packages for which there is breaking linking """ - def extract_files(lookup_packages: Iterable[str]) -> dict[Path, set[str]]: + def extract_files(lookup_packages: Iterable[str]) -> dict[str, set[str]]: database_files = self.pacman.files(lookup_packages) - files: dict[Path, set[str]] = {} + files: dict[str, set[str]] = {} for package_name, package_files in database_files.items(): # invert map for package_file in package_files: files.setdefault(package_file, set()).add(package_name) return files - dependencies = {dependency.package_base: dependency for dependency in self.database.dependencies_get()} - result: list[Package] = [] for package in self.packages(filter_packages): - if package.base not in dependencies: + dependencies = self.reporter.package_dependencies_get(package.base) + if not dependencies.paths: continue # skip check if no package dependencies found - required = dependencies[package.base].paths - required_packages = {dep for dep_packages in required.values() for dep in dep_packages} + required_packages = {dep for dep_packages in dependencies.paths.values() for dep in dep_packages} filesystem = extract_files(required_packages) - for path, packages in required.items(): + for path, packages in dependencies.paths.items(): found = filesystem.get(path, set()) if found.intersection(packages): continue diff --git a/src/ahriman/core/status/__init__.py b/src/ahriman/core/status/__init__.py index 78e01321..daec2653 100644 --- a/src/ahriman/core/status/__init__.py +++ b/src/ahriman/core/status/__init__.py @@ -17,3 +17,4 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from ahriman.core.status.client import Client diff --git a/src/ahriman/core/status/client.py b/src/ahriman/core/status/client.py index 585f2aec..c8a3398e 100644 --- a/src/ahriman/core/status/client.py +++ b/src/ahriman/core/status/client.py @@ -17,16 +17,18 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +# pylint: disable=too-many-public-methods from __future__ import annotations -import logging - from ahriman.core.configuration import Configuration +from ahriman.core.database import SQLite from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.changes import Changes +from ahriman.models.dependencies import Dependencies from ahriman.models.internal_status import InternalStatus from ahriman.models.log_record_id import LogRecordId from ahriman.models.package import Package +from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.repository_id import RepositoryId @@ -36,22 +38,31 @@ class Client: """ @staticmethod - def load(repository_id: RepositoryId, configuration: Configuration, *, report: bool) -> Client: + def load(repository_id: RepositoryId, configuration: Configuration, database: SQLite | None = None, *, + report: bool = True) -> Client: """ load client from settings Args: repository_id(RepositoryId): repository unique identifier configuration(Configuration): configuration instance - report(bool): force enable or disable reporting + database(SQLite | None, optional): database instance (Default value = None) + report(bool, optional): force enable or disable reporting (Default value = True) Returns: Client: client according to current settings """ + def make_local_client() -> Client: + if database is None: + return Client() + + from ahriman.core.status.local_client import LocalClient + return LocalClient(repository_id, database) + if not report: - return Client() + return make_local_client() if not configuration.getboolean("status", "enabled", fallback=True): # global switch - return Client() + return make_local_client() # new-style section address = configuration.get("status", "address", fallback=None) @@ -65,16 +76,8 @@ class Client: if address or legacy_address or (host and port) or socket: from ahriman.core.status.web_client import WebClient return WebClient(repository_id, configuration) - return Client() - def package_add(self, package: Package, status: BuildStatusEnum) -> None: - """ - add new package with status - - Args: - package(Package): package properties - status(BuildStatusEnum): current package build status - """ + return make_local_client() def package_changes_get(self, package_base: str) -> Changes: """ @@ -85,18 +88,52 @@ class Client: Returns: Changes: package changes if available and empty object otherwise - """ - del package_base - return Changes() - def package_changes_set(self, package_base: str, changes: Changes) -> None: + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError + + def package_changes_update(self, package_base: str, changes: Changes) -> None: """ update package changes Args: package_base(str): package base to update changes(Changes): changes descriptor + + Raises: + NotImplementedError: not implemented method """ + raise NotImplementedError + + def package_dependencies_get(self, package_base: str) -> Dependencies: + """ + get package dependencies + + Args: + package_base(str): package base to retrieve + + Returns: + list[Dependencies]: package implicit dependencies if available + + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError + + def package_dependencies_update(self, package_base: str, dependencies: Dependencies) -> None: + """ + update package dependencies + + Args: + package_base(str): package base to update + dependencies(Dependencies): dependencies descriptor + + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]: """ @@ -107,18 +144,94 @@ class Client: Returns: list[tuple[Package, BuildStatus]]: list of current package description and status if it has been found - """ - del package_base - return [] - def package_logs(self, log_record_id: LogRecordId, record: logging.LogRecord) -> None: + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError + + def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None: """ post log record Args: log_record_id(LogRecordId): log record id - record(logging.LogRecord): log record to post to api + created(float): log created timestamp + message(str): log message """ + # this method does not raise NotImplementedError because it is actively used as dummy client for http log + + def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]: + """ + get package logs + + Args: + package_base(str): package base + limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) + offset(int, optional): records offset (Default value = 0) + + Returns: + list[tuple[float, str]]: package logs + + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError + + def package_logs_remove(self, package_base: str, version: str | None) -> None: + """ + remove package logs + + Args: + package_base(str): package base + version(str | None): package version to remove logs. If None set, all logs will be removed + + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError + + def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]: + """ + get package patches + + Args: + package_base(str): package base to retrieve + variable(str | None): optional filter by patch variable + + Returns: + list[PkgbuildPatch]: list of patches for the specified package + + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError + + def package_patches_remove(self, package_base: str, variable: str | None) -> None: + """ + remove package patch + + Args: + package_base(str): package base to update + variable(str | None): patch name. If None set, all patches will be removed + + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError + + def package_patches_update(self, package_base: str, patch: PkgbuildPatch) -> None: + """ + create or update package patch + + Args: + package_base(str): package base to update + patch(PkgbuildPatch): package patch + + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError def package_remove(self, package_base: str) -> None: """ @@ -126,16 +239,37 @@ class Client: Args: package_base(str): package base to remove - """ - def package_update(self, package_base: str, status: BuildStatusEnum) -> None: + Raises: + NotImplementedError: not implemented method """ - update package build status. Unlike :func:`package_add()` it does not update package properties + raise NotImplementedError + + def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None: + """ + update package build status. Unlike :func:`package_update()` it does not update package properties Args: package_base(str): package base to update status(BuildStatusEnum): current package build status + + Raises: + NotImplementedError: not implemented method """ + raise NotImplementedError + + def package_update(self, package: Package, status: BuildStatusEnum) -> None: + """ + add new package or update existing one with status + + Args: + package(Package): package properties + status(BuildStatusEnum): current package build status + + Raises: + NotImplementedError: not implemented method + """ + raise NotImplementedError def set_building(self, package_base: str) -> None: """ @@ -144,7 +278,7 @@ class Client: Args: package_base(str): package base to update """ - return self.package_update(package_base, BuildStatusEnum.Building) + self.package_status_update(package_base, BuildStatusEnum.Building) def set_failed(self, package_base: str) -> None: """ @@ -153,7 +287,7 @@ class Client: Args: package_base(str): package base to update """ - return self.package_update(package_base, BuildStatusEnum.Failed) + self.package_status_update(package_base, BuildStatusEnum.Failed) def set_pending(self, package_base: str) -> None: """ @@ -162,7 +296,7 @@ class Client: Args: package_base(str): package base to update """ - return self.package_update(package_base, BuildStatusEnum.Pending) + self.package_status_update(package_base, BuildStatusEnum.Pending) def set_success(self, package: Package) -> None: """ @@ -171,16 +305,19 @@ class Client: Args: package(Package): current package properties """ - return self.package_add(package, BuildStatusEnum.Success) + self.package_update(package, BuildStatusEnum.Success) def set_unknown(self, package: Package) -> None: """ - set package status to unknown + set package status to unknown. Unlike other methods, this method also checks if package is known, + and - in case if it is - it silently skips updatd Args: package(Package): current package properties """ - return self.package_add(package, BuildStatusEnum.Unknown) + if self.package_get(package.base): + return # skip update in case if package is already known + self.package_update(package, BuildStatusEnum.Unknown) def status_get(self) -> InternalStatus: """ diff --git a/src/ahriman/core/status/local_client.py b/src/ahriman/core/status/local_client.py new file mode 100644 index 00000000..e0900805 --- /dev/null +++ b/src/ahriman/core/status/local_client.py @@ -0,0 +1,214 @@ +# +# Copyright (c) 2021-2024 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from ahriman.core.database import SQLite +from ahriman.core.status import Client +from ahriman.models.build_status import BuildStatus, BuildStatusEnum +from ahriman.models.changes import Changes +from ahriman.models.dependencies import Dependencies +from ahriman.models.log_record_id import LogRecordId +from ahriman.models.package import Package +from ahriman.models.pkgbuild_patch import PkgbuildPatch +from ahriman.models.repository_id import RepositoryId + + +class LocalClient(Client): + """ + local database handler + + Attributes: + database(SQLite): database instance + repository_id(RepositoryId): repository unique identifier + """ + + def __init__(self, repository_id: RepositoryId, database: SQLite) -> None: + """ + default constructor + + Args: + repository_id(RepositoryId): repository unique identifier + database(SQLite): database instance: + """ + self.database = database + self.repository_id = repository_id + + def package_changes_get(self, package_base: str) -> Changes: + """ + get package changes + + Args: + package_base(str): package base to retrieve + + Returns: + Changes: package changes if available and empty object otherwise + """ + return self.database.changes_get(package_base, self.repository_id) + + def package_changes_update(self, package_base: str, changes: Changes) -> None: + """ + update package changes + + Args: + package_base(str): package base to update + changes(Changes): changes descriptor + """ + self.database.changes_insert(package_base, changes, self.repository_id) + + def package_dependencies_get(self, package_base: str) -> Dependencies: + """ + get package dependencies + + Args: + package_base(str): package base to retrieve + + Returns: + list[Dependencies]: package implicit dependencies if available + """ + return self.database.dependencies_get(package_base, self.repository_id).get(package_base, Dependencies()) + + def package_dependencies_update(self, package_base: str, dependencies: Dependencies) -> None: + """ + update package dependencies + + Args: + package_base(str): package base to update + dependencies(Dependencies): dependencies descriptor + """ + self.database.dependencies_insert(package_base, dependencies, self.repository_id) + + def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]: + """ + get package status + + Args: + package_base(str | None): package base to get + + Returns: + list[tuple[Package, BuildStatus]]: list of current package description and status if it has been found + """ + packages = self.database.packages_get(self.repository_id) + if package_base is None: + return packages + return [(package, status) for package, status in packages if package.base == package_base] + + def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None: + """ + post log record + + Args: + log_record_id(LogRecordId): log record id + created(float): log created timestamp + message(str): log message + """ + self.database.logs_insert(log_record_id, created, message, self.repository_id) + + def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]: + """ + get package logs + + Args: + package_base(str): package base + limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) + offset(int, optional): records offset (Default value = 0) + + Returns: + list[tuple[float, str]]: package logs + """ + return self.database.logs_get(package_base, limit, offset, self.repository_id) + + def package_logs_remove(self, package_base: str, version: str | None) -> None: + """ + remove package logs + + Args: + package_base(str): package base + version(str | None): package version to remove logs. If None set, all logs will be removed + """ + self.database.logs_remove(package_base, version, self.repository_id) + + def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]: + """ + get package patches + + Args: + package_base(str): package base to retrieve + variable(str | None): optional filter by patch variable + + Returns: + list[PkgbuildPatch]: list of patches for the specified package + """ + variables = [variable] if variable is not None else None + return self.database.patches_list(package_base, variables).get(package_base, []) + + def package_patches_remove(self, package_base: str, variable: str | None) -> None: + """ + remove package patch + + Args: + package_base(str): package base to update + variable(str | None): patch name. If None set, all patches will be removed + """ + variables = [variable] if variable is not None else None + self.database.patches_remove(package_base, variables) + + def package_patches_update(self, package_base: str, patch: PkgbuildPatch) -> None: + """ + create or update package patch + + Args: + package_base(str): package base to update + patch(PkgbuildPatch): package patch + """ + self.database.patches_insert(package_base, [patch]) + + def package_remove(self, package_base: str) -> None: + """ + remove packages from watcher + + Args: + package_base(str): package base to remove + """ + self.database.package_clear(package_base) + + def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None: + """ + update package build status. Unlike :func:`package_update()` it does not update package properties + + Args: + package_base(str): package base to update + status(BuildStatusEnum): current package build status + + Raises: + NotImplementedError: not implemented method + """ + self.database.status_update(package_base, BuildStatus(status), self.repository_id) + + def package_update(self, package: Package, status: BuildStatusEnum) -> None: + """ + add new package or update existing one with status + + Args: + package(Package): package properties + status(BuildStatusEnum): current package build status + + Raises: + NotImplementedError: not implemented method + """ + self.database.package_update(package, self.repository_id) + self.database.status_update(package.base, BuildStatus(status), self.repository_id) diff --git a/src/ahriman/core/status/watcher.py b/src/ahriman/core/status/watcher.py index 7b36dae8..a3d704ff 100644 --- a/src/ahriman/core/status/watcher.py +++ b/src/ahriman/core/status/watcher.py @@ -17,17 +17,19 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # +from collections.abc import Callable from threading import Lock +from typing import Any, Self -from ahriman.core.database import SQLite from ahriman.core.exceptions import UnknownPackageError from ahriman.core.log import LazyLogging +from ahriman.core.status import Client from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.changes import Changes +from ahriman.models.dependencies import Dependencies from ahriman.models.log_record_id import LogRecordId from ahriman.models.package import Package from ahriman.models.pkgbuild_patch import PkgbuildPatch -from ahriman.models.repository_id import RepositoryId class Watcher(LazyLogging): @@ -35,21 +37,18 @@ class Watcher(LazyLogging): package status watcher Attributes: - database(SQLite): database instance - repository_id(RepositoryId): repository unique identifier + client(Client): reporter instance status(BuildStatus): daemon status """ - def __init__(self, repository_id: RepositoryId, database: SQLite) -> None: + def __init__(self, client: Client) -> None: """ default constructor Args: - repository_id(RepositoryId): repository unique identifier - database(SQLite): database instance + client(Client): reporter instance """ - self.repository_id = repository_id - self.database = database + self.client = client self._lock = Lock() self._known: dict[str, tuple[Package, BuildStatus]] = {} @@ -76,61 +75,16 @@ class Watcher(LazyLogging): with self._lock: self._known = { package.base: (package, status) - for package, status in self.database.packages_get(self.repository_id) + for package, status in self.client.package_get(None) } - def logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]: - """ - extract logs for the package base + package_changes_get: Callable[[str], Changes] - Args: - package_base(str): package base - limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) - offset(int, optional): records offset (Default value = 0) + package_changes_update: Callable[[str, Changes], None] - Returns: - list[tuple[float, str]]: package logs - """ - self.package_get(package_base) - return self.database.logs_get(package_base, limit, offset, self.repository_id) + package_dependencies_get: Callable[[str], Dependencies] - def logs_remove(self, package_base: str, version: str | None) -> None: - """ - remove package related logs - - Args: - package_base(str): package base - version(str): package versio - """ - self.database.logs_remove(package_base, version, self.repository_id) - - def logs_update(self, log_record_id: LogRecordId, created: float, record: str) -> None: - """ - make new log record into database - - Args: - log_record_id(LogRecordId): log record id - created(float): log created timestamp - record(str): log record - """ - if self._last_log_record_id != log_record_id: - # there is new log record, so we remove old ones - self.logs_remove(log_record_id.package_base, log_record_id.version) - self._last_log_record_id = log_record_id - self.database.logs_insert(log_record_id, created, record, self.repository_id) - - def package_changes_get(self, package_base: str) -> Changes: - """ - retrieve package changes - - Args: - package_base(str): package base - - Returns: - Changes: package changes if available - """ - self.package_get(package_base) - return self.database.changes_get(package_base, self.repository_id) + package_dependencies_update: Callable[[str, Dependencies], None] def package_get(self, package_base: str) -> tuple[Package, BuildStatus]: """ @@ -151,6 +105,31 @@ class Watcher(LazyLogging): except KeyError: raise UnknownPackageError(package_base) from None + def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None: + """ + make new log record into database + + Args: + log_record_id(LogRecordId): log record id + created(float): log created timestamp + message(str): log message + """ + if self._last_log_record_id != log_record_id: + # there is new log record, so we remove old ones + self.package_logs_remove(log_record_id.package_base, log_record_id.version) + self._last_log_record_id = log_record_id + self.client.package_logs_add(log_record_id, created, message) + + package_logs_get: Callable[[str, int, int], list[tuple[float, str]]] + + package_logs_remove: Callable[[str, str | None], None] + + package_patches_get: Callable[[str, str | None], list[PkgbuildPatch]] + + package_patches_remove: Callable[[str, str], None] + + package_patches_update: Callable[[str, PkgbuildPatch], None] + def package_remove(self, package_base: str) -> None: """ remove package base from known list if any @@ -160,60 +139,33 @@ class Watcher(LazyLogging): """ with self._lock: self._known.pop(package_base, None) - self.database.package_remove(package_base, self.repository_id) - self.logs_remove(package_base, None) + self.client.package_remove(package_base) + self.package_logs_remove(package_base, None) - def package_update(self, package_base: str, status: BuildStatusEnum, package: Package | None) -> None: + def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None: """ - update package status and description + update package status Args: package_base(str): package base to update status(BuildStatusEnum): new build status - package(Package | None): optional package description. In case if not set current properties will be used """ - if package is None: - package, _ = self.package_get(package_base) - full_status = BuildStatus(status) + package, _ = self.package_get(package_base) with self._lock: - self._known[package_base] = (package, full_status) - self.database.package_update(package, full_status, self.repository_id) + self._known[package_base] = (package, BuildStatus(status)) + self.client.package_status_update(package_base, status) - def patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]: + def package_update(self, package: Package, status: BuildStatusEnum) -> None: """ - get patches for the package + update package Args: - package_base(str): package base - variable(str | None): patch variable name if any - - Returns: - list[PkgbuildPatch]: list of patches which are stored for the package + package(Package): package description + status(BuildStatusEnum): new build status """ - # patches are package base based, we don't know (and don't differentiate) to which package does them belong - # so here we skip checking if package exists or not - variables = [variable] if variable is not None else None - return self.database.patches_list(package_base, variables).get(package_base, []) - - def patches_remove(self, package_base: str, variable: str) -> None: - """ - remove package patch - - Args: - package_base(str): package base - variable(str): patch variable name - """ - self.database.patches_remove(package_base, [variable]) - - def patches_update(self, package_base: str, patch: PkgbuildPatch) -> None: - """ - update package patch - - Args: - package_base(str): package base - patch(PkgbuildPatch): package patch - """ - self.database.patches_insert(package_base, [patch]) + with self._lock: + self._known[package.base] = (package, BuildStatus(status)) + self.client.package_update(package, status) def status_update(self, status: BuildStatusEnum) -> None: """ @@ -223,3 +175,34 @@ class Watcher(LazyLogging): status(BuildStatusEnum): new service status """ self.status = BuildStatus(status) + + def __call__(self, package_base: str | None) -> Self: + """ + extract client for future calls + + Args: + package_base(str | None): package base to validate that package exists if applicable + + Returns: + Self: instance of self to pass calls to the client + """ + if package_base is not None: + _ = self.package_get(package_base) + return self + + def __getattr__(self, item: str) -> Any: + """ + proxy methods for reporter client + + Args: + item(str): property name: + + Returns: + Any: attribute by its name + + Raises: + AttributeError: in case if no such attribute found + """ + if (method := getattr(self.client, item, None)) is not None: + return method + raise AttributeError(f"'{self.__class__.__qualname__}' object has no attribute '{item}'") diff --git a/src/ahriman/core/status/web_client.py b/src/ahriman/core/status/web_client.py index 45a05a90..24b4ccd3 100644 --- a/src/ahriman/core/status/web_client.py +++ b/src/ahriman/core/status/web_client.py @@ -18,18 +18,19 @@ # along with this program. If not, see . # import contextlib -import logging from urllib.parse import quote_plus as urlencode from ahriman.core.configuration import Configuration from ahriman.core.http import SyncAhrimanClient -from ahriman.core.status.client import Client +from ahriman.core.status import Client from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.changes import Changes +from ahriman.models.dependencies import Dependencies from ahriman.models.internal_status import InternalStatus from ahriman.models.log_record_id import LogRecordId from ahriman.models.package import Package +from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.repository_id import RepositoryId @@ -92,10 +93,22 @@ class WebClient(Client, SyncAhrimanClient): package_base(str): package base Returns: - str: full url for web service for logs + str: full url for web service for changes """ return f"{self.address}/api/v1/packages/{urlencode(package_base)}/changes" + def _dependencies_url(self, package_base: str) -> str: + """ + get url for the dependencies api + + Args: + package_base(str): package base + + Returns: + str: full url for web service for dependencies + """ + return f"{self.address}/api/v1/packages/{urlencode(package_base)}/dependencies" + def _logs_url(self, package_base: str) -> str: """ get url for the logs api @@ -110,7 +123,7 @@ class WebClient(Client, SyncAhrimanClient): def _package_url(self, package_base: str = "") -> str: """ - url generator + package url generator Args: package_base(str, optional): package base to generate url (Default value = "") @@ -121,6 +134,20 @@ class WebClient(Client, SyncAhrimanClient): suffix = f"/{urlencode(package_base)}" if package_base else "" return f"{self.address}/api/v1/packages{suffix}" + def _patches_url(self, package_base: str, variable: str = "") -> str: + """ + patches url generator + + Args: + package_base(str): package base + variable(str, optional): patch variable name to generate url (Default value = "") + + Returns: + str: full url of web service for the package patch + """ + suffix = f"/{urlencode(variable)}" if variable else "" + return f"{self.address}/api/v1/packages/{urlencode(package_base)}/patches{suffix}" + def _status_url(self) -> str: """ get url for the status api @@ -130,22 +157,6 @@ class WebClient(Client, SyncAhrimanClient): """ return f"{self.address}/api/v1/status" - def package_add(self, package: Package, status: BuildStatusEnum) -> None: - """ - add new package with status - - Args: - package(Package): package properties - status(BuildStatusEnum): current package build status - """ - payload = { - "status": status.value, - "package": package.view() - } - with contextlib.suppress(Exception): - self.make_request("POST", self._package_url(package.base), - params=self.repository_id.query(), json=payload) - def package_changes_get(self, package_base: str) -> Changes: """ get package changes @@ -165,7 +176,7 @@ class WebClient(Client, SyncAhrimanClient): return Changes() - def package_changes_set(self, package_base: str, changes: Changes) -> None: + def package_changes_update(self, package_base: str, changes: Changes) -> None: """ update package changes @@ -177,6 +188,37 @@ class WebClient(Client, SyncAhrimanClient): self.make_request("POST", self._changes_url(package_base), params=self.repository_id.query(), json=changes.view()) + def package_dependencies_get(self, package_base: str) -> Dependencies: + """ + get package dependencies + + Args: + package_base(str): package base to retrieve + + Returns: + list[Dependencies]: package implicit dependencies if available + """ + with contextlib.suppress(Exception): + response = self.make_request("GET", self._dependencies_url(package_base), + params=self.repository_id.query()) + response_json = response.json() + + return Dependencies.from_json(response_json) + + return Dependencies() + + def package_dependencies_update(self, package_base: str, dependencies: Dependencies) -> None: + """ + update package dependencies + + Args: + package_base(str): package base to update + dependencies(Dependencies): dependencies descriptor + """ + with contextlib.suppress(Exception): + self.make_request("POST", self._dependencies_url(package_base), + params=self.repository_id.query(), json=dependencies.view()) + def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]: """ get package status @@ -199,17 +241,18 @@ class WebClient(Client, SyncAhrimanClient): return [] - def package_logs(self, log_record_id: LogRecordId, record: logging.LogRecord) -> None: + def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None: """ post log record Args: log_record_id(LogRecordId): log record id - record(logging.LogRecord): log record to post to api + created(float): log created timestamp + message(str): log message """ payload = { - "created": record.created, - "message": record.getMessage(), + "created": created, + "message": message, "version": log_record_id.version, } @@ -219,6 +262,83 @@ class WebClient(Client, SyncAhrimanClient): self.make_request("POST", self._logs_url(log_record_id.package_base), params=self.repository_id.query(), json=payload, suppress_errors=True) + def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]: + """ + get package logs + + Args: + package_base(str): package base + limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1) + offset(int, optional): records offset (Default value = 0) + + Returns: + list[tuple[float, str]]: package logs + """ + with contextlib.suppress(Exception): + query = self.repository_id.query() + [("limit", str(limit)), ("offset", str(offset))] + response = self.make_request("GET", self._logs_url(package_base), params=query) + response_json = response.json() + + return [(record["created"], record["message"]) for record in response_json] + + return [] + + def package_logs_remove(self, package_base: str, version: str | None) -> None: + """ + remove package logs + + Args: + package_base(str): package base + version(str | None): package version to remove logs. If None set, all logs will be removed + """ + with contextlib.suppress(Exception): + query = self.repository_id.query() + if version is not None: + query += [("version", version)] + self.make_request("DELETE", self._logs_url(package_base), params=query) + + def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]: + """ + get package patches + + Args: + package_base(str): package base to retrieve + variable(str | None): optional filter by patch variable + + Returns: + list[PkgbuildPatch]: list of patches for the specified package + """ + with contextlib.suppress(Exception): + response = self.make_request("GET", self._patches_url(package_base, variable or "")) + response_json = response.json() + + patches = response_json if variable is None else [response_json] + return [PkgbuildPatch.from_json(patch) for patch in patches] + + return [] + + def package_patches_remove(self, package_base: str, variable: str | None) -> None: + """ + remove package patch + + Args: + package_base(str): package base to update + variable(str | None): patch name. If None set, all patches will be removed + """ + with contextlib.suppress(Exception): + self.make_request("DELETE", self._patches_url(package_base, variable or "")) + + def package_patches_update(self, package_base: str, patch: PkgbuildPatch) -> None: + """ + create or update package patch + + Args: + package_base(str): package base to update + patch(PkgbuildPatch): package patch + """ + with contextlib.suppress(Exception): + self.make_request("POST", self._patches_url(package_base), json=patch.view()) + def package_remove(self, package_base: str) -> None: """ remove packages from watcher @@ -229,19 +349,41 @@ class WebClient(Client, SyncAhrimanClient): with contextlib.suppress(Exception): self.make_request("DELETE", self._package_url(package_base), params=self.repository_id.query()) - def package_update(self, package_base: str, status: BuildStatusEnum) -> None: + def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None: """ - update package build status. Unlike :func:`package_add()` it does not update package properties + update package build status. Unlike :func:`package_update()` it does not update package properties Args: package_base(str): package base to update status(BuildStatusEnum): current package build status + + Raises: + NotImplementedError: not implemented method """ payload = {"status": status.value} with contextlib.suppress(Exception): self.make_request("POST", self._package_url(package_base), params=self.repository_id.query(), json=payload) + def package_update(self, package: Package, status: BuildStatusEnum) -> None: + """ + add new package or update existing one with status + + Args: + package(Package): package properties + status(BuildStatusEnum): current package build status + + Raises: + NotImplementedError: not implemented method + """ + payload = { + "status": status.value, + "package": package.view(), + } + with contextlib.suppress(Exception): + self.make_request("POST", self._package_url(package.base), + params=self.repository_id.query(), json=payload) + def status_get(self) -> InternalStatus: """ get internal service status diff --git a/src/ahriman/core/support/package_creator.py b/src/ahriman/core/support/package_creator.py index d33248ca..18dc06b7 100644 --- a/src/ahriman/core/support/package_creator.py +++ b/src/ahriman/core/support/package_creator.py @@ -19,12 +19,13 @@ # import shutil +from pathlib import Path + from ahriman.core import context from ahriman.core.build_tools.sources import Sources from ahriman.core.configuration import Configuration -from ahriman.core.database import SQLite +from ahriman.core.status import Client from ahriman.core.support.pkgbuild.pkgbuild_generator import PkgbuildGenerator -from ahriman.models.build_status import BuildStatus from ahriman.models.package import Package @@ -48,23 +49,39 @@ class PackageCreator: self.configuration = configuration self.generator = generator + def package_create(self, path: Path) -> None: + """ + create package files + + Args: + path(Path): path to directory with package files + """ + # clear old tree if any + shutil.rmtree(path, ignore_errors=True) + + # create local tree + path.mkdir(mode=0o755, parents=True, exist_ok=True) + self.generator.write_pkgbuild(path) + Sources.init(path) + + def package_register(self, path: Path) -> None: + """ + register package in build worker + + Args: + path(Path): path to directory with package files + """ + ctx = context.get() + reporter = ctx.get(Client) + _, repository_id = self.configuration.check_loaded() + package = Package.from_build(path, repository_id.architecture, None) + + reporter.set_unknown(package) + def run(self) -> None: """ create new local package """ local_path = self.configuration.repository_paths.cache_for(self.generator.pkgname) - - # clear old tree if any - shutil.rmtree(local_path, ignore_errors=True) - - # create local tree - local_path.mkdir(mode=0o755, parents=True, exist_ok=True) - self.generator.write_pkgbuild(local_path) - Sources.init(local_path) - - # register package - ctx = context.get() - database = ctx.get(SQLite) - _, repository_id = self.configuration.check_loaded() - package = Package.from_build(local_path, repository_id.architecture, None) - database.package_update(package, BuildStatus()) + self.package_create(local_path) + self.package_register(local_path) diff --git a/src/ahriman/models/dependencies.py b/src/ahriman/models/dependencies.py index fed89841..b823cc1c 100644 --- a/src/ahriman/models/dependencies.py +++ b/src/ahriman/models/dependencies.py @@ -17,8 +17,10 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . # -from dataclasses import dataclass, field -from pathlib import Path +from dataclasses import dataclass, field, fields +from typing import Any, Self + +from ahriman.core.util import dataclass_view, filter_json @dataclass(frozen=True) @@ -27,9 +29,31 @@ class Dependencies: package paths dependencies Attributes: - package_base(str): package base - paths(dict[Path, list[str]]): map of the paths used by this package to set of packages in which they were found + paths(dict[str, list[str]]): map of the paths used by this package to set of packages in which they were found """ - package_base: str - paths: dict[Path, list[str]] = field(default_factory=dict) + paths: dict[str, list[str]] = field(default_factory=dict) + + @classmethod + def from_json(cls, dump: dict[str, Any]) -> Self: + """ + construct dependencies from the json dump + + Args: + dump(dict[str, Any]): json dump body + + Returns: + Self: dependencies object + """ + # filter to only known fields + known_fields = [pair.name for pair in fields(cls)] + return cls(**filter_json(dump, known_fields)) + + def view(self) -> dict[str, Any]: + """ + generate json dependencies view + + Returns: + dict[str, Any]: json-friendly dictionary + """ + return dataclass_view(self) diff --git a/src/ahriman/models/package_archive.py b/src/ahriman/models/package_archive.py index dd82cb47..974f95bb 100644 --- a/src/ahriman/models/package_archive.py +++ b/src/ahriman/models/package_archive.py @@ -99,7 +99,7 @@ class PackageArchive: """ dependencies, roots = self.depends_on_paths() - result: dict[Path, list[str]] = {} + result: dict[str, list[str]] = {} for package, (directories, files) in self.installed_packages().items(): if package in self.package.packages: continue # skip package itself @@ -108,9 +108,9 @@ class PackageArchive: required_by.extend(library for library in files if library.name in dependencies) for path in required_by: - result.setdefault(path, []).append(package) + result.setdefault(str(path), []).append(package) - return Dependencies(self.package.base, result) + return Dependencies(result) def depends_on_paths(self) -> tuple[set[str], set[Path]]: """ diff --git a/src/ahriman/models/pkgbuild_patch.py b/src/ahriman/models/pkgbuild_patch.py index b0ba710b..def9b14e 100644 --- a/src/ahriman/models/pkgbuild_patch.py +++ b/src/ahriman/models/pkgbuild_patch.py @@ -19,11 +19,11 @@ # import shlex -from dataclasses import dataclass +from dataclasses import dataclass, fields from pathlib import Path from typing import Any, Generator, Self -from ahriman.core.util import dataclass_view +from ahriman.core.util import dataclass_view, filter_json @dataclass(frozen=True) @@ -84,6 +84,21 @@ class PkgbuildPatch: raw_value = next(iter(value_parts), "") # extract raw value return cls(key, cls.parse(raw_value)) + @classmethod + def from_json(cls, dump: dict[str, Any]) -> Self: + """ + construct patch descriptor from the json dump + + Args: + dump(dict[str, Any]): json dump body + + Returns: + Self: patch object + """ + # filter to only known fields + known_fields = [pair.name for pair in fields(cls)] + return cls(**filter_json(dump, known_fields)) + @staticmethod def parse(source: str) -> str | list[str]: """ diff --git a/src/ahriman/web/schemas/__init__.py b/src/ahriman/web/schemas/__init__.py index 3b3e4bd6..aac967f8 100644 --- a/src/ahriman/web/schemas/__init__.py +++ b/src/ahriman/web/schemas/__init__.py @@ -22,6 +22,7 @@ from ahriman.web.schemas.auth_schema import AuthSchema from ahriman.web.schemas.build_options_schema import BuildOptionsSchema from ahriman.web.schemas.changes_schema import ChangesSchema from ahriman.web.schemas.counters_schema import CountersSchema +from ahriman.web.schemas.dependencies_schema import DependenciesSchema from ahriman.web.schemas.error_schema import ErrorSchema from ahriman.web.schemas.file_schema import FileSchema from ahriman.web.schemas.info_schema import InfoSchema @@ -36,6 +37,7 @@ from ahriman.web.schemas.package_patch_schema import PackagePatchSchema from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchema from ahriman.web.schemas.package_schema import PackageSchema from ahriman.web.schemas.package_status_schema import PackageStatusSchema, PackageStatusSimplifiedSchema +from ahriman.web.schemas.package_version_schema import PackageVersionSchema from ahriman.web.schemas.pagination_schema import PaginationSchema from ahriman.web.schemas.patch_name_schema import PatchNameSchema from ahriman.web.schemas.patch_schema import PatchSchema diff --git a/src/ahriman/web/schemas/dependencies_schema.py b/src/ahriman/web/schemas/dependencies_schema.py new file mode 100644 index 00000000..0c8d98ac --- /dev/null +++ b/src/ahriman/web/schemas/dependencies_schema.py @@ -0,0 +1,31 @@ +# +# Copyright (c) 2021-2024 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from marshmallow import Schema, fields + + +class DependenciesSchema(Schema): + """ + request/response package dependencies schema + """ + + paths = fields.Dict( + keys=fields.String(), values=fields.List(fields.String()), required=True, metadata={ + "description": "Map of filesystem paths to packages which contain this path", + }) diff --git a/src/ahriman/web/schemas/package_version_schema.py b/src/ahriman/web/schemas/package_version_schema.py new file mode 100644 index 00000000..8ec967db --- /dev/null +++ b/src/ahriman/web/schemas/package_version_schema.py @@ -0,0 +1,34 @@ +# +# Copyright (c) 2021-2024 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +from marshmallow import fields + +from ahriman import __version__ +from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema + + +class PackageVersionSchema(RepositoryIdSchema): + """ + request package name schema + """ + + version = fields.String(metadata={ + "description": "Package version", + "example": __version__, + }) diff --git a/src/ahriman/web/schemas/patch_schema.py b/src/ahriman/web/schemas/patch_schema.py index aa6cebb5..56032128 100644 --- a/src/ahriman/web/schemas/patch_schema.py +++ b/src/ahriman/web/schemas/patch_schema.py @@ -25,8 +25,8 @@ class PatchSchema(Schema): request and response patch schema """ - key = fields.String(required=True, metadata={ - "description": "environment variable name", + key = fields.String(metadata={ + "description": "environment variable name. Required in case if it is not full diff", }) value = fields.String(metadata={ "description": "environment variable value", diff --git a/src/ahriman/web/views/base.py b/src/ahriman/web/views/base.py index 061c9370..6807175a 100644 --- a/src/ahriman/web/views/base.py +++ b/src/ahriman/web/views/base.py @@ -25,6 +25,7 @@ from typing import TypeVar from ahriman.core.auth import Auth from ahriman.core.configuration import Configuration from ahriman.core.distributed import WorkersCache +from ahriman.core.exceptions import UnknownPackageError from ahriman.core.sign.gpg import GPG from ahriman.core.spawn import Spawn from ahriman.core.status.watcher import Watcher @@ -218,12 +219,13 @@ class BaseView(View, CorsViewMixin): return RepositoryId(architecture, name) return next(iter(sorted(self.services.keys()))) - def service(self, repository_id: RepositoryId | None = None) -> Watcher: + def service(self, repository_id: RepositoryId | None = None, package_base: str | None = None) -> Watcher: """ get status watcher instance Args: repository_id(RepositoryId | None, optional): repository unique identifier (Default value = None) + package_base(str | None, optional): package base to validate if exists (Default value = None) Returns: Watcher: build status watcher instance. If no repository provided, it will return the first one @@ -234,9 +236,11 @@ class BaseView(View, CorsViewMixin): if repository_id is None: repository_id = self.repository_id() try: - return self.services[repository_id] + return self.services[repository_id](package_base) except KeyError: raise HTTPNotFound(reason=f"Repository {repository_id.id} is unknown") + except UnknownPackageError: + raise HTTPNotFound(reason=f"Package {package_base} is unknown") async def username(self) -> str | None: """ diff --git a/src/ahriman/web/views/v1/packages/changes.py b/src/ahriman/web/views/v1/packages/changes.py index b14eb282..6a7765d7 100644 --- a/src/ahriman/web/views/v1/packages/changes.py +++ b/src/ahriman/web/views/v1/packages/changes.py @@ -19,9 +19,8 @@ # import aiohttp_apispec # type: ignore[import-untyped] -from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response +from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response -from ahriman.core.exceptions import UnknownPackageError from ahriman.models.changes import Changes from ahriman.models.user_access import UserAccess from ahriman.web.schemas import AuthSchema, ChangesSchema, ErrorSchema, PackageNameSchema, RepositoryIdSchema @@ -70,10 +69,7 @@ class ChangesView(StatusViewGuard, BaseView): """ package_base = self.request.match_info["package"] - try: - changes = self.service().package_changes_get(package_base) - except UnknownPackageError: - raise HTTPNotFound(reason=f"Package {package_base} is unknown") + changes = self.service(package_base=package_base).package_changes_get(package_base) return json_response(changes.view()) @@ -113,7 +109,6 @@ class ChangesView(StatusViewGuard, BaseView): raise HTTPBadRequest(reason=str(ex)) changes = Changes(last_commit_sha, change) - repository_id = self.repository_id() - self.service(repository_id).database.changes_insert(package_base, changes, repository_id) + self.service().package_changes_update(package_base, changes) raise HTTPNoContent diff --git a/src/ahriman/web/views/v1/packages/dependencies.py b/src/ahriman/web/views/v1/packages/dependencies.py new file mode 100644 index 00000000..f4fce028 --- /dev/null +++ b/src/ahriman/web/views/v1/packages/dependencies.py @@ -0,0 +1,113 @@ +# +# Copyright (c) 2021-2024 ahriman team. +# +# This file is part of ahriman +# (see https://github.com/arcan1s/ahriman). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +import aiohttp_apispec # type: ignore[import-untyped] + +from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response + +from ahriman.models.dependencies import Dependencies +from ahriman.models.user_access import UserAccess +from ahriman.web.schemas import AuthSchema, DependenciesSchema, ErrorSchema, PackageNameSchema, RepositoryIdSchema +from ahriman.web.views.base import BaseView +from ahriman.web.views.status_view_guard import StatusViewGuard + + +class DependenciesView(StatusViewGuard, BaseView): + """ + package dependencies web view + + Attributes: + GET_PERMISSION(UserAccess): (class attribute) get permissions of self + POST_PERMISSION(UserAccess): (class attribute) post permissions of self + """ + + GET_PERMISSION = UserAccess.Reporter + POST_PERMISSION = UserAccess.Full + ROUTES = ["/api/v1/packages/{package}/dependencies"] + + @aiohttp_apispec.docs( + tags=["Build"], + summary="Get package dependencies", + description="Retrieve package implicit dependencies", + responses={ + 200: {"description": "Success response", "schema": DependenciesSchema}, + 401: {"description": "Authorization required", "schema": ErrorSchema}, + 403: {"description": "Access is forbidden", "schema": ErrorSchema}, + 404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema}, + 500: {"description": "Internal server error", "schema": ErrorSchema}, + }, + security=[{"token": [GET_PERMISSION]}], + ) + @aiohttp_apispec.cookies_schema(AuthSchema) + @aiohttp_apispec.match_info_schema(PackageNameSchema) + @aiohttp_apispec.querystring_schema(RepositoryIdSchema) + async def get(self) -> Response: + """ + get package dependencies + + Returns: + Response: 200 with package implicit dependencies on success + + Raises: + HTTPNotFound: if package base is unknown + """ + package_base = self.request.match_info["package"] + + dependencies = self.service(package_base=package_base).package_dependencies_get(package_base) + + return json_response(dependencies.view()) + + @aiohttp_apispec.docs( + tags=["Build"], + summary="Update package dependencies", + description="Set package implicit dependencies", + responses={ + 204: {"description": "Success response"}, + 400: {"description": "Bad data is supplied", "schema": ErrorSchema}, + 401: {"description": "Authorization required", "schema": ErrorSchema}, + 403: {"description": "Access is forbidden", "schema": ErrorSchema}, + 404: {"description": "Repository is unknown", "schema": ErrorSchema}, + 500: {"description": "Internal server error", "schema": ErrorSchema}, + }, + security=[{"token": [POST_PERMISSION]}], + ) + @aiohttp_apispec.cookies_schema(AuthSchema) + @aiohttp_apispec.match_info_schema(PackageNameSchema) + @aiohttp_apispec.querystring_schema(RepositoryIdSchema) + @aiohttp_apispec.json_schema(DependenciesSchema) + async def post(self) -> None: + """ + insert new package dependencies + + Raises: + HTTPBadRequest: if bad data is supplied + HTTPNoContent: in case of success response + """ + package_base = self.request.match_info["package"] + + try: + data = await self.request.json() + data["package_base"] = package_base # read from path instead of object + dependencies = Dependencies.from_json(data) + except Exception as ex: + raise HTTPBadRequest(reason=str(ex)) + + self.service(package_base=package_base).package_dependencies_update(package_base, dependencies) + + raise HTTPNoContent diff --git a/src/ahriman/web/views/v1/packages/logs.py b/src/ahriman/web/views/v1/packages/logs.py index af527c46..be59685e 100644 --- a/src/ahriman/web/views/v1/packages/logs.py +++ b/src/ahriman/web/views/v1/packages/logs.py @@ -25,8 +25,8 @@ from ahriman.core.exceptions import UnknownPackageError from ahriman.core.util import pretty_datetime from ahriman.models.log_record_id import LogRecordId from ahriman.models.user_access import UserAccess -from ahriman.web.schemas import AuthSchema, ErrorSchema, LogsSchema, PackageNameSchema, RepositoryIdSchema, \ - VersionedLogSchema +from ahriman.web.schemas import AuthSchema, ErrorSchema, LogsSchema, PackageNameSchema, PackageVersionSchema, \ + RepositoryIdSchema, VersionedLogSchema from ahriman.web.views.base import BaseView from ahriman.web.views.status_view_guard import StatusViewGuard @@ -60,7 +60,7 @@ class LogsView(StatusViewGuard, BaseView): ) @aiohttp_apispec.cookies_schema(AuthSchema) @aiohttp_apispec.match_info_schema(PackageNameSchema) - @aiohttp_apispec.querystring_schema(RepositoryIdSchema) + @aiohttp_apispec.querystring_schema(PackageVersionSchema) async def delete(self) -> None: """ delete package logs @@ -69,7 +69,8 @@ class LogsView(StatusViewGuard, BaseView): HTTPNoContent: on success response """ package_base = self.request.match_info["package"] - self.service().logs_remove(package_base, None) + version = self.request.query.get("version") + self.service().package_logs_remove(package_base, version) raise HTTPNoContent @@ -103,7 +104,7 @@ class LogsView(StatusViewGuard, BaseView): try: _, status = self.service().package_get(package_base) - logs = self.service().logs_get(package_base) + logs = self.service(package_base=package_base).package_logs_get(package_base, -1, 0) except UnknownPackageError: raise HTTPNotFound(reason=f"Package {package_base} is unknown") @@ -149,6 +150,6 @@ class LogsView(StatusViewGuard, BaseView): except Exception as ex: raise HTTPBadRequest(reason=str(ex)) - self.service().logs_update(LogRecordId(package_base, version), created, record) + self.service().package_logs_add(LogRecordId(package_base, version), created, record) raise HTTPNoContent diff --git a/src/ahriman/web/views/v1/packages/package.py b/src/ahriman/web/views/v1/packages/package.py index f7993cf5..a2cbddd2 100644 --- a/src/ahriman/web/views/v1/packages/package.py +++ b/src/ahriman/web/views/v1/packages/package.py @@ -152,7 +152,10 @@ class PackageView(StatusViewGuard, BaseView): raise HTTPBadRequest(reason=str(ex)) try: - self.service().package_update(package_base, status, package) + if package is None: + self.service().package_status_update(package_base, status) + else: + self.service().package_update(package, status) except UnknownPackageError: raise HTTPBadRequest(reason=f"Package {package_base} is unknown, but no package body set") diff --git a/src/ahriman/web/views/v1/packages/patch.py b/src/ahriman/web/views/v1/packages/patch.py index bb72fc2e..c0d76018 100644 --- a/src/ahriman/web/views/v1/packages/patch.py +++ b/src/ahriman/web/views/v1/packages/patch.py @@ -63,7 +63,8 @@ class PatchView(StatusViewGuard, BaseView): """ package_base = self.request.match_info["package"] variable = self.request.match_info["patch"] - self.service().patches_remove(package_base, variable) + + self.service().package_patches_remove(package_base, variable) raise HTTPNoContent @@ -95,7 +96,7 @@ class PatchView(StatusViewGuard, BaseView): package_base = self.request.match_info["package"] variable = self.request.match_info["patch"] - patches = self.service().patches_get(package_base, variable) + patches = self.service().package_patches_get(package_base, variable) selected = next((patch for patch in patches if patch.key == variable), None) if selected is None: diff --git a/src/ahriman/web/views/v1/packages/patches.py b/src/ahriman/web/views/v1/packages/patches.py index 54dc3760..8b068ed4 100644 --- a/src/ahriman/web/views/v1/packages/patches.py +++ b/src/ahriman/web/views/v1/packages/patches.py @@ -63,7 +63,7 @@ class PatchesView(StatusViewGuard, BaseView): Response: 200 with package patches on success """ package_base = self.request.match_info["package"] - patches = self.service().patches_get(package_base, None) + patches = self.service().package_patches_get(package_base, None) response = [patch.view() for patch in patches] return json_response(response) @@ -96,11 +96,11 @@ class PatchesView(StatusViewGuard, BaseView): try: data = await self.request.json() - key = data["key"] + key = data.get("key") value = data["value"] except Exception as ex: raise HTTPBadRequest(reason=str(ex)) - self.service().patches_update(package_base, PkgbuildPatch(key, value)) + self.service().package_patches_update(package_base, PkgbuildPatch(key, value)) raise HTTPNoContent diff --git a/src/ahriman/web/views/v2/packages/logs.py b/src/ahriman/web/views/v2/packages/logs.py index 98e56519..049f9c0f 100644 --- a/src/ahriman/web/views/v2/packages/logs.py +++ b/src/ahriman/web/views/v2/packages/logs.py @@ -19,9 +19,8 @@ # import aiohttp_apispec # type: ignore[import-untyped] -from aiohttp.web import HTTPNotFound, Response, json_response +from aiohttp.web import Response, json_response -from ahriman.core.exceptions import UnknownPackageError from ahriman.models.user_access import UserAccess from ahriman.web.schemas import AuthSchema, ErrorSchema, LogSchema, PackageNameSchema, PaginationSchema from ahriman.web.views.base import BaseView @@ -68,10 +67,8 @@ class LogsView(StatusViewGuard, BaseView): """ package_base = self.request.match_info["package"] limit, offset = self.page() - try: - logs = self.service().logs_get(package_base, limit, offset) - except UnknownPackageError: - raise HTTPNotFound(reason=f"Package {package_base} is unknown") + + logs = self.service(package_base=package_base).package_logs_get(package_base, limit, offset) response = [ { diff --git a/src/ahriman/web/web.py b/src/ahriman/web/web.py index 64b48ae8..c3297a70 100644 --- a/src/ahriman/web/web.py +++ b/src/ahriman/web/web.py @@ -30,6 +30,7 @@ from ahriman.core.database import SQLite from ahriman.core.distributed import WorkersCache from ahriman.core.exceptions import InitializeError from ahriman.core.spawn import Spawn +from ahriman.core.status import Client from ahriman.core.status.watcher import Watcher from ahriman.models.repository_id import RepositoryId from ahriman.web.apispec import setup_apispec @@ -167,7 +168,8 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis watchers: dict[RepositoryId, Watcher] = {} for repository_id in repositories: application.logger.info("load repository %s", repository_id) - watchers[repository_id] = Watcher(repository_id, database) + client = Client.load(repository_id, configuration, database, report=False) # explicitly load local client + watchers[repository_id] = Watcher(client) application[WatcherKey] = watchers # workers cache application[WorkersKey] = WorkersCache(configuration) diff --git a/tests/ahriman/application/application/test_application.py b/tests/ahriman/application/application/test_application.py index 526683b3..a53750f7 100644 --- a/tests/ahriman/application/application/test_application.py +++ b/tests/ahriman/application/application/test_application.py @@ -93,8 +93,7 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p side_effect=lambda *args: packages[args[0].name]) packages_mock = mocker.patch("ahriman.application.application.Application._known_packages", return_value={"devtools", "python-build", "python-pytest"}) - update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update") - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_unknown") result = application.with_dependencies([package_ahriman], process_dependencies=True) assert {package.base: package for package in result} == packages @@ -107,11 +106,6 @@ def test_with_dependencies(application: Application, package_ahriman: Package, p ], any_order=True) packages_mock.assert_called_once_with() - update_remote_mock.assert_has_calls([ - MockCall(package_python_schedule), - MockCall(packages["python"]), - MockCall(packages["python-installer"]), - ], any_order=True) status_client_mock.assert_has_calls([ MockCall(package_python_schedule), MockCall(packages["python"]), diff --git a/tests/ahriman/application/application/test_application_packages.py b/tests/ahriman/application/application/test_application_packages.py index 17d0b541..94210120 100644 --- a/tests/ahriman/application/application/test_application_packages.py +++ b/tests/ahriman/application/application/test_application_packages.py @@ -41,11 +41,11 @@ def test_add_aur(application_packages: ApplicationPackages, package_ahriman: Pac """ mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman) build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert") - update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_unknown") application_packages._add_aur(package_ahriman.base, "packager") build_queue_mock.assert_called_once_with(package_ahriman) - update_remote_mock.assert_called_once_with(package_ahriman) + status_client_mock.assert_called_once_with(package_ahriman) def test_add_directory(application_packages: ApplicationPackages, package_ahriman: Package, @@ -153,11 +153,11 @@ def test_add_repository(application_packages: ApplicationPackages, package_ahrim """ mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman) build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_insert") - update_remote_mock = mocker.patch("ahriman.core.database.SQLite.package_base_update") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_unknown") application_packages._add_repository(package_ahriman.base, "packager") build_queue_mock.assert_called_once_with(package_ahriman) - update_remote_mock.assert_called_once_with(package_ahriman) + status_client_mock.assert_called_once_with(package_ahriman) def test_add_add_archive(application_packages: ApplicationPackages, package_ahriman: Package, diff --git a/tests/ahriman/application/application/test_application_properties.py b/tests/ahriman/application/application/test_application_properties.py index ab64cdcd..cf456457 100644 --- a/tests/ahriman/application/application/test_application_properties.py +++ b/tests/ahriman/application/application/test_application_properties.py @@ -1,15 +1,15 @@ from ahriman.application.application.application_properties import ApplicationProperties -def test_create_tree(application_properties: ApplicationProperties) -> None: - """ - must have repository attribute - """ - assert application_properties.repository - - def test_architecture(application_properties: ApplicationProperties) -> None: """ must return repository architecture """ assert application_properties.architecture == application_properties.repository_id.architecture + + +def test_reporter(application_properties: ApplicationProperties) -> None: + """ + must have reporter attribute + """ + assert application_properties.reporter diff --git a/tests/ahriman/application/application/test_application_repository.py b/tests/ahriman/application/application/test_application_repository.py index cfb68680..3f023f40 100644 --- a/tests/ahriman/application/application/test_application_repository.py +++ b/tests/ahriman/application/application/test_application_repository.py @@ -17,14 +17,12 @@ def test_changes(application_repository: ApplicationRepository, package_ahriman: must generate changes for the packages """ changes = Changes("hash", "change") - hashes_mock = mocker.patch("ahriman.core.database.SQLite.hashes_get", return_value={ - package_ahriman.base: changes.last_commit_sha, - }) + hashes_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get", return_value=changes) changes_mock = mocker.patch("ahriman.core.repository.Repository.package_changes", return_value=changes) - report_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set") + report_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_update") application_repository.changes([package_ahriman]) - hashes_mock.assert_called_once_with() + hashes_mock.assert_called_once_with(package_ahriman.base) changes_mock.assert_called_once_with(package_ahriman, changes.last_commit_sha) report_mock.assert_called_once_with(package_ahriman.base, changes) @@ -34,9 +32,8 @@ def test_changes_skip(application_repository: ApplicationRepository, package_ahr """ must skip change generation if no last commit sha has been found """ - mocker.patch("ahriman.core.database.SQLite.hashes_get", return_value={}) changes_mock = mocker.patch("ahriman.core.repository.Repository.package_changes") - report_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set") + report_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_update") application_repository.changes([package_ahriman]) changes_mock.assert_not_called() diff --git a/tests/ahriman/application/handlers/test_handler_add.py b/tests/ahriman/application/handlers/test_handler_add.py index cfb640bc..5f381dd0 100644 --- a/tests/ahriman/application/handlers/test_handler_add.py +++ b/tests/ahriman/application/handlers/test_handler_add.py @@ -62,11 +62,11 @@ def test_run_with_patches(args: argparse.Namespace, configuration: Configuration args.variable = ["KEY=VALUE"] mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.application.application.Application.add") - application_mock = mocker.patch("ahriman.core.database.SQLite.patches_insert") + application_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_update") _, repository_id = configuration.check_loaded() Add.run(args, repository_id, configuration, report=False) - application_mock.assert_called_once_with(args.package[0], [PkgbuildPatch("KEY", "VALUE")]) + application_mock.assert_called_once_with(args.package[0], PkgbuildPatch("KEY", "VALUE")) def test_run_with_updates(args: argparse.Namespace, configuration: Configuration, repository: Repository, diff --git a/tests/ahriman/application/handlers/test_handler_change.py b/tests/ahriman/application/handlers/test_handler_change.py index bbe02578..eb8892f4 100644 --- a/tests/ahriman/application/handlers/test_handler_change.py +++ b/tests/ahriman/application/handlers/test_handler_change.py @@ -34,7 +34,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: """ args = _default_args(args) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - application_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_get", + application_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get", return_value=Changes("sha", "change")) check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") print_mock = mocker.patch("ahriman.core.formatters.Printer.print") @@ -54,7 +54,7 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat args = _default_args(args) args.exit_code = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.core.status.client.Client.package_changes_get", return_value=Changes()) + mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get", return_value=Changes()) check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") _, repository_id = configuration.check_loaded() @@ -70,7 +70,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, repo args = _default_args(args) args.action = Action.Remove mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - update_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set") + update_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_update") _, repository_id = configuration.check_loaded() Change.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/handlers/test_handler_patch.py b/tests/ahriman/application/handlers/test_handler_patch.py index c629714c..97be1632 100644 --- a/tests/ahriman/application/handlers/test_handler_patch.py +++ b/tests/ahriman/application/handlers/test_handler_patch.py @@ -160,13 +160,28 @@ def test_patch_set_list(application: Application, mocker: MockerFixture) -> None """ must list available patches for the command """ - get_mock = mocker.patch("ahriman.core.database.SQLite.patches_list", - return_value={"ahriman": PkgbuildPatch(None, "patch")}) + get_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get", + return_value=[PkgbuildPatch(None, "patch"), PkgbuildPatch("version", "value")]) print_mock = mocker.patch("ahriman.core.formatters.Printer.print") check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") Patch.patch_set_list(application, "ahriman", ["version"], False) - get_mock.assert_called_once_with("ahriman", ["version"]) + get_mock.assert_called_once_with("ahriman", None) + print_mock.assert_called_once_with(verbose=True, log_fn=pytest.helpers.anyvar(int), separator=" = ") + check_mock.assert_called_once_with(False, False) + + +def test_patch_set_list_all(application: Application, mocker: MockerFixture) -> None: + """ + must list all available patches for the command + """ + get_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get", + return_value=[PkgbuildPatch(None, "patch")]) + print_mock = mocker.patch("ahriman.core.formatters.Printer.print") + check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") + + Patch.patch_set_list(application, "ahriman", None, False) + get_mock.assert_called_once_with("ahriman", None) print_mock.assert_called_once_with(verbose=True, log_fn=pytest.helpers.anyvar(int), separator=" = ") check_mock.assert_called_once_with(False, False) @@ -175,7 +190,7 @@ def test_patch_set_list_empty_exception(application: Application, mocker: Mocker """ must raise ExitCode exception on empty patch list """ - mocker.patch("ahriman.core.database.SQLite.patches_list", return_value={}) + mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get", return_value={}) check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") Patch.patch_set_list(application, "ahriman", [], True) @@ -186,18 +201,27 @@ def test_patch_set_create(application: Application, package_ahriman: Package, mo """ must create patch set for the package """ - create_mock = mocker.patch("ahriman.core.database.SQLite.patches_insert") + create_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_update") Patch.patch_set_create(application, package_ahriman.base, PkgbuildPatch("version", package_ahriman.version)) - create_mock.assert_called_once_with(package_ahriman.base, [PkgbuildPatch("version", package_ahriman.version)]) + create_mock.assert_called_once_with(package_ahriman.base, PkgbuildPatch("version", package_ahriman.version)) def test_patch_set_remove(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: """ must remove patch set for the package """ - remove_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove") + remove_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_remove") Patch.patch_set_remove(application, package_ahriman.base, ["version"]) - remove_mock.assert_called_once_with(package_ahriman.base, ["version"]) + remove_mock.assert_called_once_with(package_ahriman.base, "version") + + +def test_patch_set_remove_all(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must remove all patches for the package + """ + remove_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_remove") + Patch.patch_set_remove(application, package_ahriman.base, None) + remove_mock.assert_called_once_with(package_ahriman.base, None) def test_disallow_multi_architecture_run() -> None: diff --git a/tests/ahriman/application/handlers/test_handler_rebuild.py b/tests/ahriman/application/handlers/test_handler_rebuild.py index 6ba76ae1..002b9365 100644 --- a/tests/ahriman/application/handlers/test_handler_rebuild.py +++ b/tests/ahriman/application/handlers/test_handler_rebuild.py @@ -185,7 +185,7 @@ def test_extract_packages_by_status(application: Application, mocker: MockerFixt ("package2", BuildStatus(BuildStatusEnum.Failed)), ]) assert Rebuild.extract_packages(application, BuildStatusEnum.Failed, from_database=True) == ["package2"] - packages_mock.assert_called_once_with() + packages_mock.assert_called_once_with(application.repository_id) def test_extract_packages_from_database(application: Application, mocker: MockerFixture) -> None: @@ -194,4 +194,4 @@ def test_extract_packages_from_database(application: Application, mocker: Mocker """ packages_mock = mocker.patch("ahriman.core.database.SQLite.packages_get") Rebuild.extract_packages(application, None, from_database=True) - packages_mock.assert_called_once_with() + packages_mock.assert_called_once_with(application.repository_id) diff --git a/tests/ahriman/application/handlers/test_handler_status.py b/tests/ahriman/application/handlers/test_handler_status.py index 44909243..74f53e55 100644 --- a/tests/ahriman/application/handlers/test_handler_status.py +++ b/tests/ahriman/application/handlers/test_handler_status.py @@ -37,8 +37,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: """ args = _default_args(args) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - application_mock = mocker.patch("ahriman.core.status.client.Client.status_get") - packages_mock = mocker.patch("ahriman.core.status.client.Client.package_get", + application_mock = mocker.patch("ahriman.core.status.Client.status_get") + packages_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)), (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))]) check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") @@ -63,8 +63,8 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat args = _default_args(args) args.exit_code = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.core.status.client.Client.status_get") - mocker.patch("ahriman.core.status.client.Client.package_get", return_value=[]) + mocker.patch("ahriman.core.status.Client.status_get") + mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[]) check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty") _, repository_id = configuration.check_loaded() @@ -80,7 +80,7 @@ def test_run_verbose(args: argparse.Namespace, configuration: Configuration, rep args = _default_args(args) args.info = True mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.core.status.client.Client.package_get", + mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success))]) print_mock = mocker.patch("ahriman.core.formatters.Printer.print") @@ -100,7 +100,7 @@ def test_run_with_package_filter(args: argparse.Namespace, configuration: Config args = _default_args(args) args.package = [package_ahriman.base] mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - packages_mock = mocker.patch("ahriman.core.status.client.Client.package_get", + packages_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success))]) _, repository_id = configuration.check_loaded() @@ -115,7 +115,7 @@ def test_run_by_status(args: argparse.Namespace, configuration: Configuration, r """ args = _default_args(args) args.status = BuildStatusEnum.Failed - mocker.patch("ahriman.core.status.client.Client.package_get", + mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)), (package_python_schedule, BuildStatus(BuildStatusEnum.Failed))]) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) diff --git a/tests/ahriman/application/handlers/test_handler_status_update.py b/tests/ahriman/application/handlers/test_handler_status_update.py index 99a67f60..289032e4 100644 --- a/tests/ahriman/application/handlers/test_handler_status_update.py +++ b/tests/ahriman/application/handlers/test_handler_status_update.py @@ -34,7 +34,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: """ args = _default_args(args) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - update_self_mock = mocker.patch("ahriman.core.status.client.Client.status_update") + update_self_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.status_update") _, repository_id = configuration.check_loaded() StatusUpdate.run(args, repository_id, configuration, report=False) @@ -42,20 +42,17 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository: def test_run_packages(args: argparse.Namespace, configuration: Configuration, repository: Repository, - package_ahriman: Package, mocker: MockerFixture) -> None: + mocker: MockerFixture) -> None: """ must run command with specified packages """ args = _default_args(args) - args.package = [package_ahriman.base, "package"] + args.package = ["package"] mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman]) - add_mock = mocker.patch("ahriman.core.status.client.Client.package_add") - update_mock = mocker.patch("ahriman.core.status.client.Client.package_update") + update_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_update") _, repository_id = configuration.check_loaded() StatusUpdate.run(args, repository_id, configuration, report=False) - add_mock.assert_called_once_with(package_ahriman, args.status) update_mock.assert_called_once_with("package", args.status) @@ -68,7 +65,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, repo args.package = [package_ahriman.base] args.action = Action.Remove mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) - update_mock = mocker.patch("ahriman.core.status.client.Client.package_remove") + update_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") _, repository_id = configuration.check_loaded() StatusUpdate.run(args, repository_id, configuration, report=False) diff --git a/tests/ahriman/application/test_lock.py b/tests/ahriman/application/test_lock.py index 646675f4..1a0b3b03 100644 --- a/tests/ahriman/application/test_lock.py +++ b/tests/ahriman/application/test_lock.py @@ -27,7 +27,7 @@ def test_path(args: argparse.Namespace, configuration: Configuration) -> None: with pytest.raises(ValueError): args.lock = Path("/") - Lock(args, repository_id, configuration).path # special case + assert Lock(args, repository_id, configuration).path # special case def test_check_user(lock: Lock, mocker: MockerFixture) -> None: @@ -64,7 +64,7 @@ def test_check_version(lock: Lock, mocker: MockerFixture) -> None: """ must check version correctly """ - mocker.patch("ahriman.core.status.client.Client.status_get", + mocker.patch("ahriman.core.status.Client.status_get", return_value=InternalStatus(status=BuildStatus(), version=__version__)) logging_mock = mocker.patch("logging.Logger.warning") @@ -76,7 +76,7 @@ def test_check_version_mismatch(lock: Lock, mocker: MockerFixture) -> None: """ must check mismatched version correctly """ - mocker.patch("ahriman.core.status.client.Client.status_get", + mocker.patch("ahriman.core.status.Client.status_get", return_value=InternalStatus(status=BuildStatus(), version="version")) logging_mock = mocker.patch("logging.Logger.warning") @@ -184,7 +184,7 @@ def test_enter(lock: Lock, mocker: MockerFixture) -> None: watch_mock = mocker.patch("ahriman.application.lock.Lock.watch") clear_mock = mocker.patch("ahriman.application.lock.Lock.clear") create_mock = mocker.patch("ahriman.application.lock.Lock.create") - update_status_mock = mocker.patch("ahriman.core.status.client.Client.status_update") + update_status_mock = mocker.patch("ahriman.core.status.Client.status_update") with lock: pass @@ -203,9 +203,9 @@ def test_exit_with_exception(lock: Lock, mocker: MockerFixture) -> None: mocker.patch("ahriman.application.lock.Lock.check_user") mocker.patch("ahriman.application.lock.Lock.clear") mocker.patch("ahriman.application.lock.Lock.create") - update_status_mock = mocker.patch("ahriman.core.status.client.Client.status_update") + update_status_mock = mocker.patch("ahriman.core.status.Client.status_update") - with pytest.raises(Exception): + with pytest.raises(ValueError): with lock: - raise Exception() + raise ValueError() update_status_mock.assert_has_calls([MockCall(BuildStatusEnum.Building), MockCall(BuildStatusEnum.Failed)]) diff --git a/tests/ahriman/conftest.py b/tests/ahriman/conftest.py index 878b9c12..45f72896 100644 --- a/tests/ahriman/conftest.py +++ b/tests/ahriman/conftest.py @@ -13,6 +13,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.repository import Repository from ahriman.core.spawn import Spawn +from ahriman.core.status import Client from ahriman.core.status.watcher import Watcher from ahriman.models.aur_package import AURPackage from ahriman.models.build_status import BuildStatus, BuildStatusEnum @@ -276,6 +277,21 @@ def database(configuration: Configuration) -> SQLite: database.path.unlink() +@pytest.fixture +def local_client(database: SQLite, configuration: Configuration) -> Client: + """ + local status client + + Args: + database(SQLite): database fixture + + Returns: + Client: local status client test instance + """ + _, repository_id = configuration.check_loaded() + return Client.load(repository_id, configuration, database, report=False) + + @pytest.fixture def package_ahriman(package_description_ahriman: PackageDescription, remote_source: RemoteSource) -> Package: """ @@ -559,15 +575,14 @@ def user() -> User: @pytest.fixture -def watcher(repository_id: RepositoryId, database: SQLite) -> Watcher: +def watcher(local_client: Client) -> Watcher: """ package status watcher fixture Args: - repository_id(RepositoryId): repository identifier fixture - database(SQLite): database fixture + local_client(Client): local status client fixture Returns: Watcher: package status watcher test instance """ - return Watcher(repository_id, database) + return Watcher(local_client) diff --git a/tests/ahriman/core/alpm/test_pacman.py b/tests/ahriman/core/alpm/test_pacman.py index d0dce96a..33d9b2b7 100644 --- a/tests/ahriman/core/alpm/test_pacman.py +++ b/tests/ahriman/core/alpm/test_pacman.py @@ -128,7 +128,7 @@ def test_database_copy_database_exist(pacman: Pacman, mocker: MockerFixture) -> copy_mock.assert_not_called() -def test_database_init(pacman: Pacman, configuration: Configuration) -> None: +def test_database_init(pacman: Pacman) -> None: """ must init database with settings """ @@ -184,14 +184,15 @@ def test_files(pacman: Pacman, package_ahriman: Package, mocker: MockerFixture, pacman.handle = handle_mock tarball = resource_path_root / "core" / "arcanisrepo.files.tar.gz" - mocker.patch("pathlib.Path.is_file", return_value=True) - open_mock = mocker.patch("ahriman.core.alpm.pacman.tarfile.open", return_value=tarfile.open(tarball, "r:gz")) + with tarfile.open(tarball, "r:gz") as fd: + mocker.patch("pathlib.Path.is_file", return_value=True) + open_mock = mocker.patch("ahriman.core.alpm.pacman.tarfile.open", return_value=fd) - files = pacman.files() - assert len(files) == 2 - assert package_ahriman.base in files - assert Path("usr/bin/ahriman") in files[package_ahriman.base] - open_mock.assert_called_once_with(pytest.helpers.anyvar(int), "r:gz") + files = pacman.files() + assert len(files) == 2 + assert package_ahriman.base in files + assert "usr/bin/ahriman" in files[package_ahriman.base] + open_mock.assert_called_once_with(pytest.helpers.anyvar(int), "r:gz") def test_files_package(pacman: Pacman, package_ahriman: Package, mocker: MockerFixture, @@ -205,12 +206,13 @@ def test_files_package(pacman: Pacman, package_ahriman: Package, mocker: MockerF tarball = resource_path_root / "core" / "arcanisrepo.files.tar.gz" - mocker.patch("pathlib.Path.is_file", return_value=True) - mocker.patch("ahriman.core.alpm.pacman.tarfile.open", return_value=tarfile.open(tarball, "r:gz")) + with tarfile.open(tarball, "r:gz") as fd: + mocker.patch("pathlib.Path.is_file", return_value=True) + mocker.patch("ahriman.core.alpm.pacman.tarfile.open", return_value=fd) - files = pacman.files(package_ahriman.base) - assert len(files) == 1 - assert package_ahriman.base in files + files = pacman.files(package_ahriman.base) + assert len(files) == 1 + assert package_ahriman.base in files def test_files_skip(pacman: Pacman, mocker: MockerFixture) -> None: diff --git a/tests/ahriman/core/build_tools/test_task.py b/tests/ahriman/core/build_tools/test_task.py index 893b113d..399baeac 100644 --- a/tests/ahriman/core/build_tools/test_task.py +++ b/tests/ahriman/core/build_tools/test_task.py @@ -5,7 +5,7 @@ from pytest_mock import MockerFixture from unittest.mock import call as MockCall from ahriman.core.build_tools.task import Task -from ahriman.core.database import SQLite +from ahriman.models.pkgbuild_patch import PkgbuildPatch def test_build(task_ahriman: Task, mocker: MockerFixture) -> None: @@ -91,18 +91,19 @@ def test_build_no_debug(task_ahriman: Task, mocker: MockerFixture) -> None: ]) -def test_init(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> None: +def test_init(task_ahriman: Task, mocker: MockerFixture) -> None: """ must copy tree instead of fetch """ + patches = [PkgbuildPatch("hash", "patch")] mocker.patch("ahriman.models.package.Package.from_build", return_value=task_ahriman.package) load_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha") - assert task_ahriman.init(Path("ahriman"), database, None) == "sha" - load_mock.assert_called_once_with(Path("ahriman"), task_ahriman.package, [], task_ahriman.paths) + assert task_ahriman.init(Path("ahriman"), patches, None) == "sha" + load_mock.assert_called_once_with(Path("ahriman"), task_ahriman.package, patches, task_ahriman.paths) -def test_init_bump_pkgrel(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> None: +def test_init_bump_pkgrel(task_ahriman: Task, mocker: MockerFixture) -> None: """ must bump pkgrel if it is same as provided """ @@ -111,11 +112,11 @@ def test_init_bump_pkgrel(task_ahriman: Task, database: SQLite, mocker: MockerFi write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write") local = Path("ahriman") - assert task_ahriman.init(local, database, task_ahriman.package.version) == "sha" + assert task_ahriman.init(local, [], task_ahriman.package.version) == "sha" write_mock.assert_called_once_with(local / "PKGBUILD") -def test_init_bump_pkgrel_skip(task_ahriman: Task, database: SQLite, mocker: MockerFixture) -> None: +def test_init_bump_pkgrel_skip(task_ahriman: Task, mocker: MockerFixture) -> None: """ must keep pkgrel if version is different from provided """ @@ -123,5 +124,5 @@ def test_init_bump_pkgrel_skip(task_ahriman: Task, database: SQLite, mocker: Moc mocker.patch("ahriman.core.build_tools.sources.Sources.load", return_value="sha") write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write") - assert task_ahriman.init(Path("ahriman"), database, f"2:{task_ahriman.package.version}") == "sha" + assert task_ahriman.init(Path("ahriman"), [], f"2:{task_ahriman.package.version}") == "sha" write_mock.assert_not_called() diff --git a/tests/ahriman/core/database/operations/test_changes_operations.py b/tests/ahriman/core/database/operations/test_changes_operations.py index da262a47..a82ed902 100644 --- a/tests/ahriman/core/database/operations/test_changes_operations.py +++ b/tests/ahriman/core/database/operations/test_changes_operations.py @@ -53,13 +53,3 @@ def test_changes_insert_remove_full(database: SQLite, package_ahriman: Package, assert database.changes_get(package_python_schedule.base).changes is None assert database.changes_get( package_ahriman.base, RepositoryId("i686", database._repository_id.name)).changes == "change2" - - -def test_hashes_get(database: SQLite, package_ahriman: Package, package_python_schedule: Package) -> None: - """ - must return non-empty hashes for packages - """ - database.changes_insert(package_ahriman.base, Changes("sha1", "change1")) - database.changes_insert(package_python_schedule.base, Changes()) - - assert database.hashes_get() == {package_ahriman.base: "sha1"} diff --git a/tests/ahriman/core/database/operations/test_dependencies_operations.py b/tests/ahriman/core/database/operations/test_dependencies_operations.py index 5b466f46..25e37605 100644 --- a/tests/ahriman/core/database/operations/test_dependencies_operations.py +++ b/tests/ahriman/core/database/operations/test_dependencies_operations.py @@ -1,5 +1,3 @@ -from pathlib import Path - from ahriman.core.database import SQLite from ahriman.models.dependencies import Dependencies from ahriman.models.package import Package @@ -10,16 +8,19 @@ def test_dependencies_insert_get(database: SQLite, package_ahriman: Package) -> """ must insert and get dependencies """ - dependencies = Dependencies(package_ahriman.base, {Path("usr/lib/python3.11/site-packages"): ["python"]}) - database.dependencies_insert(dependencies) - assert database.dependencies_get(package_ahriman.base) == [dependencies] + dependencies = Dependencies({"usr/lib/python3.11/site-packages": ["python"]}) + database.dependencies_insert(package_ahriman.base, dependencies) + assert database.dependencies_get(package_ahriman.base) == {package_ahriman.base: dependencies} - dependencies2 = Dependencies(package_ahriman.base, {Path("usr/lib/python3.11/site-packages"): ["python3"]}) - database.dependencies_insert(dependencies2, RepositoryId("i686", database._repository_id.name)) - assert database.dependencies_get() == [dependencies] - assert database.dependencies_get(package_ahriman.base) == [dependencies] - assert database.dependencies_get( - package_ahriman.base, RepositoryId("i686", database._repository_id.name)) == [dependencies2] + dependencies2 = Dependencies({"usr/lib/python3.11/site-packages": ["python3"]}) + database.dependencies_insert( + package_ahriman.base, dependencies2, RepositoryId( + "i686", database._repository_id.name)) + assert database.dependencies_get() == {package_ahriman.base: dependencies} + assert database.dependencies_get(package_ahriman.base) == {package_ahriman.base: dependencies} + assert database.dependencies_get(package_ahriman.base, RepositoryId("i686", database._repository_id.name)) == { + package_ahriman.base: dependencies2 + } def test_dependencies_insert_remove(database: SQLite, package_ahriman: Package, @@ -27,23 +28,28 @@ def test_dependencies_insert_remove(database: SQLite, package_ahriman: Package, """ must remove dependencies for the package """ - dependencies1 = Dependencies(package_ahriman.base, {Path("usr"): ["python"]}) - database.dependencies_insert(dependencies1) - dependencies2 = Dependencies(package_python_schedule.base, {Path("usr"): ["filesystem"]}) - database.dependencies_insert(dependencies2) - dependencies3 = Dependencies(package_ahriman.base, {Path("usr"): ["python3"]}) - database.dependencies_insert(dependencies3, RepositoryId("i686", database._repository_id.name)) + dependencies1 = Dependencies({"usr": ["python"]}) + database.dependencies_insert(package_ahriman.base, dependencies1) + dependencies2 = Dependencies({"usr": ["filesystem"]}) + database.dependencies_insert(package_python_schedule.base, dependencies2) + dependencies3 = Dependencies({"usr": ["python3"]}) + database.dependencies_insert( + package_ahriman.base, dependencies3, RepositoryId( + "i686", database._repository_id.name)) - assert database.dependencies_get() == [dependencies1, dependencies2] + assert database.dependencies_get() == { + package_ahriman.base: dependencies1, + package_python_schedule.base: dependencies2, + } database.dependencies_remove(package_ahriman.base) - assert database.dependencies_get(package_ahriman.base) == [] - assert database.dependencies_get(package_python_schedule.base) == [dependencies2] + assert database.dependencies_get(package_ahriman.base) == {} + assert database.dependencies_get(package_python_schedule.base) == {package_python_schedule.base: dependencies2} # insert null database.dependencies_remove(package_ahriman.base, RepositoryId("i686", database._repository_id.name)) - assert database.dependencies_get(package_ahriman.base, RepositoryId("i686", database._repository_id.name)) == [] - assert database.dependencies_get(package_python_schedule.base) == [dependencies2] + assert database.dependencies_get(package_ahriman.base, RepositoryId("i686", database._repository_id.name)) == {} + assert database.dependencies_get(package_python_schedule.base) == {package_python_schedule.base: dependencies2} def test_dependencies_insert_remove_full(database: SQLite, package_ahriman: Package, @@ -51,11 +57,11 @@ def test_dependencies_insert_remove_full(database: SQLite, package_ahriman: Pack """ must remove all dependencies for the repository """ - database.dependencies_insert(Dependencies(package_ahriman.base, {Path("usr"): ["python"]})) - database.dependencies_insert(Dependencies(package_python_schedule.base, {Path("usr"): ["filesystem"]})) - database.dependencies_insert(Dependencies(package_ahriman.base, {Path("usr"): ["python3"]}), + database.dependencies_insert(package_ahriman.base, Dependencies({"usr": ["python"]})) + database.dependencies_insert(package_python_schedule.base, Dependencies({"usr": ["filesystem"]})) + database.dependencies_insert(package_ahriman.base, Dependencies({"usr": ["python3"]}), RepositoryId("i686", database._repository_id.name)) database.dependencies_remove(None) - assert database.dependencies_get() == [] + assert database.dependencies_get() == {} assert database.dependencies_get(package_ahriman.base, RepositoryId("i686", database._repository_id.name)) diff --git a/tests/ahriman/core/database/operations/test_package_operations.py b/tests/ahriman/core/database/operations/test_package_operations.py index 424b990c..c87d478e 100644 --- a/tests/ahriman/core/database/operations/test_package_operations.py +++ b/tests/ahriman/core/database/operations/test_package_operations.py @@ -5,10 +5,8 @@ from sqlite3 import Connection from unittest.mock import call as MockCall from ahriman.core.database import SQLite -from ahriman.models.build_status import BuildStatus, BuildStatusEnum +from ahriman.models.build_status import BuildStatus from ahriman.models.package import Package -from ahriman.models.package_source import PackageSource -from ahriman.models.remote_source import RemoteSource def test_package_remove_package_base(database: SQLite, connection: Connection) -> None: @@ -66,14 +64,6 @@ def test_package_update_insert_packages_no_arch(database: SQLite, connection: Co connection.executemany(pytest.helpers.anyvar(str, strict=True), []) -def test_package_update_insert_status(database: SQLite, connection: Connection, package_ahriman: Package) -> None: - """ - must insert single package status - """ - database._package_update_insert_status(connection, package_ahriman.base, BuildStatus(), database._repository_id) - connection.execute(pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(int)) - - def test_packages_get_select_package_bases(database: SQLite, connection: Connection) -> None: """ must select all bases @@ -131,16 +121,12 @@ def test_package_update(database: SQLite, package_ahriman: Package, mocker: Mock """ must update package status """ - status = BuildStatus() insert_base_mock = mocker.patch("ahriman.core.database.SQLite._package_update_insert_base") - insert_status_mock = mocker.patch("ahriman.core.database.SQLite._package_update_insert_status") insert_packages_mock = mocker.patch("ahriman.core.database.SQLite._package_update_insert_packages") remove_packages_mock = mocker.patch("ahriman.core.database.SQLite._package_remove_packages") - database.package_update(package_ahriman, status) + database.package_update(package_ahriman) insert_base_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman, database._repository_id) - insert_status_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.base, status, - database._repository_id) insert_packages_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman, database._repository_id) remove_packages_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.base, @@ -168,7 +154,8 @@ def test_package_update_get(database: SQLite, package_ahriman: Package) -> None: must insert and retrieve package """ status = BuildStatus() - database.package_update(package_ahriman, status) + database.package_update(package_ahriman) + database.status_update(package_ahriman.base, status) assert next((db_package, db_status) for db_package, db_status in database.packages_get() if db_package.base == package_ahriman.base) == (package_ahriman, status) @@ -178,8 +165,7 @@ def test_package_update_remove_get(database: SQLite, package_ahriman: Package) - """ must insert, remove and retrieve package """ - status = BuildStatus() - database.package_update(package_ahriman, status) + database.package_update(package_ahriman) database.package_remove(package_ahriman.base) assert not database.packages_get() @@ -188,28 +174,20 @@ def test_package_update_update(database: SQLite, package_ahriman: Package) -> No """ must perform update for existing package """ - database.package_update(package_ahriman, BuildStatus()) - database.package_update(package_ahriman, BuildStatus(BuildStatusEnum.Failed)) - assert next(db_status.status - for db_package, db_status in database.packages_get() - if db_package.base == package_ahriman.base) == BuildStatusEnum.Failed + database.package_update(package_ahriman) + package_ahriman.version = "1.0.0" + database.package_update(package_ahriman) + assert next(db_package.version + for db_package, _ in database.packages_get() + if db_package.base == package_ahriman.base) == package_ahriman.version -def test_remote_update_get(database: SQLite, package_ahriman: Package) -> None: +def test_status_update(database: SQLite, package_ahriman: Package) -> None: """ - must insert and retrieve package remote + must insert single package status """ - database.package_base_update(package_ahriman) - assert database.remotes_get()[package_ahriman.base] == package_ahriman.remote + status = BuildStatus() - -def test_remote_update_update(database: SQLite, package_ahriman: Package) -> None: - """ - must perform package remote update for existing package - """ - database.package_base_update(package_ahriman) - remote_source = RemoteSource(source=PackageSource.Repository) - package_ahriman.remote = remote_source - - database.package_base_update(package_ahriman) - assert database.remotes_get()[package_ahriman.base] == remote_source + database.package_update(package_ahriman, database._repository_id) + database.status_update(package_ahriman.base, status, database._repository_id) + assert database.packages_get(database._repository_id) == [(package_ahriman, status)] diff --git a/tests/ahriman/core/database/test_sqlite.py b/tests/ahriman/core/database/test_sqlite.py index 5afe8354..ef30badf 100644 --- a/tests/ahriman/core/database/test_sqlite.py +++ b/tests/ahriman/core/database/test_sqlite.py @@ -44,6 +44,7 @@ def test_package_clear(database: SQLite, mocker: MockerFixture) -> None: logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove") changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_remove") dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_remove") + tree_clear_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear") database.package_clear("package") build_queue_mock.assert_called_once_with("package") @@ -51,3 +52,4 @@ def test_package_clear(database: SQLite, mocker: MockerFixture) -> None: logs_mock.assert_called_once_with("package", None) changes_mock.assert_called_once_with("package") dependencies_mock.assert_called_once_with("package") + tree_clear_mock.assert_called_once_with("package") diff --git a/tests/ahriman/core/gitremote/test_remote_push.py b/tests/ahriman/core/gitremote/test_remote_push.py index a0ff93cf..3d8a4007 100644 --- a/tests/ahriman/core/gitremote/test_remote_push.py +++ b/tests/ahriman/core/gitremote/test_remote_push.py @@ -5,15 +5,15 @@ from pytest_mock import MockerFixture from unittest.mock import call as MockCall from ahriman.core.configuration import Configuration -from ahriman.core.database import SQLite from ahriman.core.exceptions import GitRemoteError from ahriman.core.gitremote.remote_push import RemotePush +from ahriman.core.status import Client from ahriman.models.package import Package from ahriman.models.pkgbuild_patch import PkgbuildPatch from ahriman.models.result import Result -def test_package_update(database: SQLite, configuration: Configuration, package_ahriman: Package, +def test_package_update(local_client: Client, configuration: Configuration, package_ahriman: Package, mocker: MockerFixture) -> None: """ must update single package @@ -27,9 +27,10 @@ def test_package_update(database: SQLite, configuration: Configuration, package_ rmtree_mock = mocker.patch("shutil.rmtree") unlink_mock = mocker.patch("pathlib.Path.unlink") fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") - patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_get", return_value=[patch1, patch2]) + patches_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get", + return_value=[patch1, patch2]) patches_write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write") - runner = RemotePush(database, configuration, "gitremote") + runner = RemotePush(local_client, configuration, "gitremote") assert runner.package_update(package_ahriman, local) == package_ahriman.base glob_mock.assert_called_once_with(".git*") @@ -39,28 +40,28 @@ def test_package_update(database: SQLite, configuration: Configuration, package_ ]) unlink_mock.assert_called_once_with() fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.remote) - patches_mock.assert_called_once_with(package_ahriman.base) + patches_mock.assert_called_once_with(package_ahriman.base, None) patches_write_mock.assert_has_calls([ MockCall(local / package_ahriman.base / f"ahriman-{package_ahriman.base}.patch"), MockCall(local / package_ahriman.base / f"ahriman-{patch2.key}.patch"), ]) -def test_packages_update(database: SQLite, configuration: Configuration, result: Result, package_ahriman: Package, +def test_packages_update(local_client: Client, configuration: Configuration, result: Result, package_ahriman: Package, mocker: MockerFixture) -> None: """ must generate packages update """ update_mock = mocker.patch("ahriman.core.gitremote.remote_push.RemotePush.package_update", return_value=[package_ahriman.base]) - runner = RemotePush(database, configuration, "gitremote") + runner = RemotePush(local_client, configuration, "gitremote") local = Path("local") assert list(runner.packages_update(result, local)) update_mock.assert_called_once_with(package_ahriman, local) -def test_run(database: SQLite, configuration: Configuration, result: Result, package_ahriman: Package, +def test_run(local_client: Client, configuration: Configuration, result: Result, package_ahriman: Package, mocker: MockerFixture) -> None: """ must push changes on result @@ -68,7 +69,7 @@ def test_run(database: SQLite, configuration: Configuration, result: Result, pac mocker.patch("ahriman.core.gitremote.remote_push.RemotePush.packages_update", return_value=[package_ahriman.base]) fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") push_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.push") - runner = RemotePush(database, configuration, "gitremote") + runner = RemotePush(local_client, configuration, "gitremote") runner.run(result) fetch_mock.assert_called_once_with(pytest.helpers.anyvar(int), runner.remote_source) @@ -77,12 +78,12 @@ def test_run(database: SQLite, configuration: Configuration, result: Result, pac ) -def test_run_failed(database: SQLite, configuration: Configuration, result: Result, mocker: MockerFixture) -> None: +def test_run_failed(local_client: Client, configuration: Configuration, result: Result, mocker: MockerFixture) -> None: """ must reraise exception on error occurred """ mocker.patch("ahriman.core.build_tools.sources.Sources.fetch", side_effect=Exception()) - runner = RemotePush(database, configuration, "gitremote") + runner = RemotePush(local_client, configuration, "gitremote") with pytest.raises(GitRemoteError): runner.run(result) diff --git a/tests/ahriman/core/gitremote/test_remote_push_trigger.py b/tests/ahriman/core/gitremote/test_remote_push_trigger.py index d0458397..7511a256 100644 --- a/tests/ahriman/core/gitremote/test_remote_push_trigger.py +++ b/tests/ahriman/core/gitremote/test_remote_push_trigger.py @@ -1,8 +1,8 @@ from pytest_mock import MockerFixture from ahriman.core.configuration import Configuration -from ahriman.core.database import SQLite from ahriman.core.gitremote import RemotePushTrigger +from ahriman.core.status import Client from ahriman.models.package import Package from ahriman.models.result import Result @@ -19,15 +19,15 @@ def test_configuration_sections(configuration: Configuration) -> None: def test_on_result(configuration: Configuration, result: Result, package_ahriman: Package, - database: SQLite, mocker: MockerFixture) -> None: + local_client: Client, mocker: MockerFixture) -> None: """ must push changes on result """ - database_mock = mocker.patch("ahriman.core._Context.get", return_value=database) + database_mock = mocker.patch("ahriman.core._Context.get", return_value=local_client) run_mock = mocker.patch("ahriman.core.gitremote.remote_push.RemotePush.run") _, repository_id = configuration.check_loaded() trigger = RemotePushTrigger(repository_id, configuration) trigger.on_result(result, [package_ahriman]) - database_mock.assert_called_once_with(SQLite) + database_mock.assert_called_once_with(Client) run_mock.assert_called_once_with(result) diff --git a/tests/ahriman/core/log/test_http_log_handler.py b/tests/ahriman/core/log/test_http_log_handler.py index 14df2bba..40395c30 100644 --- a/tests/ahriman/core/log/test_http_log_handler.py +++ b/tests/ahriman/core/log/test_http_log_handler.py @@ -18,7 +18,7 @@ def test_load(configuration: Configuration, mocker: MockerFixture) -> None: root.removeHandler(current_handler) add_mock = mocker.patch("logging.Logger.addHandler") - load_mock = mocker.patch("ahriman.core.status.client.Client.load") + load_mock = mocker.patch("ahriman.core.status.Client.load") _, repository_id = configuration.check_loaded() handler = HttpLogHandler.load(repository_id, configuration, report=False) @@ -43,13 +43,13 @@ def test_emit(configuration: Configuration, log_record: logging.LogRecord, packa must emit log record to reporter """ log_record_id = log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version) - log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs") + log_mock = mocker.patch("ahriman.core.status.Client.package_logs_add") _, repository_id = configuration.check_loaded() handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False) handler.emit(log_record) - log_mock.assert_called_once_with(log_record_id, log_record) + log_mock.assert_called_once_with(log_record_id, log_record.created, log_record.getMessage()) def test_emit_failed(configuration: Configuration, log_record: logging.LogRecord, package_ahriman: Package, @@ -58,7 +58,7 @@ def test_emit_failed(configuration: Configuration, log_record: logging.LogRecord must call handle error on exception """ log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version) - mocker.patch("ahriman.core.status.client.Client.package_logs", side_effect=Exception()) + mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception()) handle_error_mock = mocker.patch("logging.Handler.handleError") _, repository_id = configuration.check_loaded() handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False) @@ -73,7 +73,7 @@ def test_emit_suppress_failed(configuration: Configuration, log_record: logging. must not call handle error on exception if suppress flag is set """ log_record.package_id = LogRecordId(package_ahriman.base, package_ahriman.version) - mocker.patch("ahriman.core.status.client.Client.package_logs", side_effect=Exception()) + mocker.patch("ahriman.core.status.Client.package_logs_add", side_effect=Exception()) handle_error_mock = mocker.patch("logging.Handler.handleError") _, repository_id = configuration.check_loaded() handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=True) @@ -86,7 +86,7 @@ def test_emit_skip(configuration: Configuration, log_record: logging.LogRecord, """ must skip log record posting if no package base set """ - log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs") + log_mock = mocker.patch("ahriman.core.status.Client.package_logs_add") _, repository_id = configuration.check_loaded() handler = HttpLogHandler(repository_id, configuration, report=False, suppress_errors=False) diff --git a/tests/ahriman/core/log/test_lazy_logging.py b/tests/ahriman/core/log/test_lazy_logging.py index 81abe4f2..5a05878d 100644 --- a/tests/ahriman/core/log/test_lazy_logging.py +++ b/tests/ahriman/core/log/test_lazy_logging.py @@ -68,9 +68,9 @@ def test_in_package_context_failed(database: SQLite, package_ahriman: Package, m mocker.patch("ahriman.core.log.LazyLogging._package_logger_set") reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset") - with pytest.raises(Exception): + with pytest.raises(ValueError): with database.in_package_context(package_ahriman.base, ""): - raise Exception() + raise ValueError() reset_mock.assert_called_once_with() @@ -81,11 +81,3 @@ def test_logger(database: SQLite) -> None: """ assert database.logger assert database.logger.name == "ahriman.core.database.sqlite.SQLite" - - -def test_logger_attribute_error(database: SQLite) -> None: - """ - must raise AttributeError in case if no attribute found - """ - with pytest.raises(AttributeError): - database.loggerrrr diff --git a/tests/ahriman/core/repository/test_executor.py b/tests/ahriman/core/repository/test_executor.py index ec992c08..9025634a 100644 --- a/tests/ahriman/core/repository/test_executor.py +++ b/tests/ahriman/core/repository/test_executor.py @@ -17,22 +17,21 @@ def test_process_build(executor: Executor, package_ahriman: Package, passwd: Any """ must run build process """ - dependencies = Dependencies(package_ahriman.base) mocker.patch("ahriman.models.repository_paths.getpwuid", return_value=passwd) mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)]) init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init", return_value="sha") move_mock = mocker.patch("shutil.move") - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building") - commit_sha_mock = mocker.patch("ahriman.core.status.client.Client.package_changes_set") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_building") + commit_sha_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_update") depends_on_mock = mocker.patch("ahriman.models.package_archive.PackageArchive.depends_on", - return_value=dependencies) - dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_insert") + return_value=Dependencies()) + dependencies_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_update") executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=False) init_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), None) depends_on_mock.assert_called_once_with() - dependencies_mock.assert_called_once_with(dependencies) + dependencies_mock.assert_called_once_with(package_ahriman.base, Dependencies()) # must move files (once) move_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base) # must update status @@ -47,8 +46,6 @@ def test_process_build_bump_pkgrel(executor: Executor, package_ahriman: Package, mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)]) mocker.patch("shutil.move") - mocker.patch("ahriman.core.status.client.Client.set_building") - mocker.patch("ahriman.core.status.client.Client.package_changes_set") init_mock = mocker.patch("ahriman.core.build_tools.task.Task.init") executor.process_build([package_ahriman], Packagers("packager"), bump_pkgrel=True) @@ -66,7 +63,7 @@ def test_process_build_failure(executor: Executor, package_ahriman: Package, moc mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)]) mocker.patch("ahriman.core.build_tools.task.Task.init") mocker.patch("shutil.move", side_effect=Exception()) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed") executor.process_build([package_ahriman]) status_client_mock.assert_called_once_with(package_ahriman.base) @@ -77,18 +74,14 @@ def test_process_remove_base(executor: Executor, package_ahriman: Package, mocke must run remove process for whole base """ mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) - tree_clear_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear") repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove") - database_mock = mocker.patch("ahriman.core.database.SQLite.package_clear") - status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove") + status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") executor.process_remove([package_ahriman.base]) # must remove via alpm wrapper repo_remove_mock.assert_called_once_with( package_ahriman.base, package_ahriman.packages[package_ahriman.base].filepath) # must update status and remove package files - tree_clear_mock.assert_called_once_with(package_ahriman.base) - database_mock.assert_called_once_with(package_ahriman.base) status_client_mock.assert_called_once_with(package_ahriman.base) @@ -101,9 +94,7 @@ def test_process_remove_with_debug(executor: Executor, package_ahriman: Package, f"{package_ahriman.base}-debug": package_ahriman.packages[package_ahriman.base], } mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) - mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear") - mocker.patch("ahriman.core.database.SQLite.package_clear") - mocker.patch("ahriman.core.status.client.Client.package_remove") + mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove") executor.process_remove([package_ahriman.base]) @@ -121,7 +112,7 @@ def test_process_remove_base_multiple(executor: Executor, package_python_schedul """ mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule]) repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove") - status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove") + status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") executor.process_remove([package_python_schedule.base]) # must remove via alpm wrapper @@ -140,7 +131,7 @@ def test_process_remove_base_single(executor: Executor, package_python_schedule: """ mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule]) repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove") - status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove") + status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") executor.process_remove(["python2-schedule"]) # must remove via alpm wrapper @@ -155,7 +146,7 @@ def test_process_remove_failed(executor: Executor, package_ahriman: Package, moc must suppress tree clear errors during package base removal """ mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) - mocker.patch("ahriman.models.repository_paths.RepositoryPaths.tree_clear", side_effect=Exception()) + mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove", side_effect=Exception()) executor.process_remove([package_ahriman.base]) @@ -186,7 +177,7 @@ def test_process_remove_unknown(executor: Executor, package_ahriman: Package, mo """ mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[]) repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove") - status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove") + status_client_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") executor.process_remove([package_ahriman.base]) repo_remove_mock.assert_not_called() @@ -202,7 +193,7 @@ def test_process_update(executor: Executor, package_ahriman: Package, user: User move_mock = mocker.patch("shutil.move") repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add") sign_package_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process_sign_package", side_effect=lambda fn, _: [fn]) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_success") remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove") packager_mock = mocker.patch("ahriman.core.repository.executor.Executor.packager", return_value=user) filepath = next(package.filepath for package in package_ahriman.packages.values()) @@ -234,7 +225,7 @@ def test_process_update_group(executor: Executor, package_python_schedule: Packa mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_python_schedule]) mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule]) repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add") - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_success") remove_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove") executor.process_update([package.filepath for package in package_python_schedule.packages.values()]) @@ -284,7 +275,7 @@ def test_process_update_failed(executor: Executor, package_ahriman: Package, moc mocker.patch("shutil.move", side_effect=Exception()) mocker.patch("ahriman.core.repository.executor.Executor.load_archives", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman]) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed") executor.process_update([package.filepath for package in package_ahriman.packages.values()]) status_client_mock.assert_called_once_with(package_ahriman.base) diff --git a/tests/ahriman/core/repository/test_package_info.py b/tests/ahriman/core/repository/test_package_info.py index a6a1656b..e228919e 100644 --- a/tests/ahriman/core/repository/test_package_info.py +++ b/tests/ahriman/core/repository/test_package_info.py @@ -21,9 +21,9 @@ def test_load_archives(package_ahriman: Package, package_python_schedule: Packag for package, props in package_python_schedule.packages.items() ] + [package_ahriman] mocker.patch("ahriman.models.package.Package.from_archive", side_effect=single_packages) - mocker.patch("ahriman.core.database.SQLite.remotes_get", return_value={ - package_ahriman.base: package_ahriman.base - }) + mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[ + (package_ahriman, None), + ]) packages = package_info.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")]) assert len(packages) == 2 diff --git a/tests/ahriman/core/repository/test_repository.py b/tests/ahriman/core/repository/test_repository.py index 0983c1fd..b687b20c 100644 --- a/tests/ahriman/core/repository/test_repository.py +++ b/tests/ahriman/core/repository/test_repository.py @@ -6,6 +6,7 @@ from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.repository import Repository from ahriman.core.sign.gpg import GPG +from ahriman.core.status import Client def test_load(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> None: @@ -32,5 +33,6 @@ def test_set_context(configuration: Configuration, database: SQLite, mocker: Moc MockCall(Configuration, instance.configuration), MockCall(Pacman, instance.pacman), MockCall(GPG, instance.sign), + MockCall(Client, instance.reporter), MockCall(Repository, instance), ]) diff --git a/tests/ahriman/core/repository/test_update_handler.py b/tests/ahriman/core/repository/test_update_handler.py index 567fb2e3..037d6c3f 100644 --- a/tests/ahriman/core/repository/test_update_handler.py +++ b/tests/ahriman/core/repository/test_update_handler.py @@ -20,7 +20,7 @@ def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package, packages_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.models.package.Package.from_aur", return_value=package_ahriman) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending") package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) assert update_handler.updates_aur([], vcs=True) == [package_ahriman] @@ -41,7 +41,7 @@ def test_updates_aur_official(update_handler: UpdateHandler, package_ahriman: Pa mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) mocker.patch("ahriman.models.package.Package.from_official", return_value=package_ahriman) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending") assert update_handler.updates_aur([], vcs=True) == [package_ahriman] status_client_mock.assert_called_once_with(package_ahriman.base) @@ -54,7 +54,7 @@ def test_updates_aur_failed(update_handler: UpdateHandler, package_ahriman: Pack """ mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.models.package.Package.from_aur", side_effect=Exception()) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_failed") update_handler.updates_aur([], vcs=True) status_client_mock.assert_called_once_with(package_ahriman.base) @@ -141,7 +141,7 @@ def test_updates_aur_load_by_package_failed(update_handler: UpdateHandler, packa """ mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) mocker.patch("ahriman.models.package.Package.from_aur", side_effect=UnknownPackageError(package_ahriman.base)) - mocker.patch("ahriman.core.status.client.Client.set_failed") + mocker.patch("ahriman.core.status.Client.set_failed") update_handler.updates_aur([], vcs=True) @@ -153,13 +153,14 @@ def test_updates_dependencies(update_handler: UpdateHandler, package_ahriman: Pa """ packages_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman, package_python_schedule]) - dependencies = [ - Dependencies(package_ahriman.base, {Path("usr/lib/python3.11/site-packages"): ["python"]}), - Dependencies(package_python_schedule.base, {Path("usr/lib/python3.12/site-packages"): ["python"]}), - ] - mocker.patch("ahriman.core.database.SQLite.dependencies_get", return_value=dependencies) + dependencies = { + package_ahriman.base: Dependencies({"usr/lib/python3.11/site-packages": ["python"]}), + package_python_schedule.base: Dependencies({"usr/lib/python3.12/site-packages": ["python"]}), + } + mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get", + side_effect=lambda base: dependencies[base]) mocker.patch("ahriman.core.alpm.pacman.Pacman.files", - return_value={"python": {Path("usr/lib/python3.12/site-packages")}}) + return_value={"python": {"usr/lib/python3.12/site-packages"}}) assert update_handler.updates_dependencies(["filter"]) == [package_ahriman] packages_mock.assert_called_once_with(["filter"]) @@ -171,9 +172,10 @@ def test_updates_dependencies_skip_unknown(update_handler: UpdateHandler, packag must skip unknown package dependencies """ mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) - mocker.patch("ahriman.core.database.SQLite.dependencies_get", return_value=[]) + mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get", + return_value=Dependencies()) mocker.patch("ahriman.core.alpm.pacman.Pacman.files", - return_value={"python": {Path("usr/lib/python3.12/site-packages")}}) + return_value={"python": {"usr/lib/python3.12/site-packages"}}) assert update_handler.updates_dependencies(["filter"]) == [] @@ -184,13 +186,11 @@ def test_updates_dependencies_partial(update_handler: UpdateHandler, package_ahr must skip broken dependencies update if at least one package provides file """ mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) - dependencies = [ - Dependencies(package_ahriman.base, {Path("usr"): ["filesystem", "python"]}), - ] - mocker.patch("ahriman.core.database.SQLite.dependencies_get", return_value=dependencies) + dependencies = Dependencies({"usr": ["filesystem", "python"]}) + mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get", return_value=dependencies) mocker.patch("ahriman.core.alpm.pacman.Pacman.files", return_value={ - "filesystem": {Path("usr")}, - "python": {Path("usr")}, + "filesystem": {"usr"}, + "python": {"usr"}, }) assert update_handler.updates_dependencies(["filter"]) == [] @@ -204,7 +204,7 @@ def test_updates_local(update_handler: UpdateHandler, package_ahriman: Package, mocker.patch("pathlib.Path.iterdir", return_value=[Path(package_ahriman.base)]) fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") package_load_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package_ahriman) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending") package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True) assert update_handler.updates_local(vcs=True) == [package_ahriman] @@ -280,7 +280,7 @@ def test_updates_manual_status_known(update_handler: UpdateHandler, package_ahri """ mocker.patch("ahriman.core.database.SQLite.build_queue_get", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman]) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_pending") update_handler.updates_manual() status_client_mock.assert_called_once_with(package_ahriman.base) @@ -293,7 +293,7 @@ def test_updates_manual_status_unknown(update_handler: UpdateHandler, package_ah """ mocker.patch("ahriman.core.database.SQLite.build_queue_get", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[]) - status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown") + status_client_mock = mocker.patch("ahriman.core.status.Client.set_unknown") update_handler.updates_manual() status_client_mock.assert_called_once_with(package_ahriman) diff --git a/tests/ahriman/core/status/conftest.py b/tests/ahriman/core/status/conftest.py index 6f52d866..f51af705 100644 --- a/tests/ahriman/core/status/conftest.py +++ b/tests/ahriman/core/status/conftest.py @@ -1,7 +1,7 @@ import pytest from ahriman.core.configuration import Configuration -from ahriman.core.status.client import Client +from ahriman.core.status import Client from ahriman.core.status.web_client import WebClient diff --git a/tests/ahriman/core/status/test_client.py b/tests/ahriman/core/status/test_client.py index 7fcac79b..51b151f3 100644 --- a/tests/ahriman/core/status/test_client.py +++ b/tests/ahriman/core/status/test_client.py @@ -1,26 +1,39 @@ import logging +import pytest from pytest_mock import MockerFixture from ahriman.core.configuration import Configuration -from ahriman.core.status.client import Client +from ahriman.core.database import SQLite +from ahriman.core.status import Client +from ahriman.core.status.local_client import LocalClient from ahriman.core.status.web_client import WebClient from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.changes import Changes +from ahriman.models.dependencies import Dependencies from ahriman.models.internal_status import InternalStatus from ahriman.models.log_record_id import LogRecordId from ahriman.models.package import Package +from ahriman.models.pkgbuild_patch import PkgbuildPatch def test_load_dummy_client(configuration: Configuration) -> None: + """ + must load dummy client if no settings and database set + """ + _, repository_id = configuration.check_loaded() + assert isinstance(Client.load(repository_id, configuration, report=True), Client) + + +def test_load_local_client(configuration: Configuration, database: SQLite) -> None: """ must load dummy client if no settings set """ _, repository_id = configuration.check_loaded() - assert not isinstance(Client.load(repository_id, configuration, report=True), WebClient) + assert isinstance(Client.load(repository_id, configuration, database, report=True), LocalClient) -def test_load_dummy_client_disabled(configuration: Configuration) -> None: +def test_load_local_client_disabled(configuration: Configuration, database: SQLite) -> None: """ must load dummy client if report is set to False """ @@ -28,10 +41,10 @@ def test_load_dummy_client_disabled(configuration: Configuration) -> None: configuration.set_option("web", "port", "8080") _, repository_id = configuration.check_loaded() - assert not isinstance(Client.load(repository_id, configuration, report=False), WebClient) + assert isinstance(Client.load(repository_id, configuration, database, report=False), LocalClient) -def test_load_dummy_client_disabled_in_configuration(configuration: Configuration) -> None: +def test_load_local_client_disabled_in_configuration(configuration: Configuration, database: SQLite) -> None: """ must load dummy client if disabled in configuration """ @@ -40,19 +53,19 @@ def test_load_dummy_client_disabled_in_configuration(configuration: Configuratio configuration.set_option("status", "enabled", "no") _, repository_id = configuration.check_loaded() - assert not isinstance(Client.load(repository_id, configuration, report=True), WebClient) + assert isinstance(Client.load(repository_id, configuration, database, report=True), LocalClient) -def test_load_full_client_from_address(configuration: Configuration) -> None: +def test_load_web_client_from_address(configuration: Configuration, database: SQLite) -> None: """ must load full client by using address """ configuration.set_option("status", "address", "http://localhost:8080") _, repository_id = configuration.check_loaded() - assert isinstance(Client.load(repository_id, configuration, report=True), WebClient) + assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient) -def test_load_full_client_from_legacy_host(configuration: Configuration) -> None: +def test_load_web_client_from_legacy_host(configuration: Configuration, database: SQLite) -> None: """ must load full client if host and port settings set """ @@ -60,82 +73,144 @@ def test_load_full_client_from_legacy_host(configuration: Configuration) -> None configuration.set_option("web", "port", "8080") _, repository_id = configuration.check_loaded() - assert isinstance(Client.load(repository_id, configuration, report=True), WebClient) + assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient) -def test_load_full_client_from_legacy_address(configuration: Configuration) -> None: +def test_load_web_client_from_legacy_address(configuration: Configuration, database: SQLite) -> None: """ must load full client by using legacy address """ configuration.set_option("web", "address", "http://localhost:8080") _, repository_id = configuration.check_loaded() - assert isinstance(Client.load(repository_id, configuration, report=True), WebClient) + assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient) -def test_load_full_client_from_legacy_unix_socket(configuration: Configuration) -> None: +def test_load_web_client_from_legacy_unix_socket(configuration: Configuration, database: SQLite) -> None: """ must load full client by using unix socket """ configuration.set_option("web", "unix_socket", "/var/lib/ahriman/ahriman-web.sock") _, repository_id = configuration.check_loaded() - assert isinstance(Client.load(repository_id, configuration, report=True), WebClient) - - -def test_package_add(client: Client, package_ahriman: Package) -> None: - """ - must process package addition without errors - """ - client.package_add(package_ahriman, BuildStatusEnum.Unknown) + assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient) def test_package_changes_get(client: Client, package_ahriman: Package) -> None: """ - must return null changes + must raise not implemented on package changes request """ - assert client.package_changes_get(package_ahriman.base) == Changes() + with pytest.raises(NotImplementedError): + client.package_changes_get(package_ahriman.base) -def test_package_changes_set(client: Client, package_ahriman: Package) -> None: +def test_package_changes_update(client: Client, package_ahriman: Package) -> None: """ - must process changes update without errors + must raise not implemented on changes update """ - client.package_changes_set(package_ahriman.base, Changes()) + with pytest.raises(NotImplementedError): + client.package_changes_update(package_ahriman.base, Changes()) + + +def test_package_dependencies_get(client: Client, package_ahriman: Package) -> None: + """ + must raise not implemented on package dependencies request + """ + with pytest.raises(NotImplementedError): + client.package_dependencies_get(package_ahriman.base) + + +def test_package_dependencies_update(client: Client, package_ahriman: Package) -> None: + """ + must raise not implemented on dependencies update + """ + with pytest.raises(NotImplementedError): + client.package_dependencies_update(package_ahriman.base, Dependencies()) def test_package_get(client: Client, package_ahriman: Package) -> None: """ - must return empty package list + must raise not implemented on packages get """ - assert client.package_get(package_ahriman.base) == [] - assert client.package_get(None) == [] + with pytest.raises(NotImplementedError): + assert client.package_get(package_ahriman.base) -def test_package_logs(client: Client, package_ahriman: Package, log_record: logging.LogRecord) -> None: +def test_package_logs_add(client: Client, package_ahriman: Package, log_record: logging.LogRecord) -> None: """ - must process log record without errors + must process log record addition without exception """ - client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record) + log_record_id = LogRecordId(package_ahriman.base, package_ahriman.version) + client.package_logs_add(log_record_id, log_record.created, log_record.getMessage()) + + +def test_package_logs_get(client: Client, package_ahriman: Package) -> None: + """ + must raise not implemented on logs retrieval + """ + with pytest.raises(NotImplementedError): + client.package_logs_get(package_ahriman.base) + + +def test_package_logs_remove(client: Client, package_ahriman: Package) -> None: + """ + must raise not implemented on logs removal + """ + with pytest.raises(NotImplementedError): + client.package_logs_remove(package_ahriman.base, package_ahriman.version) + + +def test_package_patches_get(client: Client, package_ahriman: Package) -> None: + """ + must raise not implemented on patches retrieval + """ + with pytest.raises(NotImplementedError): + client.package_patches_get(package_ahriman.base, None) + + +def test_package_patches_remove(client: Client, package_ahriman: Package) -> None: + """ + must raise not implemented on patches removal + """ + with pytest.raises(NotImplementedError): + client.package_patches_remove(package_ahriman.base, None) + + +def test_package_patches_update(client: Client, package_ahriman: Package) -> None: + """ + must raise not implemented on patches addition + """ + with pytest.raises(NotImplementedError): + client.package_patches_update(package_ahriman.base, PkgbuildPatch(None, "")) def test_package_remove(client: Client, package_ahriman: Package) -> None: """ - must process remove without errors + must raise not implemented on package removal """ - client.package_remove(package_ahriman.base) + with pytest.raises(NotImplementedError): + client.package_remove(package_ahriman.base) + + +def test_package_status_update(client: Client, package_ahriman: Package) -> None: + """ + must raise not implemented on package update + """ + with pytest.raises(NotImplementedError): + client.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown) def test_package_update(client: Client, package_ahriman: Package) -> None: """ - must update package status without errors + must raise not implemented on package addition """ - client.package_update(package_ahriman.base, BuildStatusEnum.Unknown) + with pytest.raises(NotImplementedError): + client.package_update(package_ahriman, BuildStatusEnum.Unknown) def test_set_building(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None: """ must set building status to the package """ - update_mock = mocker.patch("ahriman.core.status.client.Client.package_update") + update_mock = mocker.patch("ahriman.core.status.Client.package_status_update") client.set_building(package_ahriman.base) update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Building) @@ -145,7 +220,7 @@ def test_set_failed(client: Client, package_ahriman: Package, mocker: MockerFixt """ must set failed status to the package """ - update_mock = mocker.patch("ahriman.core.status.client.Client.package_update") + update_mock = mocker.patch("ahriman.core.status.Client.package_status_update") client.set_failed(package_ahriman.base) update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Failed) @@ -155,7 +230,7 @@ def test_set_pending(client: Client, package_ahriman: Package, mocker: MockerFix """ must set building status to the package """ - update_mock = mocker.patch("ahriman.core.status.client.Client.package_update") + update_mock = mocker.patch("ahriman.core.status.Client.package_status_update") client.set_pending(package_ahriman.base) update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Pending) @@ -165,20 +240,32 @@ def test_set_success(client: Client, package_ahriman: Package, mocker: MockerFix """ must set success status to the package """ - add_mock = mocker.patch("ahriman.core.status.client.Client.package_add") + update_mock = mocker.patch("ahriman.core.status.Client.package_update") client.set_success(package_ahriman) - add_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Success) + update_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Success) def test_set_unknown(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None: """ must add new package with unknown status """ - add_mock = mocker.patch("ahriman.core.status.client.Client.package_add") + mocker.patch("ahriman.core.status.Client.package_get", return_value=[]) + update_mock = mocker.patch("ahriman.core.status.Client.package_update") client.set_unknown(package_ahriman) - add_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Unknown) + update_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Unknown) + + +def test_set_unknown_skip(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must skip unknown status update in case if pacakge is already known + """ + mocker.patch("ahriman.core.status.Client.package_get", return_value=[(package_ahriman, None)]) + update_mock = mocker.patch("ahriman.core.status.Client.package_update") + client.set_unknown(package_ahriman) + + update_mock.assert_not_called() def test_status_get(client: Client) -> None: diff --git a/tests/ahriman/core/status/test_local_client.py b/tests/ahriman/core/status/test_local_client.py new file mode 100644 index 00000000..8e3db4f4 --- /dev/null +++ b/tests/ahriman/core/status/test_local_client.py @@ -0,0 +1,182 @@ +import logging +import pytest + +from pytest_mock import MockerFixture + +from ahriman.core.status.local_client import LocalClient +from ahriman.models.build_status import BuildStatus, BuildStatusEnum +from ahriman.models.changes import Changes +from ahriman.models.dependencies import Dependencies +from ahriman.models.log_record_id import LogRecordId +from ahriman.models.package import Package +from ahriman.models.pkgbuild_patch import PkgbuildPatch + + +def test_package_changes_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must retrieve package changes + """ + changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_get") + local_client.package_changes_get(package_ahriman.base) + changes_mock.assert_called_once_with(package_ahriman.base, local_client.repository_id) + + +def test_package_changes_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must update package changes + """ + changes_mock = mocker.patch("ahriman.core.database.SQLite.changes_insert") + changes = Changes() + + local_client.package_changes_update(package_ahriman.base, changes) + changes_mock.assert_called_once_with(package_ahriman.base, changes, local_client.repository_id) + + +def test_package_dependencies_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must retrieve package dependencies + """ + dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_get") + local_client.package_dependencies_get(package_ahriman.base) + dependencies_mock.assert_called_once_with(package_ahriman.base, local_client.repository_id) + + +def test_package_dependencies_update( + local_client: LocalClient, + package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must update package dependencies + """ + dependencies_mock = mocker.patch("ahriman.core.database.SQLite.dependencies_insert") + local_client.package_dependencies_update(package_ahriman.base, Dependencies()) + dependencies_mock.assert_called_once_with(package_ahriman.base, Dependencies(), local_client.repository_id) + + +def test_package_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must retrieve packages + """ + result = [(package_ahriman, BuildStatus())] + package_mock = mocker.patch("ahriman.core.database.SQLite.packages_get", return_value=result) + assert local_client.package_get(None) == result + package_mock.assert_called_once_with(local_client.repository_id) + + +def test_package_get_package(local_client: LocalClient, package_ahriman: Package, package_python_schedule: Package, + mocker: MockerFixture) -> None: + """ + must retrieve specific package + """ + result = [(package_ahriman, BuildStatus()), (package_python_schedule, BuildStatus())] + package_mock = mocker.patch("ahriman.core.database.SQLite.packages_get", return_value=result) + assert local_client.package_get(package_ahriman.base) == [result[0]] + package_mock.assert_called_once_with(local_client.repository_id) + + +def test_package_logs_add(local_client: LocalClient, package_ahriman: Package, log_record: logging.LogRecord, + mocker: MockerFixture) -> None: + """ + must add package logs + """ + logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_insert") + log_record_id = LogRecordId(package_ahriman.base, package_ahriman.version) + + local_client.package_logs_add(log_record_id, log_record.created, log_record.getMessage()) + logs_mock.assert_called_once_with(log_record_id, log_record.created, log_record.getMessage(), + local_client.repository_id) + + +def test_package_logs_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must retrieve package logs + """ + logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get") + local_client.package_logs_get(package_ahriman.base, 1, 2) + logs_mock.assert_called_once_with(package_ahriman.base, 1, 2, local_client.repository_id) + + +def test_package_logs_remove(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must remove package logs + """ + logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove") + local_client.package_logs_remove(package_ahriman.base, package_ahriman.version) + logs_mock.assert_called_once_with(package_ahriman.base, package_ahriman.version, local_client.repository_id) + + +def test_package_patches_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must retrieve package patches + """ + patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_list") + local_client.package_patches_get(package_ahriman.base, None) + patches_mock.assert_called_once_with(package_ahriman.base, None) + + +def test_package_patches_get_key(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must retrieve package patches for specific patch name + """ + patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_list") + local_client.package_patches_get(package_ahriman.base, "key") + patches_mock.assert_called_once_with(package_ahriman.base, ["key"]) + + +def test_package_patches_remove(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must remove package patches + """ + patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove") + local_client.package_patches_remove(package_ahriman.base, None) + patches_mock.assert_called_once_with(package_ahriman.base, None) + + +def test_package_patches_remove_key(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must remove package specific package patch + """ + patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove") + local_client.package_patches_remove(package_ahriman.base, "key") + patches_mock.assert_called_once_with(package_ahriman.base, ["key"]) + + +def test_package_patches_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must add package patches + """ + patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_insert") + patch = PkgbuildPatch("key", "value") + + local_client.package_patches_update(package_ahriman.base, patch) + patches_mock.assert_called_once_with(package_ahriman.base, [patch]) + + +def test_package_remove(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must remove package + """ + package_mock = mocker.patch("ahriman.core.database.SQLite.package_clear") + local_client.package_remove(package_ahriman.base) + package_mock.assert_called_once_with(package_ahriman.base) + + +def test_package_status_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must update package status + """ + status_mock = mocker.patch("ahriman.core.database.SQLite.status_update") + local_client.package_status_update(package_ahriman.base, BuildStatusEnum.Success) + status_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int), local_client.repository_id) + + +def test_package_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must process package addition + """ + package_mock = mocker.patch("ahriman.core.database.SQLite.package_update") + status_mock = mocker.patch("ahriman.core.database.SQLite.status_update") + + local_client.package_update(package_ahriman, BuildStatusEnum.Success) + package_mock.assert_called_once_with(package_ahriman, local_client.repository_id) + status_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int), local_client.repository_id) diff --git a/tests/ahriman/core/status/test_watcher.py b/tests/ahriman/core/status/test_watcher.py index 9962e1dc..34e9c182 100644 --- a/tests/ahriman/core/status/test_watcher.py +++ b/tests/ahriman/core/status/test_watcher.py @@ -1,26 +1,33 @@ import pytest from pytest_mock import MockerFixture -from unittest.mock import call as MockCall from ahriman.core.exceptions import UnknownPackageError from ahriman.core.status.watcher import Watcher from ahriman.models.build_status import BuildStatus, BuildStatusEnum -from ahriman.models.changes import Changes from ahriman.models.log_record_id import LogRecordId from ahriman.models.package import Package -from ahriman.models.pkgbuild_patch import PkgbuildPatch + + +def test_packages(watcher: Watcher, package_ahriman: Package) -> None: + """ + must return list of available packages + """ + assert not watcher.packages + + watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())} + assert watcher.packages def test_load(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: """ must correctly load packages """ - cache_mock = mocker.patch("ahriman.core.database.SQLite.packages_get", + cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[(package_ahriman, BuildStatus())]) watcher.load() - cache_mock.assert_called_once_with(watcher.repository_id) + cache_mock.assert_called_once_with(None) package, status = watcher._known[package_ahriman.base] assert package == package_ahriman assert status.status == BuildStatusEnum.Unknown @@ -31,7 +38,7 @@ def test_load_known(watcher: Watcher, package_ahriman: Package, mocker: MockerFi must correctly load packages with known statuses """ status = BuildStatus(BuildStatusEnum.Success) - mocker.patch("ahriman.core.database.SQLite.packages_get", return_value=[(package_ahriman, status)]) + mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[(package_ahriman, status)]) watcher._known = {package_ahriman.base: (package_ahriman, status)} watcher.load() @@ -39,85 +46,6 @@ def test_load_known(watcher: Watcher, package_ahriman: Package, mocker: MockerFi assert status.status == BuildStatusEnum.Success -def test_logs_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must return package logs - """ - watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())} - logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_get") - - watcher.logs_get(package_ahriman.base, 1, 2) - logs_mock.assert_called_once_with(package_ahriman.base, 1, 2, watcher.repository_id) - - -def test_logs_get_failed(watcher: Watcher, package_ahriman: Package) -> None: - """ - must raise UnknownPackageError on logs in case of unknown package - """ - with pytest.raises(UnknownPackageError): - watcher.logs_get(package_ahriman.base) - - -def test_logs_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must remove package logs - """ - logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove") - watcher.logs_remove(package_ahriman.base, "42") - logs_mock.assert_called_once_with(package_ahriman.base, "42", watcher.repository_id) - - -def test_logs_update_new(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must create package logs record for new package - """ - delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.logs_remove") - insert_mock = mocker.patch("ahriman.core.database.SQLite.logs_insert") - - log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version) - assert watcher._last_log_record_id != log_record_id - - watcher.logs_update(log_record_id, 42.01, "log record") - delete_mock.assert_called_once_with(package_ahriman.base, log_record_id.version) - insert_mock.assert_called_once_with(log_record_id, 42.01, "log record", watcher.repository_id) - - assert watcher._last_log_record_id == log_record_id - - -def test_logs_update_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must create package logs record for current package - """ - delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.logs_remove") - insert_mock = mocker.patch("ahriman.core.database.SQLite.logs_insert") - - log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version) - watcher._last_log_record_id = log_record_id - - watcher.logs_update(log_record_id, 42.01, "log record") - delete_mock.assert_not_called() - insert_mock.assert_called_once_with(log_record_id, 42.01, "log record", watcher.repository_id) - - -def test_package_changes_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must return package changes - """ - get_mock = mocker.patch("ahriman.core.database.SQLite.changes_get", return_value=Changes("sha")) - watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())} - - assert watcher.package_changes_get(package_ahriman.base) == Changes("sha") - get_mock.assert_called_once_with(package_ahriman.base, watcher.repository_id) - - -def test_package_changes_get_failed(watcher: Watcher, package_ahriman: Package) -> None: - """ - must raise UnknownPackageError on changes in case of unknown package - """ - with pytest.raises(UnknownPackageError): - watcher.package_changes_get(package_ahriman.base) - - def test_package_get(watcher: Watcher, package_ahriman: Package) -> None: """ must return package status @@ -136,17 +64,49 @@ def test_package_get_failed(watcher: Watcher, package_ahriman: Package) -> None: watcher.package_get(package_ahriman.base) +def test_package_logs_add_new(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must create package logs record for new package + """ + delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True) + insert_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_logs_add") + + log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version) + assert watcher._last_log_record_id != log_record_id + + watcher.package_logs_add(log_record_id, 42.01, "log record") + delete_mock.assert_called_once_with(package_ahriman.base, log_record_id.version) + insert_mock.assert_called_once_with(log_record_id, 42.01, "log record") + + assert watcher._last_log_record_id == log_record_id + + +def test_package_logs_add_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must create package logs record for current package + """ + delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True) + insert_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_logs_add") + + log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version) + watcher._last_log_record_id = log_record_id + + watcher.package_logs_add(log_record_id, 42.01, "log record") + delete_mock.assert_not_called() + insert_mock.assert_called_once_with(log_record_id, 42.01, "log record") + + def test_package_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: """ must remove package base """ - cache_mock = mocker.patch("ahriman.core.database.SQLite.package_remove") - logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.logs_remove") + cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") + logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True) watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())} watcher.package_remove(package_ahriman.base) assert not watcher._known - cache_mock.assert_called_once_with(package_ahriman.base, watcher.repository_id) + cache_mock.assert_called_once_with(package_ahriman.base) logs_mock.assert_called_once_with(package_ahriman.base, None) @@ -154,82 +114,42 @@ def test_package_remove_unknown(watcher: Watcher, package_ahriman: Package, mock """ must not fail on unknown base removal """ - cache_mock = mocker.patch("ahriman.core.database.SQLite.package_remove") - + cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") watcher.package_remove(package_ahriman.base) - cache_mock.assert_called_once_with(package_ahriman.base, watcher.repository_id) + cache_mock.assert_called_once_with(package_ahriman.base) -def test_package_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must update package status - """ - cache_mock = mocker.patch("ahriman.core.database.SQLite.package_update") - - watcher.package_update(package_ahriman.base, BuildStatusEnum.Unknown, package_ahriman) - cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int), watcher.repository_id) - package, status = watcher._known[package_ahriman.base] - assert package == package_ahriman - assert status.status == BuildStatusEnum.Unknown - - -def test_package_update_ping(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_status_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: """ must update package status only for known package """ - cache_mock = mocker.patch("ahriman.core.database.SQLite.package_update") + cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_status_update") watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())} - watcher.package_update(package_ahriman.base, BuildStatusEnum.Success, None) - cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int), watcher.repository_id) + watcher.package_status_update(package_ahriman.base, BuildStatusEnum.Success) + cache_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int)) package, status = watcher._known[package_ahriman.base] assert package == package_ahriman assert status.status == BuildStatusEnum.Success -def test_package_update_unknown(watcher: Watcher, package_ahriman: Package) -> None: +def test_package_status_update_unknown(watcher: Watcher, package_ahriman: Package) -> None: """ must fail on unknown package status update only """ with pytest.raises(UnknownPackageError): - watcher.package_update(package_ahriman.base, BuildStatusEnum.Unknown, None) + watcher.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown) -def test_patches_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: """ - must return patches for the package + must add package to cache """ - watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())} - patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_list") + cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_update") - watcher.patches_get(package_ahriman.base, None) - watcher.patches_get(package_ahriman.base, "var") - patches_mock.assert_has_calls([ - MockCall(package_ahriman.base, None), - MockCall().get(package_ahriman.base, []), - MockCall(package_ahriman.base, ["var"]), - MockCall().get(package_ahriman.base, []), - ]) - - -def test_patches_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must remove patches for the package - """ - patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove") - watcher.patches_remove(package_ahriman.base, "var") - patches_mock.assert_called_once_with(package_ahriman.base, ["var"]) - - -def test_patches_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must update patches for the package - """ - patch = PkgbuildPatch("key", "value") - patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_insert") - - watcher.patches_update(package_ahriman.base, patch) - patches_mock.assert_called_once_with(package_ahriman.base, [patch]) + watcher.package_update(package_ahriman, BuildStatusEnum.Unknown) + assert watcher.packages + cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int)) def test_status_update(watcher: Watcher) -> None: @@ -238,3 +158,41 @@ def test_status_update(watcher: Watcher) -> None: """ watcher.status_update(BuildStatusEnum.Success) assert watcher.status.status == BuildStatusEnum.Success + + +def test_call(watcher: Watcher, package_ahriman: Package) -> None: + """ + must return self instance if package exists + """ + watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())} + assert watcher(package_ahriman.base) + + +def test_call_skip(watcher: Watcher) -> None: + """ + must return self instance if no package base set + """ + assert watcher(None) + + +def test_call_failed(watcher: Watcher, package_ahriman: Package) -> None: + """ + must raise UnknownPackage + """ + with pytest.raises(UnknownPackageError): + assert watcher(package_ahriman.base) + + +def test_getattr(watcher: Watcher) -> None: + """ + must return client method call + """ + assert watcher.package_logs_remove + + +def test_getattr_unknown_method(watcher: Watcher) -> None: + """ + must raise AttributeError in case if no reporter attribute found + """ + with pytest.raises(AttributeError): + assert watcher.random_method diff --git a/tests/ahriman/core/status/test_web_client.py b/tests/ahriman/core/status/test_web_client.py index c174d396..3b75fb95 100644 --- a/tests/ahriman/core/status/test_web_client.py +++ b/tests/ahriman/core/status/test_web_client.py @@ -9,9 +9,11 @@ from ahriman.core.configuration import Configuration from ahriman.core.status.web_client import WebClient from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.changes import Changes +from ahriman.models.dependencies import Dependencies from ahriman.models.internal_status import InternalStatus from ahriman.models.log_record_id import LogRecordId from ahriman.models.package import Package +from ahriman.models.pkgbuild_patch import PkgbuildPatch def test_parse_address(configuration: Configuration) -> None: @@ -41,6 +43,31 @@ def test_changes_url(web_client: WebClient, package_ahriman: Package) -> None: assert web_client._changes_url("some/package%name").endswith("/api/v1/packages/some%2Fpackage%25name/changes") +def test_dependencies_url(web_client: WebClient, package_ahriman: Package) -> None: + """ + must generate changes url correctly + """ + assert web_client._dependencies_url(package_ahriman.base).startswith(web_client.address) + assert web_client._dependencies_url(package_ahriman.base).endswith( + f"/api/v1/packages/{package_ahriman.base}/dependencies") + assert web_client._dependencies_url("some/package%name").endswith( + "/api/v1/packages/some%2Fpackage%25name/dependencies") + + +def test__patches_url(web_client: WebClient, package_ahriman: Package) -> None: + """ + must generate changes url correctly + """ + assert web_client._patches_url(package_ahriman.base).startswith(web_client.address) + assert web_client._patches_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/patches") + assert web_client._patches_url("some/package%name").endswith("/api/v1/packages/some%2Fpackage%25name/patches") + + assert web_client._patches_url(package_ahriman.base, "var").endswith( + f"/api/v1/packages/{package_ahriman.base}/patches/var") + assert web_client._patches_url(package_ahriman.base, "some/variable%name").endswith( + f"/api/v1/packages/{package_ahriman.base}/patches/some%2Fvariable%25name") + + def test_logs_url(web_client: WebClient, package_ahriman: Package) -> None: """ must generate logs url correctly @@ -70,59 +97,6 @@ def test_status_url(web_client: WebClient) -> None: assert web_client._status_url().endswith("/api/v1/status") -def test_package_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must process package addition - """ - requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request") - payload = pytest.helpers.get_package_status(package_ahriman) - - web_client.package_add(package_ahriman, BuildStatusEnum.Unknown) - requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), - params=web_client.repository_id.query(), json=payload) - - -def test_package_add_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must suppress any exception happened during addition - """ - mocker.patch("requests.Session.request", side_effect=Exception()) - web_client.package_add(package_ahriman, BuildStatusEnum.Unknown) - - -def test_package_add_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must suppress HTTP exception happened during addition - """ - mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) - web_client.package_add(package_ahriman, BuildStatusEnum.Unknown) - - -def test_package_add_failed_suppress(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: - """ - must suppress any exception happened during addition and don't log - """ - web_client.suppress_errors = True - mocker.patch("requests.Session.request", side_effect=Exception()) - logging_mock = mocker.patch("logging.exception") - - web_client.package_add(package_ahriman, BuildStatusEnum.Unknown) - logging_mock.assert_not_called() - - -def test_package_add_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, - mocker: MockerFixture) -> None: - """ - must suppress HTTP exception happened during addition and don't log - """ - web_client.suppress_errors = True - mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) - logging_mock = mocker.patch("logging.exception") - - web_client.package_add(package_ahriman, BuildStatusEnum.Unknown) - logging_mock.assert_not_called() - - def test_package_changes_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: """ must get changes @@ -183,37 +157,37 @@ def test_package_changes_get_failed_http_error_suppress(web_client: WebClient, p logging_mock.assert_not_called() -def test_package_changes_set(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_changes_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: """ must set changes """ changes = Changes("sha") requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request") - web_client.package_changes_set(package_ahriman.base, changes) + web_client.package_changes_update(package_ahriman.base, changes) requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=web_client.repository_id.query(), json=changes.view()) -def test_package_changes_set_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_changes_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: """ must suppress any exception happened during changes update """ mocker.patch("requests.Session.request", side_effect=Exception()) - web_client.package_changes_set(package_ahriman.base, Changes()) + web_client.package_changes_update(package_ahriman.base, Changes()) -def test_package_changes_set_failed_http_error(web_client: WebClient, package_ahriman: Package, - mocker: MockerFixture) -> None: +def test_package_changes_update_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: """ must suppress HTTP exception happened during changes update """ mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) - web_client.package_changes_set(package_ahriman.base, Changes()) + web_client.package_changes_update(package_ahriman.base, Changes()) -def test_package_changes_set_failed_suppress(web_client: WebClient, package_ahriman: Package, - mocker: MockerFixture) -> None: +def test_package_changes_update_failed_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: """ must suppress any exception happened during changes update and don't log """ @@ -221,12 +195,12 @@ def test_package_changes_set_failed_suppress(web_client: WebClient, package_ahri mocker.patch("requests.Session.request", side_effect=Exception()) logging_mock = mocker.patch("logging.exception") - web_client.package_changes_set(package_ahriman.base, Changes()) + web_client.package_changes_update(package_ahriman.base, Changes()) logging_mock.assert_not_called() -def test_package_changes_set_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, - mocker: MockerFixture) -> None: +def test_package_changes_update_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: """ must suppress HTTP exception happened during changes update and don't log """ @@ -234,7 +208,124 @@ def test_package_changes_set_failed_http_error_suppress(web_client: WebClient, p mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) logging_mock = mocker.patch("logging.exception") - web_client.package_changes_set(package_ahriman.base, Changes()) + web_client.package_changes_update(package_ahriman.base, Changes()) + logging_mock.assert_not_called() + + +def test_package_dependencies_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must get dependencies + """ + dependencies = Dependencies({"path": ["package"]}) + response_obj = requests.Response() + response_obj._content = json.dumps(dependencies.view()).encode("utf8") + response_obj.status_code = 200 + + requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj) + + result = web_client.package_dependencies_get(package_ahriman.base) + requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True), + params=web_client.repository_id.query()) + assert result == dependencies + + +def test_package_dependencies_get_failed(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during dependencies fetch + """ + mocker.patch("requests.Session.request", side_effect=Exception()) + web_client.package_dependencies_get(package_ahriman.base) + + +def test_package_dependencies_get_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during dependencies fetch + """ + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + web_client.package_dependencies_get(package_ahriman.base) + + +def test_package_dependencies_get_failed_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during dependencies fetch and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=Exception()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_dependencies_get(package_ahriman.base) + logging_mock.assert_not_called() + + +def test_package_dependencies_get_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during dependencies fetch and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_dependencies_get(package_ahriman.base) + logging_mock.assert_not_called() + + +def test_package_dependencies_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must set dependencies + """ + dependencies = Dependencies({"path": ["package"]}) + requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request") + + web_client.package_dependencies_update(package_ahriman.base, dependencies) + requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), + params=web_client.repository_id.query(), json=dependencies.view()) + + +def test_package_dependencies_update_failed(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during dependencies update + """ + mocker.patch("requests.Session.request", side_effect=Exception()) + web_client.package_dependencies_update(package_ahriman.base, Dependencies()) + + +def test_package_dependencies_update_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during dependencies update + """ + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + web_client.package_dependencies_update(package_ahriman.base, Dependencies()) + + +def test_package_dependencies_update_failed_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during dependencies update and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=Exception()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_dependencies_update(package_ahriman.base, Dependencies()) + logging_mock.assert_not_called() + + +def test_package_dependencies_update_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during dependencies update and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_dependencies_update(package_ahriman.base, Dependencies()) logging_mock.assert_not_called() @@ -291,8 +382,8 @@ def test_package_get_single(web_client: WebClient, package_ahriman: Package, moc assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result] -def test_package_logs(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package, - mocker: MockerFixture) -> None: +def test_package_logs_add(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package, + mocker: MockerFixture) -> None: """ must process log record """ @@ -303,31 +394,314 @@ def test_package_logs(web_client: WebClient, log_record: logging.LogRecord, pack "version": package_ahriman.version, } - web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record) + web_client.package_logs_add(LogRecordId(package_ahriman.base, package_ahriman.version), + log_record.created, log_record.getMessage()) requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=web_client.repository_id.query(), json=payload, suppress_errors=True) -def test_package_logs_failed(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package, - mocker: MockerFixture) -> None: +def test_package_logs_add_failed(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package, + mocker: MockerFixture) -> None: """ must pass exception during log post """ mocker.patch("requests.Session.request", side_effect=Exception()) log_record.package_base = package_ahriman.base with pytest.raises(Exception): - web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record) + web_client.package_logs_add(LogRecordId(package_ahriman.base, package_ahriman.version), + log_record.created, log_record.getMessage()) -def test_package_logs_failed_http_error(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package, - mocker: MockerFixture) -> None: +def test_package_logs_add_failed_http_error(web_client: WebClient, log_record: logging.LogRecord, + package_ahriman: Package, mocker: MockerFixture) -> None: """ must pass exception during log post """ mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) log_record.package_base = package_ahriman.base with pytest.raises(Exception): - web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record) + web_client.package_logs_add(LogRecordId(package_ahriman.base, package_ahriman.version), + log_record.created, log_record.getMessage()) + + +def test_package_logs_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must get logs + """ + message = {"created": 42.0, "message": "log"} + response_obj = requests.Response() + response_obj._content = json.dumps([message]).encode("utf8") + response_obj.status_code = 200 + + requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj) + + result = web_client.package_logs_get(package_ahriman.base, 1, 2) + requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True), + params=web_client.repository_id.query() + [("limit", "1"), ("offset", "2")]) + assert result == [(message["created"], message["message"])] + + +def test_package_logs_get_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must suppress any exception happened during logs fetch + """ + mocker.patch("requests.Session.request", side_effect=Exception()) + web_client.package_logs_get(package_ahriman.base) + + +def test_package_logs_get_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during logs fetch + """ + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + web_client.package_logs_get(package_ahriman.base) + + +def test_package_logs_get_failed_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during logs fetch and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=Exception()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_logs_get(package_ahriman.base) + logging_mock.assert_not_called() + + +def test_package_logs_get_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during logs fetch and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_logs_get(package_ahriman.base) + logging_mock.assert_not_called() + + +def test_package_logs_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must remove logs + """ + requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request") + + web_client.package_logs_remove(package_ahriman.base, "42") + requests_mock.assert_called_once_with("DELETE", pytest.helpers.anyvar(str, True), + params=web_client.repository_id.query() + [("version", "42")]) + + +def test_package_logs_remove_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must suppress any exception happened during logs removal + """ + mocker.patch("requests.Session.request", side_effect=Exception()) + web_client.package_logs_remove(package_ahriman.base, "42") + + +def test_package_logs_remove_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during logs removal + """ + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + web_client.package_logs_remove(package_ahriman.base, "42") + + +def test_package_logs_remove_failed_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during logs removal and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=Exception()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_logs_remove(package_ahriman.base, "42") + logging_mock.assert_not_called() + + +def test_package_logs_remove_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during logs removal and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_logs_remove(package_ahriman.base, "42") + logging_mock.assert_not_called() + + +def test_package_patches_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must get patches + """ + patch = PkgbuildPatch("key", "value") + response_obj = requests.Response() + response_obj._content = json.dumps(patch.view()).encode("utf8") + response_obj.status_code = 200 + + requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj) + + result = web_client.package_patches_get(package_ahriman.base, "key") + requests_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True)) + assert result == [patch] + + +def test_package_patches_get_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must suppress any exception happened during patches fetch + """ + mocker.patch("requests.Session.request", side_effect=Exception()) + web_client.package_patches_get(package_ahriman.base, None) + + +def test_package_patches_get_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during dependencies fetch + """ + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + web_client.package_patches_get(package_ahriman.base, None) + + +def test_package_patches_get_failed_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during patches fetch and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=Exception()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_patches_get(package_ahriman.base, None) + logging_mock.assert_not_called() + + +def test_package_patches_get_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during patches fetch and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_patches_get(package_ahriman.base, None) + logging_mock.assert_not_called() + + +def test_package_patches_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must set patches + """ + patch = PkgbuildPatch("key", "value") + requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request") + + web_client.package_patches_update(package_ahriman.base, patch) + requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), json=patch.view()) + + +def test_package_patches_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must suppress any exception happened during patches update + """ + mocker.patch("requests.Session.request", side_effect=Exception()) + web_client.package_patches_update(package_ahriman.base, PkgbuildPatch("key", "value")) + + +def test_package_patches_update_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during patches update + """ + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + web_client.package_patches_update(package_ahriman.base, PkgbuildPatch("key", "value")) + + +def test_package_patches_update_failed_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during patches update and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=Exception()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_patches_update(package_ahriman.base, PkgbuildPatch("key", "value")) + logging_mock.assert_not_called() + + +def test_package_patches_update_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during patches update and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_patches_update(package_ahriman.base, PkgbuildPatch("key", "value")) + logging_mock.assert_not_called() + + +def test_package_patches_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must remove patches + """ + requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request") + + web_client.package_patches_remove(package_ahriman.base, "key") + requests_mock.assert_called_once_with("DELETE", pytest.helpers.anyvar(str, True)) + + +def test_package_patches_remove_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must suppress any exception happened during patches removal + """ + mocker.patch("requests.Session.request", side_effect=Exception()) + web_client.package_patches_remove(package_ahriman.base, None) + + +def test_package_patches_remove_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during patches removal + """ + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + web_client.package_patches_remove(package_ahriman.base, None) + + +def test_package_patches_remove_failed_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress any exception happened during patches removal and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=Exception()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_patches_remove(package_ahriman.base, None) + logging_mock.assert_not_called() + + +def test_package_patches_remove_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during patches removal and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_patches_remove(package_ahriman.base, None) + logging_mock.assert_not_called() def test_package_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: @@ -358,13 +732,13 @@ def test_package_remove_failed_http_error(web_client: WebClient, package_ahriman web_client.package_remove(package_ahriman.base) -def test_package_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_status_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: """ must process package update """ requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request") - web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown) + web_client.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown) requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=web_client.repository_id.query(), json={ @@ -372,21 +746,75 @@ def test_package_update(web_client: WebClient, package_ahriman: Package, mocker: }) -def test_package_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: +def test_package_status_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: """ must suppress any exception happened during update """ mocker.patch("requests.Session.request", side_effect=Exception()) - web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown) + web_client.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown) + + +def test_package_status_update_failed_http_error(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during update + """ + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + web_client.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown) + + +def test_package_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must process package addition + """ + requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request") + payload = pytest.helpers.get_package_status(package_ahriman) + + web_client.package_update(package_ahriman, BuildStatusEnum.Unknown) + requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), + params=web_client.repository_id.query(), json=payload) + + +def test_package_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must suppress any exception happened during addition + """ + mocker.patch("requests.Session.request", side_effect=Exception()) + web_client.package_update(package_ahriman, BuildStatusEnum.Unknown) def test_package_update_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: """ - must suppress HTTP exception happened during update + must suppress HTTP exception happened during addition """ mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) - web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown) + web_client.package_update(package_ahriman, BuildStatusEnum.Unknown) + + +def test_package_update_failed_suppress(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None: + """ + must suppress any exception happened during addition and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=Exception()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_update(package_ahriman, BuildStatusEnum.Unknown) + logging_mock.assert_not_called() + + +def test_package_update_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, + mocker: MockerFixture) -> None: + """ + must suppress HTTP exception happened during addition and don't log + """ + web_client.suppress_errors = True + mocker.patch("requests.Session.request", side_effect=requests.HTTPError()) + logging_mock = mocker.patch("logging.exception") + + web_client.package_update(package_ahriman, BuildStatusEnum.Unknown) + logging_mock.assert_not_called() def test_status_get(web_client: WebClient, mocker: MockerFixture) -> None: diff --git a/tests/ahriman/core/support/test_package_creator.py b/tests/ahriman/core/support/test_package_creator.py index acb32316..dc2b88a7 100644 --- a/tests/ahriman/core/support/test_package_creator.py +++ b/tests/ahriman/core/support/test_package_creator.py @@ -1,8 +1,7 @@ -import pytest - +from pathlib import Path from pytest_mock import MockerFixture -from ahriman.core.database import SQLite +from ahriman.core.status import Client from ahriman.core.support.package_creator import PackageCreator from ahriman.models.package import Package from ahriman.models.package_description import PackageDescription @@ -10,32 +9,52 @@ from ahriman.models.package_source import PackageSource from ahriman.models.remote_source import RemoteSource -def test_run(package_creator: PackageCreator, database: SQLite, mocker: MockerFixture) -> None: +def test_package_create(package_creator: PackageCreator, mocker: MockerFixture) -> None: """ - must correctly process package creation + must create package """ + path = Path("local") + rmtree_mock = mocker.patch("shutil.rmtree") + mkdir_mock = mocker.patch("pathlib.Path.mkdir") + write_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.write_pkgbuild") + init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init") + + package_creator.package_create(path) + rmtree_mock.assert_called_once_with(path, ignore_errors=True) + mkdir_mock.assert_called_once_with(mode=0o755, parents=True, exist_ok=True) + write_mock.assert_called_once_with(path) + init_mock.assert_called_once_with(path) + + +def test_package_register(package_creator: PackageCreator, mocker: MockerFixture) -> None: + """ + must register package + """ + path = Path("local") package = Package( base=package_creator.generator.pkgname, version=package_creator.generator.pkgver, remote=RemoteSource(source=PackageSource.Local), packages={package_creator.generator.pkgname: PackageDescription()}, ) - local_path = package_creator.configuration.repository_paths.cache_for(package_creator.generator.pkgname) - - rmtree_mock = mocker.patch("shutil.rmtree") - database_mock = mocker.patch("ahriman.core._Context.get", return_value=database) - init_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.init") - insert_mock = mocker.patch("ahriman.core.database.SQLite.package_update") - mkdir_mock = mocker.patch("pathlib.Path.mkdir") + client_mock = mocker.patch("ahriman.core._Context.get", return_value=Client()) + insert_mock = mocker.patch("ahriman.core.status.Client.set_unknown") package_mock = mocker.patch("ahriman.models.package.Package.from_build", return_value=package) - write_mock = mocker.patch("ahriman.core.support.pkgbuild.pkgbuild_generator.PkgbuildGenerator.write_pkgbuild") + + package_creator.package_register(path) + package_mock.assert_called_once_with(path, "x86_64", None) + client_mock.assert_called_once_with(Client) + insert_mock.assert_called_once_with(package) + + +def test_run(package_creator: PackageCreator, mocker: MockerFixture) -> None: + """ + must correctly process package creation + """ + path = package_creator.configuration.repository_paths.cache_for(package_creator.generator.pkgname) + create_mock = mocker.patch("ahriman.core.support.package_creator.PackageCreator.package_create") + register_mock = mocker.patch("ahriman.core.support.package_creator.PackageCreator.package_register") package_creator.run() - rmtree_mock.assert_called_once_with(local_path, ignore_errors=True) - mkdir_mock.assert_called_once_with(mode=0o755, parents=True, exist_ok=True) - write_mock.assert_called_once_with(local_path) - init_mock.assert_called_once_with(local_path) - - package_mock.assert_called_once_with(local_path, "x86_64", None) - database_mock.assert_called_once_with(SQLite) - insert_mock.assert_called_once_with(package, pytest.helpers.anyvar(int)) + create_mock.assert_called_once_with(path) + register_mock.assert_called_once_with(path) diff --git a/tests/ahriman/core/test_util.py b/tests/ahriman/core/test_util.py index 9686e332..b26303fc 100644 --- a/tests/ahriman/core/test_util.py +++ b/tests/ahriman/core/test_util.py @@ -2,7 +2,6 @@ import datetime import logging import os import pytest -import shlex from pathlib import Path from pytest_mock import MockerFixture diff --git a/tests/ahriman/models/test_dependencies.py b/tests/ahriman/models/test_dependencies.py index e69de29b..098a8eff 100644 --- a/tests/ahriman/models/test_dependencies.py +++ b/tests/ahriman/models/test_dependencies.py @@ -0,0 +1,9 @@ +from ahriman.models.dependencies import Dependencies + + +def test_from_json_view() -> None: + """ + must construct and serialize dependencies to json + """ + dependencies = Dependencies({"/usr/bin/python3": ["python"]}) + assert Dependencies.from_json(dependencies.view()) == dependencies diff --git a/tests/ahriman/models/test_package_archive.py b/tests/ahriman/models/test_package_archive.py index 35c27654..6cab8a0e 100644 --- a/tests/ahriman/models/test_package_archive.py +++ b/tests/ahriman/models/test_package_archive.py @@ -65,13 +65,11 @@ def test_depends_on(package_archive_ahriman: PackageArchive, mocker: MockerFixtu )) result = package_archive_ahriman.depends_on() - assert result.package_base == package_archive_ahriman.package.base assert result.paths == { - Path("package1") / "file1": ["package1"], - Path("package2") / "file3": ["package2"], - Path("package2") / "dir4": ["package2"], - Path("package2") / "file3": ["package2"], - Path("usr") / "dir2": ["package1", "package2"] + "package1/file1": ["package1"], + "package2/file3": ["package2"], + "package2/dir4": ["package2"], + "usr/dir2": ["package1", "package2"] } diff --git a/tests/ahriman/models/test_pkgbuild_patch.py b/tests/ahriman/models/test_pkgbuild_patch.py index e0214d7c..70d95ed3 100644 --- a/tests/ahriman/models/test_pkgbuild_patch.py +++ b/tests/ahriman/models/test_pkgbuild_patch.py @@ -51,6 +51,14 @@ def test_from_env() -> None: assert PkgbuildPatch.from_env("KEY") == PkgbuildPatch("KEY", "") +def test_from_json_view() -> None: + """ + must correctly serialize to json + """ + patch = PkgbuildPatch("key", "value") + assert PkgbuildPatch.from_json(patch.view()) == patch + + def test_parse() -> None: """ must parse string correctly @@ -124,13 +132,6 @@ def test_serialize_list() -> None: assert PkgbuildPatch("key", ["val'ue", "val\"ue2"]).serialize() == """key=('val'"'"'ue' 'val"ue2')""" -def test_view() -> None: - """ - must correctly serialize to json - """ - assert PkgbuildPatch("key", "value").view() == {"key": "key", "value": "value"} - - def test_write(mocker: MockerFixture) -> None: """ must write serialized value to the file diff --git a/tests/ahriman/models/test_repository_id.py b/tests/ahriman/models/test_repository_id.py index 94a9fb23..f18973c0 100644 --- a/tests/ahriman/models/test_repository_id.py +++ b/tests/ahriman/models/test_repository_id.py @@ -60,4 +60,4 @@ def test_lt_invalid() -> None: must raise ValueError if other is not valid repository id """ with pytest.raises(ValueError): - RepositoryId("x86_64", "a") < 42 + assert RepositoryId("x86_64", "a") < 42 diff --git a/tests/ahriman/web/schemas/test_dependencies_schema.py b/tests/ahriman/web/schemas/test_dependencies_schema.py new file mode 100644 index 00000000..1982fb6b --- /dev/null +++ b/tests/ahriman/web/schemas/test_dependencies_schema.py @@ -0,0 +1 @@ +# schema testing goes in view class tests diff --git a/tests/ahriman/web/schemas/test_package_version_schema.py b/tests/ahriman/web/schemas/test_package_version_schema.py new file mode 100644 index 00000000..1982fb6b --- /dev/null +++ b/tests/ahriman/web/schemas/test_package_version_schema.py @@ -0,0 +1 @@ +# schema testing goes in view class tests diff --git a/tests/ahriman/web/views/test_view_base.py b/tests/ahriman/web/views/test_view_base.py index cb74933a..901e095c 100644 --- a/tests/ahriman/web/views/test_view_base.py +++ b/tests/ahriman/web/views/test_view_base.py @@ -204,6 +204,15 @@ def test_service_not_found(base: BaseView) -> None: base.service(RepositoryId("", "")) +def test_service_package(base: BaseView, repository_id: RepositoryId, mocker: MockerFixture) -> None: + """ + must validate that package exists + """ + mocker.patch("ahriman.web.views.base.BaseView.repository_id", return_value=repository_id) + with pytest.raises(HTTPNotFound): + base.service(package_base="base") + + async def test_username(base: BaseView, mocker: MockerFixture) -> None: """ must return identity of logged-in user diff --git a/tests/ahriman/web/views/v1/packages/test_view_v1_packages_dependencies.py b/tests/ahriman/web/views/v1/packages/test_view_v1_packages_dependencies.py new file mode 100644 index 00000000..ffa1e913 --- /dev/null +++ b/tests/ahriman/web/views/v1/packages/test_view_v1_packages_dependencies.py @@ -0,0 +1,97 @@ +import pytest + +from aiohttp.test_utils import TestClient + +from ahriman.models.build_status import BuildStatusEnum +from ahriman.models.dependencies import Dependencies +from ahriman.models.package import Package +from ahriman.models.user_access import UserAccess +from ahriman.web.views.v1.packages.dependencies import DependenciesView + + +async def test_get_permission() -> None: + """ + must return correct permission for the request + """ + for method in ("GET",): + request = pytest.helpers.request("", "", method) + assert await DependenciesView.get_permission(request) == UserAccess.Reporter + for method in ("POST",): + request = pytest.helpers.request("", "", method) + assert await DependenciesView.get_permission(request) == UserAccess.Full + + +def test_routes() -> None: + """ + must return correct routes + """ + assert DependenciesView.ROUTES == ["/api/v1/packages/{package}/dependencies"] + + +async def test_get(client: TestClient, package_ahriman: Package) -> None: + """ + must get dependencies for package + """ + dependency = Dependencies({"path": ["package"]}) + await client.post(f"/api/v1/packages/{package_ahriman.base}", + json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) + await client.post(f"/api/v1/packages/{package_ahriman.base}/dependencies", json=dependency.view()) + response_schema = pytest.helpers.schema_response(DependenciesView.get) + + response = await client.get(f"/api/v1/packages/{package_ahriman.base}/dependencies") + assert response.status == 200 + + dependencies = await response.json() + assert not response_schema.validate(dependencies) + assert dependencies == dependency.view() + + +async def test_get_not_found(client: TestClient, package_ahriman: Package) -> None: + """ + must return not found for missing package + """ + response_schema = pytest.helpers.schema_response(DependenciesView.get, code=404) + + response = await client.get(f"/api/v1/packages/{package_ahriman.base}/dependencies") + assert response.status == 404 + assert not response_schema.validate(await response.json()) + + +async def test_post(client: TestClient, package_ahriman: Package) -> None: + """ + must create dependencies + """ + await client.post(f"/api/v1/packages/{package_ahriman.base}", + json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) + request_schema = pytest.helpers.schema_request(DependenciesView.post) + + payload = {"paths": {"path": ["package"]}} + assert not request_schema.validate(payload) + response = await client.post(f"/api/v1/packages/{package_ahriman.base}/dependencies", json=payload) + assert response.status == 204 + + response = await client.get(f"/api/v1/packages/{package_ahriman.base}/dependencies") + dependencies = await response.json() + assert dependencies == payload + + +async def test_post_exception(client: TestClient, package_ahriman: Package) -> None: + """ + must raise exception on invalid payload + """ + response_schema = pytest.helpers.schema_response(DependenciesView.post, code=400) + + response = await client.post(f"/api/v1/packages/{package_ahriman.base}/dependencies", json=[]) + assert response.status == 400 + assert not response_schema.validate(await response.json()) + + +async def test_post_not_found(client: TestClient, package_ahriman: Package) -> None: + """ + must raise exception on unknown package + """ + response_schema = pytest.helpers.schema_response(DependenciesView.post, code=404) + + response = await client.post(f"/api/v1/packages/{package_ahriman.base}/dependencies", json={}) + assert response.status == 404 + assert not response_schema.validate(await response.json()) diff --git a/tests/ahriman/web/views/v1/packages/test_view_v1_packages_logs.py b/tests/ahriman/web/views/v1/packages/test_view_v1_packages_logs.py index 8c220f0d..04c50f58 100644 --- a/tests/ahriman/web/views/v1/packages/test_view_v1_packages_logs.py +++ b/tests/ahriman/web/views/v1/packages/test_view_v1_packages_logs.py @@ -37,9 +37,21 @@ async def test_delete(client: TestClient, package_ahriman: Package, package_pyth json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()}) await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", - json={"created": 42.0, "message": "message", "version": "42"}) + json={"created": 42.0, "message": "message 1", "version": "42"}) await client.post(f"/api/v1/packages/{package_python_schedule.base}/logs", - json={"created": 42.0, "message": "message", "version": "42"}) + json={"created": 42.0, "message": "message 2", "version": "42"}) + request_schema = pytest.helpers.schema_request(LogsView.delete, location="querystring") + + payload = {} + assert not request_schema.validate(payload) + payload = {"version": "42"} + + response = await client.delete(f"/api/v1/packages/{package_ahriman.base}/logs", params=payload) + assert response.status == 204 + + response = await client.get(f"/api/v1/packages/{package_ahriman.base}/logs") + logs = await response.json() + assert logs["logs"] response = await client.delete(f"/api/v1/packages/{package_ahriman.base}/logs") assert response.status == 204 diff --git a/tests/ahriman/web/views/v1/packages/test_view_v1_packages_patches.py b/tests/ahriman/web/views/v1/packages/test_view_v1_packages_patches.py index fee9bc0f..b337e968 100644 --- a/tests/ahriman/web/views/v1/packages/test_view_v1_packages_patches.py +++ b/tests/ahriman/web/views/v1/packages/test_view_v1_packages_patches.py @@ -64,6 +64,24 @@ async def test_post(client: TestClient, package_ahriman: Package) -> None: assert patches == [payload] +async def test_post_full_diff(client: TestClient, package_ahriman: Package) -> None: + """ + must create patch from full diff + """ + await client.post(f"/api/v1/packages/{package_ahriman.base}", + json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()}) + request_schema = pytest.helpers.schema_request(PatchesView.post) + + payload = {"value": "v"} + assert not request_schema.validate(payload) + response = await client.post(f"/api/v1/packages/{package_ahriman.base}/patches", json=payload) + assert response.status == 204 + + response = await client.get(f"/api/v1/packages/{package_ahriman.base}/patches") + patches = await response.json() + assert patches == [payload] + + async def test_post_exception(client: TestClient, package_ahriman: Package) -> None: """ must raise exception on invalid payload diff --git a/tests/ahriman/web/views/v1/status/test_view_v1_status_info.py b/tests/ahriman/web/views/v1/status/test_view_v1_status_info.py index 3e72deaf..ff741482 100644 --- a/tests/ahriman/web/views/v1/status/test_view_v1_status_info.py +++ b/tests/ahriman/web/views/v1/status/test_view_v1_status_info.py @@ -30,7 +30,7 @@ async def test_get(client: TestClient, repository_id: RepositoryId) -> None: """ response_schema = pytest.helpers.schema_response(InfoView.get) - response = await client.get(f"/api/v1/info") + response = await client.get("/api/v1/info") assert response.ok json = await response.json() assert not response_schema.validate(json) diff --git a/tests/ahriman/web/views/v1/status/test_view_v1_status_repositories.py b/tests/ahriman/web/views/v1/status/test_view_v1_status_repositories.py index cd6f54f8..62ec0628 100644 --- a/tests/ahriman/web/views/v1/status/test_view_v1_status_repositories.py +++ b/tests/ahriman/web/views/v1/status/test_view_v1_status_repositories.py @@ -29,7 +29,7 @@ async def test_get(client: TestClient, repository_id: RepositoryId) -> None: """ response_schema = pytest.helpers.schema_response(RepositoriesView.get) - response = await client.get(f"/api/v1/repositories") + response = await client.get("/api/v1/repositories") assert response.ok json = await response.json() assert not response_schema.validate(json, many=True)