mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-03-12 21:13:38 +00:00
feat: support archive listing
This commit is contained in:
@@ -12,6 +12,14 @@ ahriman.application.handlers.add module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.archives module
|
||||
--------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.application.handlers.archives
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.backup module
|
||||
------------------------------------------
|
||||
|
||||
|
||||
@@ -4,6 +4,14 @@ ahriman.web.views.v1.packages package
|
||||
Submodules
|
||||
----------
|
||||
|
||||
ahriman.web.views.v1.packages.archives module
|
||||
---------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.packages.archives
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.packages.changes module
|
||||
--------------------------------------------
|
||||
|
||||
|
||||
@@ -154,13 +154,13 @@ class Application(ApplicationPackages, ApplicationRepository):
|
||||
for package_name, packager in missing.items():
|
||||
if (source_dir := self.repository.paths.cache_for(package_name)).is_dir():
|
||||
# there is local cache, load package from it
|
||||
leaf = Package.from_build(source_dir, self.repository.architecture, packager)
|
||||
leaf = Package.from_build(source_dir, self.repository.repository_id.architecture, packager)
|
||||
else:
|
||||
leaf = Package.from_aur(package_name, packager, include_provides=True)
|
||||
portion[leaf.base] = leaf
|
||||
|
||||
# register package in the database
|
||||
self.repository.reporter.set_unknown(leaf)
|
||||
self.reporter.set_unknown(leaf)
|
||||
|
||||
return portion
|
||||
|
||||
|
||||
@@ -46,7 +46,7 @@ class ApplicationRepository(ApplicationProperties):
|
||||
continue # skip check in case if we can't calculate diff
|
||||
|
||||
if (changes := self.repository.package_changes(package, last_commit_sha)) is not None:
|
||||
self.repository.reporter.package_changes_update(package.base, changes)
|
||||
self.reporter.package_changes_update(package.base, changes)
|
||||
|
||||
def clean(self, *, cache: bool, chroot: bool, manual: bool, packages: bool, pacman: bool) -> None:
|
||||
"""
|
||||
|
||||
81
src/ahriman/application/handlers/archives.py
Normal file
81
src/ahriman/application/handlers/archives.py
Normal file
@@ -0,0 +1,81 @@
|
||||
#
|
||||
# Copyright (c) 2021-2026 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.formatters import PackagePrinter
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class Archives(Handler):
|
||||
"""
|
||||
package archives handler
|
||||
"""
|
||||
|
||||
ALLOW_MULTI_ARCHITECTURE_RUN = False # conflicting io
|
||||
|
||||
@classmethod
|
||||
def run(cls, args: argparse.Namespace, repository_id: RepositoryId, configuration: Configuration, *,
|
||||
report: bool) -> None:
|
||||
"""
|
||||
callback for command line
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line args
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=True)
|
||||
|
||||
match args.action:
|
||||
case Action.List:
|
||||
archives = application.repository.package_archives(args.package)
|
||||
for package in archives:
|
||||
PackagePrinter(package, BuildStatus(BuildStatusEnum.Success))(verbose=args.info)
|
||||
|
||||
Archives.check_status(args.exit_code, bool(archives))
|
||||
|
||||
@staticmethod
|
||||
def _set_package_archives_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for package archives subcommand
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("package-archives", help="list package archive versions",
|
||||
description="list available archive versions for the package")
|
||||
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("--info", help="show additional package information",
|
||||
action=argparse.BooleanOptionalAction, default=False)
|
||||
parser.set_defaults(action=Action.List, lock=None, quiet=True, report=False, unsafe=True)
|
||||
return parser
|
||||
|
||||
arguments = [_set_package_archives_parser]
|
||||
@@ -47,8 +47,7 @@ class Change(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=True)
|
||||
client = application.repository.reporter
|
||||
client = Application(repository_id, configuration, report=True).reporter
|
||||
|
||||
match args.action:
|
||||
case Action.List:
|
||||
|
||||
@@ -48,8 +48,7 @@ class Pkgbuild(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=True)
|
||||
client = application.repository.reporter
|
||||
client = Application(repository_id, configuration, report=True).reporter
|
||||
|
||||
match args.action:
|
||||
case Action.List:
|
||||
|
||||
@@ -44,8 +44,7 @@ class Reload(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=True)
|
||||
client = application.repository.reporter
|
||||
client = Application(repository_id, configuration, report=True).reporter
|
||||
client.configuration_reload()
|
||||
|
||||
@staticmethod
|
||||
|
||||
@@ -52,7 +52,7 @@ class Status(Handler):
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
# we are using reporter here
|
||||
client = Application(repository_id, configuration, report=True).repository.reporter
|
||||
client = Application(repository_id, configuration, report=True).reporter
|
||||
if args.ahriman:
|
||||
service_status = client.status_get()
|
||||
StatusPrinter(service_status.status)(verbose=args.info)
|
||||
|
||||
@@ -47,8 +47,7 @@ class StatusUpdate(Handler):
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=True)
|
||||
client = application.repository.reporter
|
||||
client = Application(repository_id, configuration, report=True).reporter
|
||||
|
||||
match args.action:
|
||||
case Action.Update if args.package:
|
||||
|
||||
@@ -61,12 +61,11 @@ class Executor(PackageInfo, Cleaner):
|
||||
if built.version != package.version:
|
||||
continue
|
||||
|
||||
packages = built.packages.values()
|
||||
# all packages must be either any or same architecture
|
||||
if not all(single.architecture in ("any", self.architecture) for single in packages):
|
||||
if not built.supports_architecture(self.repository_id.architecture):
|
||||
continue
|
||||
|
||||
return list_flatmap(packages, lambda single: archive.glob(f"{single.filename}*"))
|
||||
return list_flatmap(built.packages.values(), lambda single: archive.glob(f"{single.filename}*"))
|
||||
|
||||
return []
|
||||
|
||||
@@ -102,11 +101,11 @@ class Executor(PackageInfo, Cleaner):
|
||||
"""
|
||||
self.reporter.set_building(package.base)
|
||||
|
||||
task = Task(package, self.configuration, self.architecture, self.paths)
|
||||
task = Task(package, self.configuration, self.repository_id.architecture, self.paths)
|
||||
patches = self.reporter.package_patches_get(package.base, None)
|
||||
commit_sha = task.init(path, patches, local_version)
|
||||
|
||||
loaded_package = Package.from_build(path, self.architecture, None)
|
||||
loaded_package = Package.from_build(path, self.repository_id.architecture, None)
|
||||
if prebuilt := list(self._archive_lookup(loaded_package)):
|
||||
self.logger.info("using prebuilt packages for %s-%s", loaded_package.base, loaded_package.version)
|
||||
built = []
|
||||
@@ -218,7 +217,7 @@ class Executor(PackageInfo, Cleaner):
|
||||
except Exception:
|
||||
self.reporter.set_failed(single.base)
|
||||
result.add_failed(single)
|
||||
self.logger.exception("%s (%s) build exception", single.base, self.architecture)
|
||||
self.logger.exception("%s (%s) build exception", single.base, self.repository_id.architecture)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ from ahriman.core.status import Client
|
||||
from ahriman.core.utils import package_like
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class PackageInfo(LazyLogging):
|
||||
@@ -43,11 +44,13 @@ class PackageInfo(LazyLogging):
|
||||
configuration(Configuration): configuration instance
|
||||
pacman(Pacman): alpm wrapper instance
|
||||
reporter(Client): build status reporter instance
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
"""
|
||||
|
||||
configuration: Configuration
|
||||
pacman: Pacman
|
||||
reporter: Client
|
||||
repository_id: RepositoryId
|
||||
|
||||
def full_depends(self, package: Package, packages: Iterable[Package]) -> list[str]:
|
||||
"""
|
||||
@@ -133,6 +136,8 @@ class PackageInfo(LazyLogging):
|
||||
# we can't use here load_archives, because it ignores versions
|
||||
for full_path in filter(package_like, paths.archive_for(package_base).iterdir()):
|
||||
local = Package.from_archive(full_path)
|
||||
if not local.supports_architecture(self.repository_id.architecture):
|
||||
continue
|
||||
packages.setdefault((local.base, local.version), local).packages.update(local.packages)
|
||||
|
||||
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
||||
|
||||
@@ -72,32 +72,12 @@ class RepositoryProperties(EventLogger, LazyLogging):
|
||||
self.ignore_list = configuration.getlist("build", "ignore_packages", fallback=[])
|
||||
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.repo = Repo(self.repository_id.name, self.paths, self.sign.repository_sign_args)
|
||||
self.reporter = Client.load(repository_id, configuration, database, report=report)
|
||||
self.triggers = TriggerLoader.load(repository_id, configuration)
|
||||
|
||||
self.scan_paths = ScanPaths(configuration.getlist("build", "scan_paths", fallback=[]))
|
||||
|
||||
@property
|
||||
def architecture(self) -> str:
|
||||
"""
|
||||
repository architecture for backward compatibility
|
||||
|
||||
Returns:
|
||||
str: repository architecture
|
||||
"""
|
||||
return self.repository_id.architecture
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
"""
|
||||
repository name for backward compatibility
|
||||
|
||||
Returns:
|
||||
str: repository name
|
||||
"""
|
||||
return self.repository_id.name
|
||||
|
||||
def packager(self, packagers: Packagers, package_base: str) -> User:
|
||||
"""
|
||||
extract packager from configuration having username
|
||||
|
||||
@@ -150,7 +150,7 @@ class UpdateHandler(PackageInfo, Cleaner):
|
||||
)
|
||||
|
||||
Sources.fetch(cache_dir, source)
|
||||
remote = Package.from_build(cache_dir, self.architecture, None)
|
||||
remote = Package.from_build(cache_dir, self.repository_id.architecture, None)
|
||||
|
||||
local = packages.get(remote.base)
|
||||
if local is None:
|
||||
|
||||
@@ -23,6 +23,7 @@ from typing import Any, Self
|
||||
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.repository.package_info import PackageInfo
|
||||
from ahriman.core.status import Client
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.changes import Changes
|
||||
@@ -39,15 +40,18 @@ class Watcher(LazyLogging):
|
||||
|
||||
Attributes:
|
||||
client(Client): reporter instance
|
||||
package_info(PackageInfo): package info instance
|
||||
status(BuildStatus): daemon status
|
||||
"""
|
||||
|
||||
def __init__(self, client: Client) -> None:
|
||||
def __init__(self, client: Client, package_info: PackageInfo) -> None:
|
||||
"""
|
||||
Args:
|
||||
client(Client): reporter instance
|
||||
package_info(PackageInfo): package info instance
|
||||
"""
|
||||
self.client = client
|
||||
self.package_info = package_info
|
||||
|
||||
self._lock = Lock()
|
||||
self._known: dict[str, tuple[Package, BuildStatus]] = {}
|
||||
@@ -80,6 +84,18 @@ class Watcher(LazyLogging):
|
||||
|
||||
logs_rotate: Callable[[int], None]
|
||||
|
||||
def package_archives(self, package_base: str) -> list[Package]:
|
||||
"""
|
||||
get known package archives
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
|
||||
Returns:
|
||||
list[Package]: list of built package for this package base
|
||||
"""
|
||||
return self.package_info.package_archives(package_base)
|
||||
|
||||
package_changes_get: Callable[[str], Changes]
|
||||
|
||||
package_changes_update: Callable[[str, Changes], None]
|
||||
|
||||
@@ -137,16 +137,6 @@ class Package(LazyLogging):
|
||||
"""
|
||||
return list_flatmap(self.packages.values(), lambda package: package.groups)
|
||||
|
||||
@property
|
||||
def is_single_package(self) -> bool:
|
||||
"""
|
||||
is it possible to transform package base to single package or not
|
||||
|
||||
Returns:
|
||||
bool: true in case if this base has only one package with the same name
|
||||
"""
|
||||
return self.base in self.packages and len(self.packages) == 1
|
||||
|
||||
@property
|
||||
def is_vcs(self) -> bool:
|
||||
"""
|
||||
@@ -375,9 +365,22 @@ class Package(LazyLogging):
|
||||
Returns:
|
||||
str: print-friendly string
|
||||
"""
|
||||
details = "" if self.is_single_package else f" ({" ".join(sorted(self.packages.keys()))})"
|
||||
is_single_package = self.base in self.packages and len(self.packages) == 1
|
||||
details = "" if is_single_package else f" ({" ".join(sorted(self.packages.keys()))})"
|
||||
return f"{self.base}{details}"
|
||||
|
||||
def supports_architecture(self, architecture: str) -> bool:
|
||||
"""
|
||||
helper to check if the package belongs to the specified architecture
|
||||
|
||||
Args:
|
||||
architecture(str): probe repository architecture
|
||||
|
||||
Returns:
|
||||
bool: ``True`` if all packages are same architecture or any
|
||||
"""
|
||||
return all(single.architecture in ("any", architecture) for single in self.packages.values())
|
||||
|
||||
def vercmp(self, version: str) -> int:
|
||||
"""
|
||||
typed wrapper around :func:`pyalpm.vercmp()`
|
||||
|
||||
65
src/ahriman/web/views/v1/packages/archives.py
Normal file
65
src/ahriman/web/views/v1/packages/archives.py
Normal file
@@ -0,0 +1,65 @@
|
||||
#
|
||||
# Copyright (c) 2021-2026 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 <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
from aiohttp.web import Response
|
||||
from typing import ClassVar
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.apispec.decorators import apidocs
|
||||
from ahriman.web.schemas import PackageNameSchema, PackageSchema, RepositoryIdSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
from ahriman.web.views.status_view_guard import StatusViewGuard
|
||||
|
||||
|
||||
class Archives(StatusViewGuard, BaseView):
|
||||
"""
|
||||
package archives web view
|
||||
|
||||
Attributes:
|
||||
GET_PERMISSION(UserAccess): (class attribute) get permissions of self
|
||||
"""
|
||||
|
||||
GET_PERMISSION: ClassVar[UserAccess] = UserAccess.Reporter
|
||||
ROUTES = ["/api/v1/packages/{package}/archives"]
|
||||
|
||||
@apidocs(
|
||||
tags=["Packages"],
|
||||
summary="Get package archives",
|
||||
description="Retrieve built package archives for the base",
|
||||
permission=GET_PERMISSION,
|
||||
error_404_description="Package base and/or repository are unknown",
|
||||
schema=PackageSchema(many=True),
|
||||
match_schema=PackageNameSchema,
|
||||
query_schema=RepositoryIdSchema,
|
||||
)
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
get package archives
|
||||
|
||||
Returns:
|
||||
Response: 200 with package archives on success
|
||||
|
||||
Raises:
|
||||
HTTPNotFound: if no package was found
|
||||
"""
|
||||
package_base = self.request.match_info["package"]
|
||||
|
||||
archives = self.service(package_base=package_base).package_archives(package_base)
|
||||
|
||||
return self.json_response([archive.view() for archive in archives])
|
||||
@@ -23,12 +23,14 @@ import logging
|
||||
import socket
|
||||
|
||||
from aiohttp.web import Application, normalize_path_middleware, run_app
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.auth import Auth
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.distributed import WorkersCache
|
||||
from ahriman.core.exceptions import InitializeError
|
||||
from ahriman.core.repository.package_info import PackageInfo
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status import Client
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
@@ -78,6 +80,34 @@ def _create_socket(configuration: Configuration, application: Application) -> so
|
||||
return sock
|
||||
|
||||
|
||||
def _create_watcher(path: Path, repository_id: RepositoryId) -> Watcher:
|
||||
"""
|
||||
build watcher for selected repository
|
||||
|
||||
Args:
|
||||
path(Path): path to configuration file
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
|
||||
Returns:
|
||||
Watcher: watcher instance
|
||||
"""
|
||||
logging.getLogger(__name__).info("load repository %s", repository_id)
|
||||
# load settings explicitly for architecture if any
|
||||
configuration = Configuration.from_path(path, repository_id)
|
||||
|
||||
# load database instance, because it holds identifier
|
||||
database = SQLite.load(configuration)
|
||||
# explicitly load local client
|
||||
client = Client.load(repository_id, configuration, database, report=False)
|
||||
|
||||
# load package info wrapper
|
||||
package_info = PackageInfo()
|
||||
package_info.configuration = configuration
|
||||
package_info.repository_id = repository_id
|
||||
|
||||
return Watcher(client, package_info)
|
||||
|
||||
|
||||
async def _on_shutdown(application: Application) -> None:
|
||||
"""
|
||||
web application shutdown handler
|
||||
@@ -168,18 +198,11 @@ def setup_server(configuration: Configuration, spawner: Spawn, repositories: lis
|
||||
# package cache
|
||||
if not repositories:
|
||||
raise InitializeError("No repositories configured, exiting")
|
||||
watchers: dict[RepositoryId, Watcher] = {}
|
||||
configuration_path, _ = configuration.check_loaded()
|
||||
for repository_id in repositories:
|
||||
application.logger.info("load repository %s", repository_id)
|
||||
# load settings explicitly for architecture if any
|
||||
repository_configuration = Configuration.from_path(configuration_path, repository_id)
|
||||
# load database instance, because it holds identifier
|
||||
database = SQLite.load(repository_configuration)
|
||||
# explicitly load local client
|
||||
client = Client.load(repository_id, repository_configuration, database, report=False)
|
||||
watchers[repository_id] = Watcher(client)
|
||||
application[WatcherKey] = watchers
|
||||
application[WatcherKey] = {
|
||||
repository_id: _create_watcher(configuration_path, repository_id)
|
||||
for repository_id in repositories
|
||||
}
|
||||
# workers cache
|
||||
application[WorkersKey] = WorkersCache(configuration)
|
||||
# process spawner
|
||||
|
||||
84
tests/ahriman/application/handlers/test_handler_archives.py
Normal file
84
tests/ahriman/application/handlers/test_handler_archives.py
Normal file
@@ -0,0 +1,84 @@
|
||||
import argparse
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.handlers.archives import Archives
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.database import SQLite
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.action import Action
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
"""
|
||||
default arguments for these test cases
|
||||
|
||||
Args:
|
||||
args(argparse.Namespace): command line arguments fixture
|
||||
|
||||
Returns:
|
||||
argparse.Namespace: generated arguments for these test cases
|
||||
"""
|
||||
args.action = Action.List
|
||||
args.exit_code = False
|
||||
args.info = False
|
||||
args.package = "package"
|
||||
return args
|
||||
|
||||
|
||||
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
application_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives",
|
||||
return_value=[package_ahriman])
|
||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Archives.run(args, repository_id, configuration, report=False)
|
||||
application_mock.assert_called_once_with(args.package)
|
||||
check_mock.assert_called_once_with(False, True)
|
||||
print_mock.assert_called_once_with(verbose=False, log_fn=pytest.helpers.anyvar(int), separator=": ")
|
||||
|
||||
|
||||
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise ExitCode exception on empty archives result
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.exit_code = True
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives", return_value=[])
|
||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Archives.run(args, repository_id, configuration, report=False)
|
||||
check_mock.assert_called_once_with(True, False)
|
||||
|
||||
|
||||
def test_imply_with_report(args: argparse.Namespace, configuration: Configuration, database: SQLite,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create application object with native reporting
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
|
||||
load_mock = mocker.patch("ahriman.core.repository.Repository.load")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Archives.run(args, repository_id, configuration, report=False)
|
||||
load_mock.assert_called_once_with(repository_id, configuration, database, report=True, refresh_pacman_database=0)
|
||||
|
||||
|
||||
def test_disallow_multi_architecture_run() -> None:
|
||||
"""
|
||||
must not allow multi architecture run
|
||||
"""
|
||||
assert not Archives.ALLOW_MULTI_ARCHITECTURE_RUN
|
||||
@@ -271,6 +271,22 @@ def test_subparsers_package_add_option_variable_multiple(parser: argparse.Argume
|
||||
assert args.variable == ["var1", "var2"]
|
||||
|
||||
|
||||
def test_subparsers_package_archives(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-archives command must imply action, exit code, info, lock, quiet, report and unsafe
|
||||
"""
|
||||
args = parser.parse_args(["-a", "x86_64", "-r", "repo", "package-archives", "ahriman"])
|
||||
assert args.action == Action.List
|
||||
assert args.architecture == "x86_64"
|
||||
assert not args.exit_code
|
||||
assert not args.info
|
||||
assert args.lock is None
|
||||
assert args.quiet
|
||||
assert not args.report
|
||||
assert args.repository == "repo"
|
||||
assert args.unsafe
|
||||
|
||||
|
||||
def test_subparsers_package_changes(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-changes command must imply action, exit code, lock, quiet, report and unsafe
|
||||
|
||||
@@ -16,6 +16,7 @@ from ahriman.core.database import SQLite
|
||||
from ahriman.core.database.migrations import Migrations
|
||||
from ahriman.core.log.log_loader import LogLoader
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.core.repository.package_info import PackageInfo
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status import Client
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
@@ -688,4 +689,5 @@ def watcher(local_client: Client) -> Watcher:
|
||||
Returns:
|
||||
Watcher: package status watcher test instance
|
||||
"""
|
||||
return Watcher(local_client)
|
||||
package_info = PackageInfo()
|
||||
return Watcher(local_client, package_info)
|
||||
|
||||
@@ -11,6 +11,7 @@ from ahriman.models.changes import Changes
|
||||
from ahriman.models.dependencies import Dependencies
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.user import User
|
||||
|
||||
|
||||
@@ -56,7 +57,7 @@ def test_archive_lookup_architecture_mismatch(executor: Executor, package_ahrima
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].architecture = "x86_64"
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.architecture", return_value="i686")
|
||||
executor.repository_id = RepositoryId("i686", executor.repository_id.name)
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
])
|
||||
@@ -116,7 +117,7 @@ def test_package_build(executor: Executor, package_ahriman: Package, mocker: Moc
|
||||
assert executor._package_build(package_ahriman, Path("local"), "packager", None) == "sha"
|
||||
status_client_mock.assert_called_once_with(package_ahriman.base)
|
||||
init_mock.assert_called_once_with(pytest.helpers.anyvar(int), pytest.helpers.anyvar(int), None)
|
||||
package_mock.assert_called_once_with(Path("local"), executor.architecture, None)
|
||||
package_mock.assert_called_once_with(Path("local"), executor.repository_id.architecture, None)
|
||||
lookup_mock.assert_called_once_with(package_ahriman)
|
||||
with_packages_mock.assert_called_once_with([Path(package_ahriman.base)])
|
||||
rename_mock.assert_called_once_with(Path(package_ahriman.base), executor.paths.packages / package_ahriman.base)
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import pytest
|
||||
|
||||
from dataclasses import replace
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock
|
||||
@@ -95,26 +96,30 @@ def test_package_archives(repository: Repository, package_ahriman: Package, mock
|
||||
"""
|
||||
must load package archives sorted by version
|
||||
"""
|
||||
from dataclasses import replace
|
||||
from typing import Any
|
||||
|
||||
def package(version: Any, *args: Any, **kwargs: Any) -> Package:
|
||||
generated = replace(package_ahriman, version=str(version))
|
||||
generated.packages = {
|
||||
key: replace(value, filename=str(version))
|
||||
for key, value in generated.packages.items()
|
||||
}
|
||||
return generated
|
||||
|
||||
mocker.patch("ahriman.core.repository.package_info.package_like", return_value=True)
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path(str(i)) for i in range(5)])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=package)
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[str(i) for i in range(5)])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive",
|
||||
side_effect=lambda version: replace(package_ahriman, version=version))
|
||||
|
||||
result = repository.package_archives(package_ahriman.base)
|
||||
assert len(result) == 5
|
||||
assert [p.version for p in result] == [str(i) for i in range(5)]
|
||||
|
||||
|
||||
def test_package_archives_architecture_mismatch(repository: Repository, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip packages with mismatched architecture
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].architecture = "i686"
|
||||
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.packages[package_ahriman.base].filepath])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman)
|
||||
|
||||
result = repository.package_archives(package_ahriman.base)
|
||||
assert len(result) == 0
|
||||
|
||||
|
||||
def test_package_changes(repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load package changes
|
||||
|
||||
@@ -6,20 +6,6 @@ from ahriman.models.user import User
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
def test_architecture(repository: RepositoryProperties) -> None:
|
||||
"""
|
||||
must provide repository architecture for backward compatibility
|
||||
"""
|
||||
assert repository.architecture == repository.repository_id.architecture
|
||||
|
||||
|
||||
def test_name(repository: RepositoryProperties) -> None:
|
||||
"""
|
||||
must provide repository name for backward compatibility
|
||||
"""
|
||||
assert repository.name == repository.repository_id.name
|
||||
|
||||
|
||||
def test_packager(repository: RepositoryProperties, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must extract packager
|
||||
|
||||
@@ -45,6 +45,18 @@ def test_load_known(watcher: Watcher, package_ahriman: Package, mocker: MockerFi
|
||||
assert status.status == BuildStatusEnum.Success
|
||||
|
||||
|
||||
def test_package_archives(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return package archives from package info
|
||||
"""
|
||||
archives_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives",
|
||||
return_value=[package_ahriman])
|
||||
|
||||
result = watcher.package_archives(package_ahriman.base)
|
||||
assert result == [package_ahriman]
|
||||
archives_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
||||
|
||||
def test_package_get(watcher: Watcher, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return package status
|
||||
|
||||
@@ -101,20 +101,6 @@ def test_groups(package_ahriman: Package) -> None:
|
||||
assert sorted(package_ahriman.groups) == package_ahriman.groups
|
||||
|
||||
|
||||
def test_is_single_package_false(package_python_schedule: Package) -> None:
|
||||
"""
|
||||
python-schedule must not be single package
|
||||
"""
|
||||
assert not package_python_schedule.is_single_package
|
||||
|
||||
|
||||
def test_is_single_package_true(package_ahriman: Package) -> None:
|
||||
"""
|
||||
ahriman must be single package
|
||||
"""
|
||||
assert package_ahriman.is_single_package
|
||||
|
||||
|
||||
def test_is_vcs_false(package_ahriman: Package) -> None:
|
||||
"""
|
||||
ahriman must not be VCS package
|
||||
@@ -353,6 +339,30 @@ def test_build_status_pretty_print(package_ahriman: Package) -> None:
|
||||
assert isinstance(package_ahriman.pretty_print(), str)
|
||||
|
||||
|
||||
def test_supports_architecture(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must check if package supports architecture
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].architecture = "x86_64"
|
||||
assert package_ahriman.supports_architecture("x86_64")
|
||||
|
||||
|
||||
def test_supports_architecture_any(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must support any architecture
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].architecture = "any"
|
||||
assert package_ahriman.supports_architecture("x86_64")
|
||||
|
||||
|
||||
def test_supports_architecture_mismatch(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must not support mismatched architecture
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].architecture = "i686"
|
||||
assert not package_ahriman.supports_architecture("x86_64")
|
||||
|
||||
|
||||
def test_vercmp(package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call vercmp
|
||||
|
||||
@@ -10,7 +10,7 @@ from ahriman.core.exceptions import InitializeError
|
||||
from ahriman.core.spawn import Spawn
|
||||
from ahriman.core.status.watcher import Watcher
|
||||
from ahriman.web.keys import ConfigurationKey
|
||||
from ahriman.web.web import _create_socket, _on_shutdown, _on_startup, run_server, setup_server
|
||||
from ahriman.web.web import _create_socket, _create_watcher, _on_shutdown, _on_startup, run_server, setup_server
|
||||
|
||||
|
||||
async def test_create_socket(application: Application, mocker: MockerFixture) -> None:
|
||||
@@ -139,6 +139,20 @@ def test_run_with_socket(application: Application, mocker: MockerFixture) -> Non
|
||||
)
|
||||
|
||||
|
||||
def test_create_watcher(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create watcher for repository
|
||||
"""
|
||||
database_mock = mocker.patch("ahriman.core.database.SQLite.load")
|
||||
client_mock = mocker.patch("ahriman.core.status.Client.load")
|
||||
configuration_path, repository_id = configuration.check_loaded()
|
||||
|
||||
result = _create_watcher(configuration_path, repository_id)
|
||||
assert isinstance(result, Watcher)
|
||||
database_mock.assert_called_once()
|
||||
client_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_setup_no_repositories(configuration: Configuration, spawner: Spawn) -> None:
|
||||
"""
|
||||
must raise InitializeError if no repositories set
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import pytest
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1.packages.archives import Archives
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
"""
|
||||
must return correct permission for the request
|
||||
"""
|
||||
for method in ("GET",):
|
||||
request = pytest.helpers.request("", "", method)
|
||||
assert await Archives.get_permission(request) == UserAccess.Reporter
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert Archives.ROUTES == ["/api/v1/packages/{package}/archives"]
|
||||
|
||||
|
||||
async def test_get(client: TestClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get archives for package
|
||||
"""
|
||||
await client.post(f"/api/v1/packages/{package_ahriman.base}",
|
||||
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
|
||||
mocker.patch("ahriman.core.status.watcher.Watcher.package_archives", return_value=[package_ahriman])
|
||||
response_schema = pytest.helpers.schema_response(Archives.get)
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_ahriman.base}/archives")
|
||||
assert response.status == 200
|
||||
|
||||
archives = await response.json()
|
||||
assert not response_schema.validate(archives)
|
||||
|
||||
|
||||
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(Archives.get, code=404)
|
||||
|
||||
response = await client.get(f"/api/v1/packages/{package_ahriman.base}/archives")
|
||||
assert response.status == 404
|
||||
assert not response_schema.validate(await response.json())
|
||||
Reference in New Issue
Block a user