mirror of
https://github.com/arcan1s/ahriman.git
synced 2026-03-21 17:13:39 +00:00
implement support of rollback handler
This commit is contained in:
@@ -164,6 +164,14 @@ ahriman.application.handlers.restore module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.rollback module
|
||||
--------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.application.handlers.rollback
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.application.handlers.run module
|
||||
---------------------------------------
|
||||
|
||||
|
||||
@@ -252,6 +252,14 @@ ahriman.web.schemas.package\_version\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.packager\_schema module
|
||||
-------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.packager_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.pagination\_schema module
|
||||
---------------------------------------------
|
||||
|
||||
@@ -332,6 +340,14 @@ ahriman.web.schemas.repository\_stats\_schema module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.rollback\_schema module
|
||||
-------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.schemas.rollback_schema
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.schemas.search\_schema module
|
||||
-----------------------------------------
|
||||
|
||||
|
||||
@@ -68,6 +68,14 @@ ahriman.web.views.v1.service.request module
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.service.rollback module
|
||||
--------------------------------------------
|
||||
|
||||
.. automodule:: ahriman.web.views.v1.service.rollback
|
||||
:members:
|
||||
:no-undoc-members:
|
||||
:show-inheritance:
|
||||
|
||||
ahriman.web.views.v1.service.search module
|
||||
------------------------------------------
|
||||
|
||||
|
||||
@@ -156,12 +156,15 @@ class ApplicationRepository(ApplicationProperties):
|
||||
result = Result()
|
||||
|
||||
# process already built packages if any
|
||||
built_packages = self.repository.packages_built()
|
||||
if built_packages: # speedup a bit
|
||||
if built_packages := self.repository.packages_built(): # speedup a bit
|
||||
build_result = self.repository.process_update(built_packages, packagers)
|
||||
self.on_result(build_result)
|
||||
result.merge(build_result)
|
||||
|
||||
# filter packages which were prebuilt
|
||||
succeeded = {package.base for package in build_result.success}
|
||||
updates = [package for package in updates if package.base not in succeeded]
|
||||
|
||||
builder = Updater.load(self.repository_id, self.configuration, self.repository)
|
||||
|
||||
# ok so for now we split all packages into chunks and process each chunk accordingly
|
||||
|
||||
@@ -21,10 +21,10 @@ import argparse
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.application.handlers.update import Update
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.utils import enum_values, extract_user
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
@@ -48,26 +48,7 @@ class Add(Handler):
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
|
||||
application.on_start()
|
||||
|
||||
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
|
||||
for patch in patches:
|
||||
application.reporter.package_patches_update(package, patch)
|
||||
|
||||
if not args.now:
|
||||
return
|
||||
|
||||
packages = application.updates(args.package, aur=False, local=False, manual=True, vcs=False, check_files=False)
|
||||
if args.changes: # generate changes if requested
|
||||
application.changes(packages)
|
||||
|
||||
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
|
||||
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
|
||||
|
||||
application.print_updates(packages, log_fn=application.logger.info)
|
||||
result = application.update(packages, packagers, bump_pkgrel=args.increment)
|
||||
Add.check_status(args.exit_code, not result.is_empty)
|
||||
Add.perform_action(application, args)
|
||||
|
||||
@staticmethod
|
||||
def _set_package_add_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
@@ -103,14 +84,34 @@ class Add(Handler):
|
||||
parser.add_argument("--increment", help="increment package release (pkgrel) version on duplicate",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-n", "--now", help="run update function after", action="store_true")
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
"-yy to force refresh even if up to date",
|
||||
action="count", default=False)
|
||||
parser.add_argument("-s", "--source", help="explicitly specify the package source for this command",
|
||||
type=PackageSource, choices=enum_values(PackageSource), default=PackageSource.Auto)
|
||||
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
|
||||
parser.add_argument("-v", "--variable", help="apply specified makepkg variables to the next build",
|
||||
action="append")
|
||||
parser.add_argument("-y", "--refresh", help="download fresh package databases from the mirror before actions, "
|
||||
"-yy to force refresh even if up to date",
|
||||
action="count", default=False)
|
||||
parser.set_defaults(aur=False, check_files=False, dry_run=False, local=False, manual=True, vcs=False)
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def perform_action(application: Application, args: argparse.Namespace) -> None:
|
||||
"""
|
||||
perform add action
|
||||
|
||||
Args:
|
||||
application(Application): application instance
|
||||
args(argparse.Namespace): command line args
|
||||
"""
|
||||
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
|
||||
for patch in patches:
|
||||
application.reporter.package_patches_update(package, patch)
|
||||
|
||||
if not args.now:
|
||||
return
|
||||
Update.perform_action(application, args)
|
||||
|
||||
arguments = [_set_package_add_parser]
|
||||
|
||||
131
src/ahriman/application/handlers/rollback.py
Normal file
131
src/ahriman/application/handlers/rollback.py
Normal file
@@ -0,0 +1,131 @@
|
||||
#
|
||||
# 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 dataclasses import replace
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.add import Add
|
||||
from ahriman.application.handlers.handler import Handler, SubParserAction
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.utils import extract_user
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
class Rollback(Handler):
|
||||
"""
|
||||
package rollback handler
|
||||
"""
|
||||
|
||||
@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=report)
|
||||
application.on_start()
|
||||
|
||||
package = Rollback.package_load(application, args.package, args.version)
|
||||
artifacts = Rollback.package_artifacts(application, package)
|
||||
|
||||
args.package = [str(artifact) for artifact in artifacts]
|
||||
Add.perform_action(application, args)
|
||||
|
||||
if args.hold:
|
||||
application.reporter.package_hold_update(package.base, enabled=True)
|
||||
|
||||
@staticmethod
|
||||
def _set_package_rollback_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
"""
|
||||
add parser for package rollback subcommand
|
||||
|
||||
Args:
|
||||
root(SubParserAction): subparsers for the commands
|
||||
|
||||
Returns:
|
||||
argparse.ArgumentParser: created argument parser
|
||||
"""
|
||||
parser = root.add_parser("package-rollback", help="rollback package",
|
||||
description="rollback package to specified version from archives")
|
||||
parser.add_argument("package", help="package base")
|
||||
parser.add_argument("version", help="package version")
|
||||
parser.add_argument("--hold", help="hold package afterwards",
|
||||
action=argparse.BooleanOptionalAction, default=True)
|
||||
parser.add_argument("-u", "--username", help="build as user", default=extract_user())
|
||||
parser.set_defaults(aur=False, changes=False, check_files=False, dependencies=False, dry_run=False,
|
||||
exit_code=True, increment=False, now=True, local=False, manual=False, refresh=False,
|
||||
source=PackageSource.Archive, variable=None, vcs=False)
|
||||
return parser
|
||||
|
||||
@staticmethod
|
||||
def package_artifacts(application: Application, package: Package) -> list[Path]:
|
||||
"""
|
||||
look for requested package artifacts and return paths to them
|
||||
|
||||
Args:
|
||||
application(Application): application instance
|
||||
package(Package): package descriptor
|
||||
|
||||
Returns:
|
||||
list[Path]: paths to found artifacts
|
||||
|
||||
Raises:
|
||||
UnknownPackageError: if artifacts do not exist
|
||||
"""
|
||||
# lookup for built artifacts
|
||||
artifacts = application.repository.package_archives_lookup(package)
|
||||
if not artifacts:
|
||||
raise UnknownPackageError(package.base)
|
||||
return artifacts
|
||||
|
||||
@staticmethod
|
||||
def package_load(application: Application, package_base: str, version: str) -> Package:
|
||||
"""
|
||||
load package from repository, while setting requested version
|
||||
|
||||
Args:
|
||||
application(Application): application instance
|
||||
package_base(str): package base
|
||||
version(str): package version
|
||||
|
||||
Returns:
|
||||
Package: loaded package
|
||||
|
||||
Raises:
|
||||
UnknownPackageError: if package does not exist
|
||||
"""
|
||||
try:
|
||||
package, _ = next(iter(application.reporter.package_get(package_base)))
|
||||
return replace(package, version=version)
|
||||
except StopIteration:
|
||||
raise UnknownPackageError(package_base) from None
|
||||
|
||||
arguments = [_set_package_rollback_parser]
|
||||
@@ -48,22 +48,7 @@ class Update(Handler):
|
||||
"""
|
||||
application = Application(repository_id, configuration, report=report, refresh_pacman_database=args.refresh)
|
||||
application.on_start()
|
||||
|
||||
packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
|
||||
check_files=args.check_files)
|
||||
if args.changes: # generate changes if requested
|
||||
application.changes(packages)
|
||||
|
||||
if args.dry_run: # exit from application if no build requested
|
||||
Update.check_status(args.exit_code, packages) # status code check
|
||||
return
|
||||
|
||||
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
|
||||
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
|
||||
|
||||
application.print_updates(packages, log_fn=application.logger.info)
|
||||
result = application.update(packages, packagers, bump_pkgrel=args.increment)
|
||||
Update.check_status(args.exit_code, not result.is_empty)
|
||||
Update.perform_action(application, args)
|
||||
|
||||
@staticmethod
|
||||
def _set_repo_check_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
@@ -153,6 +138,31 @@ class Update(Handler):
|
||||
return print(line) if dry_run else application.logger.info(line) # pylint: disable=bad-builtin
|
||||
return inner
|
||||
|
||||
@staticmethod
|
||||
def perform_action(application: Application, args: argparse.Namespace) -> None:
|
||||
"""
|
||||
perform update action
|
||||
|
||||
Args:
|
||||
application(Application): application instance
|
||||
args(argparse.Namespace): command line args
|
||||
"""
|
||||
packages = application.updates(args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs,
|
||||
check_files=args.check_files)
|
||||
if args.changes: # generate changes if requested
|
||||
application.changes(packages)
|
||||
|
||||
if args.dry_run: # exit from application if no build requested
|
||||
Update.check_status(args.exit_code, packages) # status code check
|
||||
return
|
||||
|
||||
packages = application.with_dependencies(packages, process_dependencies=args.dependencies)
|
||||
packagers = Packagers(args.username, {package.base: package.packager for package in packages})
|
||||
|
||||
application.print_updates(packages, log_fn=application.logger.info)
|
||||
result = application.update(packages, packagers, bump_pkgrel=args.increment)
|
||||
Update.check_status(args.exit_code, not result.is_empty)
|
||||
|
||||
arguments = [
|
||||
_set_repo_check_parser,
|
||||
_set_repo_update_parser,
|
||||
|
||||
@@ -25,7 +25,6 @@ from pathlib import Path
|
||||
from tempfile import TemporaryDirectory
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.build_tools.package_version import PackageVersion
|
||||
from ahriman.core.build_tools.sources import Sources
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log import LazyLogging
|
||||
@@ -89,19 +88,21 @@ class PackageInfo(LazyLogging):
|
||||
|
||||
return sorted(result)
|
||||
|
||||
def load_archives(self, packages: Iterable[Path]) -> list[Package]:
|
||||
def load_archives(self, packages: Iterable[Path], *, latest_only: bool = True) -> list[Package]:
|
||||
"""
|
||||
load packages from list of archives
|
||||
|
||||
Args:
|
||||
packages(Iterable[Path]): paths to package archives
|
||||
latest_only(bool, optional): filter packages with the same base, keeping only fresh packages installed
|
||||
(Default value = True)
|
||||
|
||||
Returns:
|
||||
list[Package]: list of read packages
|
||||
"""
|
||||
sources = {package.base: package.remote for package, _, in self.reporter.package_get(None)}
|
||||
|
||||
result: dict[str, Package] = {}
|
||||
result: dict[str, dict[str, Package]] = {}
|
||||
# we are iterating over bases, not single packages
|
||||
for full_path in packages:
|
||||
try:
|
||||
@@ -109,17 +110,23 @@ class PackageInfo(LazyLogging):
|
||||
if (source := sources.get(local.base)) is not None: # update source with remote
|
||||
local.remote = source
|
||||
|
||||
current = result.setdefault(local.base, local)
|
||||
if current.version != local.version:
|
||||
# force version to max of them
|
||||
self.logger.warning("version of %s differs, found %s and %s",
|
||||
current.base, current.version, local.version)
|
||||
if PackageVersion(current).is_outdated(local, self.configuration, calculate_version=False):
|
||||
current.version = local.version
|
||||
loaded_versions = result.setdefault(local.base, {})
|
||||
current = loaded_versions.setdefault(local.version, local)
|
||||
current.packages.update(local.packages)
|
||||
except Exception:
|
||||
self.logger.exception("could not load package from %s", full_path)
|
||||
return list(result.values())
|
||||
|
||||
if latest_only:
|
||||
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
||||
for package_base, versions in result.items():
|
||||
newest = max(versions.values(), key=cmp_to_key(comparator))
|
||||
result[package_base] = {newest.version: newest}
|
||||
|
||||
return [
|
||||
package
|
||||
for versions in result.values()
|
||||
for package in versions.values()
|
||||
]
|
||||
|
||||
def package_archives(self, package_base: str) -> list[Package]:
|
||||
"""
|
||||
@@ -133,16 +140,17 @@ class PackageInfo(LazyLogging):
|
||||
Returns:
|
||||
list[Package]: list of packages belonging to this base, sorted by version by ascension
|
||||
"""
|
||||
packages: dict[tuple[str, str], Package] = {}
|
||||
# we can't use here load_archives, because it ignores versions
|
||||
for full_path in filter(package_like, self.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)
|
||||
archive = self.paths.archive_for(package_base)
|
||||
if not archive.is_dir():
|
||||
return []
|
||||
|
||||
packages = self.load_archives(filter(package_like, archive.iterdir()), latest_only=False)
|
||||
|
||||
comparator: Callable[[Package, Package], int] = lambda left, right: left.vercmp(right.version)
|
||||
return sorted(packages.values(), key=cmp_to_key(comparator))
|
||||
return sorted(
|
||||
(package for package in packages if package.supports_architecture(self.repository_id.architecture)),
|
||||
key=cmp_to_key(comparator),
|
||||
)
|
||||
|
||||
def package_archives_lookup(self, package: Package) -> list[Path]:
|
||||
"""
|
||||
@@ -155,19 +163,11 @@ class PackageInfo(LazyLogging):
|
||||
list[Path]: list of built packages and signatures if available, empty list otherwise
|
||||
"""
|
||||
archive = self.paths.archive_for(package.base)
|
||||
if not archive.is_dir():
|
||||
return []
|
||||
|
||||
for path in filter(package_like, archive.iterdir()):
|
||||
# check if package version is the same
|
||||
built = Package.from_archive(path)
|
||||
for built in self.package_archives(package.base):
|
||||
if built.version != package.version:
|
||||
continue
|
||||
|
||||
# all packages must be either any or same architecture
|
||||
if not built.supports_architecture(self.repository_id.architecture):
|
||||
continue
|
||||
|
||||
return list_flatmap(built.packages.values(), lambda single: archive.glob(f"{single.filename}*"))
|
||||
|
||||
return []
|
||||
|
||||
@@ -232,6 +232,27 @@ class Spawn(Thread, LazyLogging):
|
||||
"""
|
||||
return self._spawn_process(repository_id, "package-remove", *packages)
|
||||
|
||||
def packages_rollback(self, repository_id: RepositoryId, package: str, version: str, username: str | None, *,
|
||||
hold: bool) -> str:
|
||||
"""
|
||||
rollback package
|
||||
|
||||
Args:
|
||||
repository_id(RepositoryId): repository unique identifier
|
||||
package(str): package base to rollback
|
||||
version(str): package version to rollback
|
||||
username(str | None): optional override of username for build process
|
||||
hold(bool): hold package after rollback
|
||||
|
||||
Returns:
|
||||
str: spawned process identifier
|
||||
"""
|
||||
kwargs = {
|
||||
"username": username,
|
||||
self.boolean_action_argument("hold", hold): "",
|
||||
}
|
||||
return self._spawn_process(repository_id, "package-rollback", package, version, **kwargs)
|
||||
|
||||
def packages_update(self, repository_id: RepositoryId, username: str | None, *,
|
||||
aur: bool, local: bool, manual: bool, increment: bool, refresh: bool) -> str:
|
||||
"""
|
||||
|
||||
@@ -48,6 +48,7 @@ from ahriman.web.schemas.package_properties_schema import PackagePropertiesSchem
|
||||
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.packager_schema import PackagerSchema
|
||||
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
|
||||
@@ -58,6 +59,7 @@ from ahriman.web.schemas.process_schema import ProcessSchema
|
||||
from ahriman.web.schemas.remote_schema import RemoteSchema
|
||||
from ahriman.web.schemas.repository_id_schema import RepositoryIdSchema
|
||||
from ahriman.web.schemas.repository_stats_schema import RepositoryStatsSchema
|
||||
from ahriman.web.schemas.rollback_schema import RollbackSchema
|
||||
from ahriman.web.schemas.search_schema import SearchSchema
|
||||
from ahriman.web.schemas.status_schema import StatusSchema
|
||||
from ahriman.web.schemas.update_flags_schema import UpdateFlagsSchema
|
||||
|
||||
@@ -17,10 +17,11 @@
|
||||
# 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 ahriman.web.apispec import Schema, fields
|
||||
from ahriman.web.apispec import fields
|
||||
from ahriman.web.schemas.packager_schema import PackagerSchema
|
||||
|
||||
|
||||
class BuildOptionsSchema(Schema):
|
||||
class BuildOptionsSchema(PackagerSchema):
|
||||
"""
|
||||
request build options schema
|
||||
"""
|
||||
@@ -28,9 +29,6 @@ class BuildOptionsSchema(Schema):
|
||||
increment = fields.Boolean(dump_default=True, metadata={
|
||||
"description": "Increment pkgrel on conflicts",
|
||||
})
|
||||
packager = fields.String(metadata={
|
||||
"description": "Packager identity if applicable",
|
||||
})
|
||||
refresh = fields.Boolean(dump_default=True, metadata={
|
||||
"description": "Refresh pacman database"
|
||||
})
|
||||
|
||||
30
src/ahriman/web/schemas/packager_schema.py
Normal file
30
src/ahriman/web/schemas/packager_schema.py
Normal file
@@ -0,0 +1,30 @@
|
||||
#
|
||||
# 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 ahriman.web.apispec import Schema, fields
|
||||
|
||||
|
||||
class PackagerSchema(Schema):
|
||||
"""
|
||||
request packager schema
|
||||
"""
|
||||
|
||||
packager = fields.String(metadata={
|
||||
"description": "Packager identity if applicable",
|
||||
})
|
||||
40
src/ahriman/web/schemas/rollback_schema.py
Normal file
40
src/ahriman/web/schemas/rollback_schema.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#
|
||||
# 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 ahriman import __version__
|
||||
from ahriman.web.apispec import fields
|
||||
from ahriman.web.schemas.packager_schema import PackagerSchema
|
||||
|
||||
|
||||
class RollbackSchema(PackagerSchema):
|
||||
"""
|
||||
request schema for package rollback
|
||||
"""
|
||||
|
||||
hold = fields.Boolean(dump_default=True, metadata={
|
||||
"description": "Hold package after rollback",
|
||||
})
|
||||
package = fields.String(required=True, metadata={
|
||||
"description": "Package name",
|
||||
"example": "ahriman",
|
||||
})
|
||||
version = fields.String(required=True, metadata={
|
||||
"description": "Package version",
|
||||
"example": __version__,
|
||||
})
|
||||
@@ -227,7 +227,7 @@ class BaseView(View, CorsViewMixin):
|
||||
extract repository from request
|
||||
|
||||
Returns:
|
||||
RepositoryIde: repository if possible to construct and first one otherwise
|
||||
RepositoryId: repository if possible to construct and first one otherwise
|
||||
"""
|
||||
architecture = self.request.query.get("architecture")
|
||||
name = self.request.query.get("repository")
|
||||
|
||||
@@ -44,7 +44,6 @@ class AddView(BaseView):
|
||||
description="Add new package(s) from AUR",
|
||||
permission=POST_PERMISSION,
|
||||
error_400_enabled=True,
|
||||
error_404_description="Repository is unknown",
|
||||
schema=ProcessIdSchema,
|
||||
query_schema=RepositoryIdSchema,
|
||||
body_schema=PackagePatchSchema,
|
||||
|
||||
@@ -43,7 +43,6 @@ class RebuildView(BaseView):
|
||||
description="Rebuild packages which depend on specified one",
|
||||
permission=POST_PERMISSION,
|
||||
error_400_enabled=True,
|
||||
error_404_description="Repository is unknown",
|
||||
schema=ProcessIdSchema,
|
||||
query_schema=RepositoryIdSchema,
|
||||
body_schema=PackageNamesSchema,
|
||||
|
||||
@@ -43,7 +43,6 @@ class RemoveView(BaseView):
|
||||
description="Remove specified packages from the repository",
|
||||
permission=POST_PERMISSION,
|
||||
error_400_enabled=True,
|
||||
error_404_description="Repository is unknown",
|
||||
schema=ProcessIdSchema,
|
||||
query_schema=RepositoryIdSchema,
|
||||
body_schema=PackageNamesSchema,
|
||||
|
||||
@@ -44,7 +44,6 @@ class RequestView(BaseView):
|
||||
description="Request new package(s) to be added from AUR",
|
||||
permission=POST_PERMISSION,
|
||||
error_400_enabled=True,
|
||||
error_404_description="Repository is unknown",
|
||||
schema=ProcessIdSchema,
|
||||
query_schema=RepositoryIdSchema,
|
||||
body_schema=PackagePatchSchema,
|
||||
|
||||
77
src/ahriman/web/views/v1/service/rollback.py
Normal file
77
src/ahriman/web/views/v1/service/rollback.py
Normal file
@@ -0,0 +1,77 @@
|
||||
#
|
||||
# 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 HTTPBadRequest, Response
|
||||
from typing import ClassVar
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.apispec.decorators import apidocs
|
||||
from ahriman.web.schemas import ProcessIdSchema, RepositoryIdSchema, RollbackSchema
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
class RollbackView(BaseView):
|
||||
"""
|
||||
package rollback web view
|
||||
|
||||
Attributes:
|
||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
||||
"""
|
||||
|
||||
POST_PERMISSION: ClassVar[UserAccess] = UserAccess.Full
|
||||
ROUTES = ["/api/v1/service/rollback"]
|
||||
|
||||
@apidocs(
|
||||
tags=["Actions"],
|
||||
summary="Rollback package",
|
||||
description="Rollback package to specified version",
|
||||
permission=POST_PERMISSION,
|
||||
error_400_enabled=True,
|
||||
schema=ProcessIdSchema,
|
||||
query_schema=RepositoryIdSchema,
|
||||
body_schema=RollbackSchema,
|
||||
)
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
run package rollback
|
||||
|
||||
Returns:
|
||||
Response: 200 with spawned process id
|
||||
|
||||
Raises:
|
||||
HTTPBadRequest: if bad data is supplied
|
||||
"""
|
||||
try:
|
||||
data = await self.request.json()
|
||||
package = self.get_non_empty(lambda key: data[key], "package")
|
||||
version = self.get_non_empty(lambda key: data[key], "version")
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
repository_id = self.repository_id()
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_rollback(
|
||||
repository_id,
|
||||
package,
|
||||
version,
|
||||
username,
|
||||
hold=data.get("hold", True),
|
||||
)
|
||||
|
||||
return self.json_response({"process_id": process_id})
|
||||
@@ -43,14 +43,13 @@ class UpdateView(BaseView):
|
||||
description="Run repository update process",
|
||||
permission=POST_PERMISSION,
|
||||
error_400_enabled=True,
|
||||
error_404_description="Repository is unknown",
|
||||
schema=ProcessIdSchema,
|
||||
query_schema=RepositoryIdSchema,
|
||||
body_schema=UpdateFlagsSchema,
|
||||
)
|
||||
async def post(self) -> Response:
|
||||
"""
|
||||
run repository update. No parameters supported here
|
||||
run repository update
|
||||
|
||||
Returns:
|
||||
Response: 200 with spawned process id
|
||||
|
||||
@@ -118,7 +118,6 @@ class UploadView(BaseView):
|
||||
permission=POST_PERMISSION,
|
||||
response_code=HTTPCreated,
|
||||
error_400_enabled=True,
|
||||
error_404_description="Repository is unknown",
|
||||
query_schema=RepositoryIdSchema,
|
||||
body_schema=FileSchema,
|
||||
body_location="form",
|
||||
|
||||
@@ -104,6 +104,7 @@ def _create_watcher(path: Path, repository_id: RepositoryId) -> Watcher:
|
||||
package_info = PackageInfo()
|
||||
package_info.configuration = configuration
|
||||
package_info.paths = configuration.repository_paths
|
||||
package_info.reporter = client
|
||||
package_info.repository_id = repository_id
|
||||
|
||||
return Watcher(client, package_info)
|
||||
|
||||
@@ -190,13 +190,14 @@ def test_update(application_repository: ApplicationRepository, package_ahriman:
|
||||
"""
|
||||
paths = [package.filepath for package in package_ahriman.packages.values()]
|
||||
tree = Tree([Leaf(package_ahriman)])
|
||||
prebuilt_result = Result()
|
||||
|
||||
resolve_mock = mocker.patch("ahriman.application.application.workers.local_updater.LocalUpdater.partition",
|
||||
return_value=tree.levels())
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=paths)
|
||||
build_mock = mocker.patch("ahriman.application.application.workers.local_updater.LocalUpdater.update",
|
||||
return_value=result)
|
||||
update_mock = mocker.patch("ahriman.core.repository.Repository.process_update", return_value=result)
|
||||
update_mock = mocker.patch("ahriman.core.repository.Repository.process_update", return_value=prebuilt_result)
|
||||
on_result_mock = mocker.patch(
|
||||
"ahriman.application.application.application_repository.ApplicationRepository.on_result")
|
||||
|
||||
@@ -204,7 +205,24 @@ def test_update(application_repository: ApplicationRepository, package_ahriman:
|
||||
resolve_mock.assert_called_once_with([package_ahriman])
|
||||
build_mock.assert_called_once_with([package_ahriman], Packagers("username"), bump_pkgrel=True)
|
||||
update_mock.assert_called_once_with(paths, Packagers("username"))
|
||||
on_result_mock.assert_has_calls([MockCall(result), MockCall(result)])
|
||||
on_result_mock.assert_has_calls([MockCall(prebuilt_result), MockCall(result)])
|
||||
|
||||
|
||||
def test_update_prebuilt_filter(application_repository: ApplicationRepository, package_ahriman: Package, result: Result,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must filter out packages which were successfully prebuilt
|
||||
"""
|
||||
paths = [package.filepath for package in package_ahriman.packages.values()]
|
||||
|
||||
resolve_mock = mocker.patch("ahriman.application.application.workers.local_updater.LocalUpdater.partition",
|
||||
return_value=[])
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=paths)
|
||||
mocker.patch("ahriman.core.repository.Repository.process_update", return_value=result)
|
||||
mocker.patch("ahriman.application.application.application_repository.ApplicationRepository.on_result")
|
||||
|
||||
application_repository.update([package_ahriman], Packagers("username"), bump_pkgrel=True)
|
||||
resolve_mock.assert_called_once_with([])
|
||||
|
||||
|
||||
def test_update_empty(application_repository: ApplicationRepository, package_ahriman: Package, result: Result,
|
||||
|
||||
@@ -3,14 +3,12 @@ import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.add import Add
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.packagers import Packagers
|
||||
from ahriman.models.pkgbuild_patch import PkgbuildPatch
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
@@ -24,13 +22,9 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
argparse.Namespace: generated arguments for these test cases
|
||||
"""
|
||||
args.package = ["ahriman"]
|
||||
args.changes = True
|
||||
args.exit_code = False
|
||||
args.increment = True
|
||||
args.now = False
|
||||
args.refresh = 0
|
||||
args.source = PackageSource.Auto
|
||||
args.dependencies = True
|
||||
args.username = "username"
|
||||
args.variable = None
|
||||
return args
|
||||
@@ -43,103 +37,49 @@ 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.application.application.Application.add")
|
||||
dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies")
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
perform_mock = mocker.patch("ahriman.application.handlers.add.Add.perform_action")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Add.run(args, repository_id, configuration, report=False)
|
||||
application_mock.assert_called_once_with(args.package, args.source, args.username)
|
||||
dependencies_mock.assert_not_called()
|
||||
on_start_mock.assert_called_once_with()
|
||||
perform_mock.assert_called_once_with(pytest.helpers.anyvar(int), args)
|
||||
|
||||
|
||||
def test_run_with_patches(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
mocker: MockerFixture) -> None:
|
||||
def test_perform_action(args: argparse.Namespace, application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command and insert temporary patches
|
||||
must perform add action
|
||||
"""
|
||||
args = _default_args(args)
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.add")
|
||||
update_mock = mocker.patch("ahriman.application.handlers.update.Update.perform_action")
|
||||
|
||||
Add.perform_action(application, args)
|
||||
application_mock.assert_called_once_with(args.package, args.source, args.username)
|
||||
update_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_perform_action_with_patches(args: argparse.Namespace, application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must perform add action and insert temporary patches
|
||||
"""
|
||||
args = _default_args(args)
|
||||
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.status.local_client.LocalClient.package_patches_update")
|
||||
patches_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"))
|
||||
Add.perform_action(application, args)
|
||||
patches_mock.assert_called_once_with(args.package[0], PkgbuildPatch("KEY", "VALUE"))
|
||||
|
||||
|
||||
def test_run_with_updates(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_perform_action_with_updates(args: argparse.Namespace, application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command with updates after
|
||||
must perform add action with updates after
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.now = True
|
||||
result = Result()
|
||||
result.add_updated(package_ahriman)
|
||||
mocker.patch("ahriman.application.application.Application.add")
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
|
||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
|
||||
dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies",
|
||||
return_value=[package_ahriman])
|
||||
print_mock = mocker.patch("ahriman.application.application.Application.print_updates")
|
||||
update_mock = mocker.patch("ahriman.application.handlers.update.Update.perform_action")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Add.run(args, repository_id, configuration, report=False)
|
||||
updates_mock.assert_called_once_with(args.package,
|
||||
aur=False, local=False, manual=True, vcs=False, check_files=False)
|
||||
changes_mock.assert_called_once_with([package_ahriman])
|
||||
application_mock.assert_called_once_with([package_ahriman],
|
||||
Packagers(args.username, {package_ahriman.base: "packager"}),
|
||||
bump_pkgrel=args.increment)
|
||||
dependencies_mock.assert_called_once_with([package_ahriman], process_dependencies=args.dependencies)
|
||||
check_mock.assert_called_once_with(False, True)
|
||||
print_mock.assert_called_once_with([package_ahriman], log_fn=pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
def test_run_no_changes(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip changes calculation during package addition
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.now = True
|
||||
args.changes = False
|
||||
mocker.patch("ahriman.application.application.Application.add")
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.application.application.Application.update")
|
||||
mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
mocker.patch("ahriman.application.application.Application.updates")
|
||||
mocker.patch("ahriman.application.application.Application.with_dependencies")
|
||||
mocker.patch("ahriman.application.application.Application.print_updates")
|
||||
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Add.run(args, repository_id, configuration, report=False)
|
||||
changes_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise ExitCode exception on empty result
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.now = True
|
||||
args.exit_code = True
|
||||
mocker.patch("ahriman.application.application.Application.add")
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
|
||||
mocker.patch("ahriman.application.application.Application.with_dependencies")
|
||||
mocker.patch("ahriman.application.application.Application.updates")
|
||||
mocker.patch("ahriman.application.application.Application.print_updates")
|
||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Add.run(args, repository_id, configuration, report=False)
|
||||
check_mock.assert_called_once_with(True, False)
|
||||
Add.perform_action(application, args)
|
||||
update_mock.assert_called_once_with(application, args)
|
||||
|
||||
113
tests/ahriman/application/handlers/test_handler_rollback.py
Normal file
113
tests/ahriman/application/handlers/test_handler_rollback.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import argparse
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.rollback import Rollback
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import UnknownPackageError
|
||||
from ahriman.core.repository import Repository
|
||||
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.package = "ahriman"
|
||||
args.version = "1.0.0-1"
|
||||
args.hold = False
|
||||
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)
|
||||
artifacts = [package.filepath for package in package_ahriman.packages.values()]
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
load_mock = mocker.patch("ahriman.application.handlers.rollback.Rollback.package_load",
|
||||
return_value=package_ahriman)
|
||||
artifacts_mock = mocker.patch("ahriman.application.handlers.rollback.Rollback.package_artifacts",
|
||||
return_value=artifacts)
|
||||
perform_mock = mocker.patch("ahriman.application.handlers.add.Add.perform_action")
|
||||
hold_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_hold_update")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Rollback.run(args, repository_id, configuration, report=False)
|
||||
on_start_mock.assert_called_once_with()
|
||||
load_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman.base, args.version)
|
||||
artifacts_mock.assert_called_once_with(pytest.helpers.anyvar(int), package_ahriman)
|
||||
perform_mock.assert_called_once_with(pytest.helpers.anyvar(int), args)
|
||||
hold_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_run_hold(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must hold package after rollback
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.hold = True
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.application.application.Application.on_start")
|
||||
mocker.patch("ahriman.application.handlers.rollback.Rollback.package_load", return_value=package_ahriman)
|
||||
mocker.patch("ahriman.application.handlers.rollback.Rollback.package_artifacts", return_value=[])
|
||||
mocker.patch("ahriman.application.handlers.add.Add.perform_action")
|
||||
hold_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_hold_update")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Rollback.run(args, repository_id, configuration, report=False)
|
||||
hold_mock.assert_called_once_with(package_ahriman.base, enabled=True)
|
||||
|
||||
|
||||
def test_package_artifacts(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return package artifacts
|
||||
"""
|
||||
artifacts = [package.filepath for package in package_ahriman.packages.values()]
|
||||
lookup_mock = mocker.patch("ahriman.core.repository.Repository.package_archives_lookup", return_value=artifacts)
|
||||
|
||||
assert Rollback.package_artifacts(application, package_ahriman) == artifacts
|
||||
lookup_mock.assert_called_once_with(package_ahriman)
|
||||
|
||||
|
||||
def test_package_artifacts_empty(application: Application, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise UnknownPackageError if no artifacts found
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.Repository.package_archives_lookup", return_value=[])
|
||||
with pytest.raises(UnknownPackageError):
|
||||
Rollback.package_artifacts(application, package_ahriman)
|
||||
|
||||
|
||||
def test_package_load(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load package from reporter
|
||||
"""
|
||||
package_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_get",
|
||||
return_value=[(package_ahriman, None)])
|
||||
|
||||
result = Rollback.package_load(application, package_ahriman.base, "2.0.0-1")
|
||||
assert result.version == "2.0.0-1"
|
||||
package_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
||||
|
||||
def test_package_load_unknown(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise UnknownPackageError if package not found
|
||||
"""
|
||||
mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[])
|
||||
with pytest.raises(UnknownPackageError):
|
||||
Rollback.package_load(application, package_ahriman.base, package_ahriman.version)
|
||||
@@ -39,26 +39,39 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
return args
|
||||
|
||||
|
||||
def test_run(args: argparse.Namespace, package_ahriman: Package, configuration: Configuration, repository: Repository,
|
||||
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
perform_mock = mocker.patch("ahriman.application.handlers.update.Update.perform_action")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Update.run(args, repository_id, configuration, report=False)
|
||||
on_start_mock.assert_called_once_with()
|
||||
perform_mock.assert_called_once_with(pytest.helpers.anyvar(int), args)
|
||||
|
||||
|
||||
def test_perform_action(args: argparse.Namespace, application: Application, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must perform update action
|
||||
"""
|
||||
args = _default_args(args)
|
||||
result = Result()
|
||||
result.add_updated(package_ahriman)
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update", return_value=result)
|
||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
dependencies_mock = mocker.patch("ahriman.application.application.Application.with_dependencies",
|
||||
return_value=[package_ahriman])
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
|
||||
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
||||
on_start_mock = mocker.patch("ahriman.application.application.Application.on_start")
|
||||
print_mock = mocker.patch("ahriman.application.application.Application.print_updates")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Update.run(args, repository_id, configuration, report=False)
|
||||
Update.perform_action(application, args)
|
||||
application_mock.assert_called_once_with([package_ahriman],
|
||||
Packagers(args.username, {package_ahriman.base: "packager"}),
|
||||
bump_pkgrel=args.increment)
|
||||
@@ -67,35 +80,31 @@ def test_run(args: argparse.Namespace, package_ahriman: Package, configuration:
|
||||
changes_mock.assert_called_once_with([package_ahriman])
|
||||
dependencies_mock.assert_called_once_with([package_ahriman], process_dependencies=args.dependencies)
|
||||
check_mock.assert_called_once_with(False, True)
|
||||
on_start_mock.assert_called_once_with()
|
||||
print_mock.assert_called_once_with([package_ahriman], log_fn=pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
mocker: MockerFixture) -> None:
|
||||
def test_perform_action_empty_exception(args: argparse.Namespace, application: Application,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise ExitCode exception on empty update list
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.exit_code = True
|
||||
args.dry_run = True
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.application.application.Application.updates", return_value=[])
|
||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Update.run(args, repository_id, configuration, report=False)
|
||||
Update.perform_action(application, args)
|
||||
check_mock.assert_called_once_with(True, [])
|
||||
|
||||
|
||||
def test_run_update_empty_exception(args: argparse.Namespace, package_ahriman: Package, configuration: Configuration,
|
||||
repository: Repository, mocker: MockerFixture) -> None:
|
||||
def test_perform_action_update_empty_exception(args: argparse.Namespace, application: Application,
|
||||
package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise ExitCode exception on empty build result
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.exit_code = True
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.application.application.Application.update", return_value=Result())
|
||||
mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.application.application.Application.with_dependencies", return_value=[package_ahriman])
|
||||
@@ -103,26 +112,23 @@ def test_run_update_empty_exception(args: argparse.Namespace, package_ahriman: P
|
||||
mocker.patch("ahriman.application.application.Application.changes")
|
||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Update.run(args, repository_id, configuration, report=False)
|
||||
Update.perform_action(application, args)
|
||||
check_mock.assert_called_once_with(True, False)
|
||||
|
||||
|
||||
def test_run_dry_run(args: argparse.Namespace, package_ahriman: Package, configuration: Configuration,
|
||||
repository: Repository, mocker: MockerFixture) -> None:
|
||||
def test_perform_action_dry_run(args: argparse.Namespace, application: Application, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run simplified command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.dry_run = True
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
application_mock = mocker.patch("ahriman.application.application.Application.update")
|
||||
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
updates_mock = mocker.patch("ahriman.application.application.Application.updates", return_value=[package_ahriman])
|
||||
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Update.run(args, repository_id, configuration, report=False)
|
||||
Update.perform_action(application, args)
|
||||
updates_mock.assert_called_once_with(
|
||||
args.package, aur=args.aur, local=args.local, manual=args.manual, vcs=args.vcs, check_files=args.check_files)
|
||||
application_mock.assert_not_called()
|
||||
@@ -130,22 +136,19 @@ def test_run_dry_run(args: argparse.Namespace, package_ahriman: Package, configu
|
||||
check_mock.assert_called_once_with(False, [package_ahriman])
|
||||
|
||||
|
||||
def test_run_no_changes(args: argparse.Namespace, configuration: Configuration, repository: Repository,
|
||||
mocker: MockerFixture) -> None:
|
||||
def test_perform_action_no_changes(args: argparse.Namespace, application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip changes calculation
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.dry_run = True
|
||||
args.changes = False
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.application.application.Application.update")
|
||||
mocker.patch("ahriman.application.handlers.handler.Handler.check_status")
|
||||
mocker.patch("ahriman.application.application.Application.updates")
|
||||
changes_mock = mocker.patch("ahriman.application.application.Application.changes")
|
||||
|
||||
_, repository_id = configuration.check_loaded()
|
||||
Update.run(args, repository_id, configuration, report=False)
|
||||
Update.perform_action(application, args)
|
||||
changes_mock.assert_not_called()
|
||||
|
||||
|
||||
|
||||
@@ -271,6 +271,18 @@ def test_subparsers_package_add_option_variable_multiple(parser: argparse.Argume
|
||||
assert args.variable == ["var1", "var2"]
|
||||
|
||||
|
||||
def test_subparsers_package_add_repo_update(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-add must have same keys as repo-update
|
||||
"""
|
||||
args = parser.parse_args(["package-add", "ahriman"])
|
||||
reference_args = parser.parse_args(["repo-update"])
|
||||
del args.now
|
||||
del args.source
|
||||
del args.variable
|
||||
assert dir(args) == dir(reference_args)
|
||||
|
||||
|
||||
def test_subparsers_package_archives(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-archives command must imply action, exit code, info, lock, quiet, report and unsafe
|
||||
@@ -325,6 +337,26 @@ def test_subparsers_package_changes_remove_package_changes(parser: argparse.Argu
|
||||
assert dir(args) == dir(reference_args)
|
||||
|
||||
|
||||
def test_subparsers_package_copy_option_architecture(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-copy command must correctly parse architecture list
|
||||
"""
|
||||
args = parser.parse_args(["package-copy", "source", "ahriman"])
|
||||
assert args.architecture is None
|
||||
args = parser.parse_args(["-a", "x86_64", "package-copy", "source", "ahriman"])
|
||||
assert args.architecture == "x86_64"
|
||||
|
||||
|
||||
def test_subparsers_package_copy_option_repository(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-copy command must correctly parse repository list
|
||||
"""
|
||||
args = parser.parse_args(["package-copy", "source", "ahriman"])
|
||||
assert args.repository is None
|
||||
args = parser.parse_args(["-r", "repo", "package-copy", "source", "ahriman"])
|
||||
assert args.repository == "repo"
|
||||
|
||||
|
||||
def test_subparsers_package_pkgbuild(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-pkgbuild command must imply action, exit code, lock, quiet, report and unsafe
|
||||
@@ -363,26 +395,6 @@ def test_subparsers_package_pkgbuild_remove_package_pkgbuild(parser: argparse.Ar
|
||||
assert dir(args) == dir(reference_args)
|
||||
|
||||
|
||||
def test_subparsers_package_copy_option_architecture(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-copy command must correctly parse architecture list
|
||||
"""
|
||||
args = parser.parse_args(["package-copy", "source", "ahriman"])
|
||||
assert args.architecture is None
|
||||
args = parser.parse_args(["-a", "x86_64", "package-copy", "source", "ahriman"])
|
||||
assert args.architecture == "x86_64"
|
||||
|
||||
|
||||
def test_subparsers_package_copy_option_repository(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-copy command must correctly parse repository list
|
||||
"""
|
||||
args = parser.parse_args(["package-copy", "source", "ahriman"])
|
||||
assert args.repository is None
|
||||
args = parser.parse_args(["-r", "repo", "package-copy", "source", "ahriman"])
|
||||
assert args.repository == "repo"
|
||||
|
||||
|
||||
def test_subparsers_package_remove_option_architecture(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-remove command must correctly parse architecture list
|
||||
@@ -403,6 +415,68 @@ def test_subparsers_package_remove_option_repository(parser: argparse.ArgumentPa
|
||||
assert args.repository == "repo"
|
||||
|
||||
|
||||
def test_subparsers_package_rollback(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-rollback command must imply aur, changes, check-files, dependencies, dry-run, exit-code, increment, now,
|
||||
local, manual, refresh, source, variable and vcs
|
||||
"""
|
||||
args = parser.parse_args(["package-rollback", "ahriman", "1.0.0-1"])
|
||||
assert not args.aur
|
||||
assert not args.changes
|
||||
assert not args.check_files
|
||||
assert not args.dependencies
|
||||
assert not args.dry_run
|
||||
assert args.exit_code
|
||||
assert not args.increment
|
||||
assert not args.local
|
||||
assert not args.manual
|
||||
assert args.now
|
||||
assert not args.refresh
|
||||
assert not args.vcs
|
||||
assert args.variable is None
|
||||
|
||||
|
||||
def test_subparsers_package_rollback_option_architecture(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-rollback command must correctly parse architecture list
|
||||
"""
|
||||
args = parser.parse_args(["package-rollback", "ahriman", "1.0.0-1"])
|
||||
assert args.architecture is None
|
||||
args = parser.parse_args(["-a", "x86_64", "package-rollback", "ahriman", "1.0.0-1"])
|
||||
assert args.architecture == "x86_64"
|
||||
|
||||
|
||||
def test_subparsers_package_rollback_option_repository(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-rollback command must correctly parse repository list
|
||||
"""
|
||||
args = parser.parse_args(["package-rollback", "ahriman", "1.0.0-1"])
|
||||
assert args.repository is None
|
||||
args = parser.parse_args(["-r", "repo", "package-rollback", "ahriman", "1.0.0-1"])
|
||||
assert args.repository == "repo"
|
||||
|
||||
|
||||
def test_subparsers_package_rollback_option_hold(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-rollback command must correctly parse hold option
|
||||
"""
|
||||
args = parser.parse_args(["package-rollback", "ahriman", "1.0.0-1"])
|
||||
assert args.hold
|
||||
args = parser.parse_args(["package-rollback", "ahriman", "1.0.0-1", "--no-hold"])
|
||||
assert not args.hold
|
||||
|
||||
|
||||
def test_subparsers_package_rollback_package_add(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-rollback must have same keys as package-add
|
||||
"""
|
||||
args = parser.parse_args(["package-rollback", "ahriman", "1.0.0-1"])
|
||||
reference_args = parser.parse_args(["package-add", "ahriman"])
|
||||
del args.hold
|
||||
del args.version
|
||||
assert dir(args) == dir(reference_args)
|
||||
|
||||
|
||||
def test_subparsers_package_status(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
package-status command must imply lock, quiet, report and unsafe
|
||||
|
||||
@@ -8,7 +8,6 @@ from unittest.mock import MagicMock
|
||||
from ahriman.core.repository import Repository
|
||||
from ahriman.models.changes import Changes
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
|
||||
|
||||
def test_full_depends(repository: Repository, package_ahriman: Package, package_python_schedule: Package,
|
||||
@@ -93,18 +92,41 @@ def test_load_archives_different_version(repository: Repository, package_python_
|
||||
assert packages[0].version == package_python_schedule.version
|
||||
|
||||
|
||||
def test_load_archives_all_versions(repository: Repository, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load packages with different versions keeping all when latest_only is False
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package.from_archive",
|
||||
side_effect=[package_ahriman, replace(package_ahriman, version="0.0.1-1")])
|
||||
mocker.patch("ahriman.core.status.local_client.LocalClient.package_get", return_value=[])
|
||||
|
||||
packages = repository.load_archives([Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz")], latest_only=False)
|
||||
assert len(packages) == 2
|
||||
|
||||
|
||||
def test_package_archives(repository: Repository, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load package archives sorted by version
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.package_info.package_like", return_value=True)
|
||||
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))
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.iterdir")
|
||||
load_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives",
|
||||
return_value=[replace(package_ahriman, version=str(i)) for i in range(5)])
|
||||
|
||||
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)]
|
||||
load_mock.assert_called_once_with(pytest.helpers.anyvar(int), latest_only=False)
|
||||
|
||||
|
||||
def test_package_archives_no_directory(repository: Repository, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty list if archive directory does not exist
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
assert repository.package_archives(package_ahriman.base) == []
|
||||
|
||||
|
||||
def test_package_archives_architecture_mismatch(repository: Repository, package_ahriman: Package,
|
||||
@@ -114,8 +136,10 @@ def test_package_archives_architecture_mismatch(repository: Repository, package_
|
||||
"""
|
||||
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)
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.iterdir")
|
||||
mocker.patch("ahriman.core.repository.package_info.PackageInfo.load_archives",
|
||||
return_value=[package_ahriman])
|
||||
|
||||
result = repository.package_archives(package_ahriman.base)
|
||||
assert len(result) == 0
|
||||
@@ -126,13 +150,7 @@ def test_package_archives_lookup(repository: Repository, package_ahriman: Packag
|
||||
"""
|
||||
must existing packages which match the version
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
Path("2.pkg.tar.zst"),
|
||||
Path("3.pkg.tar.zst"),
|
||||
])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive", side_effect=[
|
||||
archives_mock = mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives", return_value=[
|
||||
package_ahriman,
|
||||
package_python_schedule,
|
||||
replace(package_ahriman, version="1"),
|
||||
@@ -140,6 +158,7 @@ def test_package_archives_lookup(repository: Repository, package_ahriman: Packag
|
||||
glob_mock = mocker.patch("pathlib.Path.glob", return_value=[Path("1.pkg.tar.xz")])
|
||||
|
||||
assert repository.package_archives_lookup(package_ahriman) == [Path("1.pkg.tar.xz")]
|
||||
archives_mock.assert_called_once_with(package_ahriman.base)
|
||||
glob_mock.assert_called_once_with(f"{package_ahriman.packages[package_ahriman.base].filename}*")
|
||||
|
||||
|
||||
@@ -148,12 +167,8 @@ def test_package_archives_lookup_version_mismatch(repository: Repository, packag
|
||||
"""
|
||||
must return nothing if no packages found with the same version
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive", return_value=replace(package_ahriman, version="1"))
|
||||
|
||||
mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives",
|
||||
return_value=[replace(package_ahriman, version="1")])
|
||||
assert repository.package_archives_lookup(package_ahriman) == []
|
||||
|
||||
|
||||
@@ -162,14 +177,7 @@ def test_package_archives_lookup_architecture_mismatch(repository: Repository, p
|
||||
"""
|
||||
must return nothing if architecture doesn't match
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].architecture = "x86_64"
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=True)
|
||||
repository.repository_id = RepositoryId("i686", repository.repository_id.name)
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[
|
||||
Path("1.pkg.tar.zst"),
|
||||
])
|
||||
mocker.patch("ahriman.models.package.Package.from_archive", return_value=package_ahriman)
|
||||
|
||||
mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives", return_value=[])
|
||||
assert repository.package_archives_lookup(package_ahriman) == []
|
||||
|
||||
|
||||
@@ -178,7 +186,7 @@ def test_package_archives_lookup_no_archive_directory(repository: Repository, pa
|
||||
"""
|
||||
must return nothing if no archive directory found
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_dir", return_value=False)
|
||||
mocker.patch("ahriman.core.repository.package_info.PackageInfo.package_archives", return_value=[])
|
||||
assert repository.package_archives_lookup(package_ahriman) == []
|
||||
|
||||
|
||||
|
||||
@@ -196,6 +196,26 @@ def test_packages_remove(spawner: Spawn, repository_id: RepositoryId, mocker: Mo
|
||||
spawn_mock.assert_called_once_with(repository_id, "package-remove", "ahriman", "linux")
|
||||
|
||||
|
||||
def test_packages_rollback(spawner: Spawn, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call package rollback
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
|
||||
assert spawner.packages_rollback(repository_id, "ahriman", "1.0.0-1", "packager", hold=False)
|
||||
spawn_mock.assert_called_once_with(repository_id, "package-rollback", "ahriman", "1.0.0-1",
|
||||
**{"username": "packager", "no-hold": ""})
|
||||
|
||||
|
||||
def test_packages_rollback_with_hold(spawner: Spawn, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call package rollback with hold
|
||||
"""
|
||||
spawn_mock = mocker.patch("ahriman.core.spawn.Spawn._spawn_process")
|
||||
assert spawner.packages_rollback(repository_id, "ahriman", "1.0.0-1", "packager", hold=True)
|
||||
spawn_mock.assert_called_once_with(repository_id, "package-rollback", "ahriman", "1.0.0-1",
|
||||
**{"username": "packager", "hold": ""})
|
||||
|
||||
|
||||
def test_packages_update(spawner: Spawn, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call repo update
|
||||
|
||||
1
tests/ahriman/web/schemas/test_packager_schema.py
Normal file
1
tests/ahriman/web/schemas/test_packager_schema.py
Normal file
@@ -0,0 +1 @@
|
||||
# schema testing goes in view class tests
|
||||
1
tests/ahriman/web/schemas/test_rollback_schema.py
Normal file
1
tests/ahriman/web/schemas/test_rollback_schema.py
Normal file
@@ -0,0 +1 @@
|
||||
# schema testing goes in view class tests
|
||||
@@ -0,0 +1,70 @@
|
||||
import pytest
|
||||
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.repository_id import RepositoryId
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1.service.rollback import RollbackView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
"""
|
||||
must return correct permission for the request
|
||||
"""
|
||||
for method in ("POST",):
|
||||
request = pytest.helpers.request("", "", method)
|
||||
assert await RollbackView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert RollbackView.ROUTES == ["/api/v1/service/rollback"]
|
||||
|
||||
|
||||
async def test_post(client: TestClient, repository_id: RepositoryId, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly
|
||||
"""
|
||||
rollback_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_rollback", return_value="abc")
|
||||
user_mock = AsyncMock()
|
||||
user_mock.return_value = "username"
|
||||
mocker.patch("ahriman.web.views.base.BaseView.username", side_effect=user_mock)
|
||||
request_schema = pytest.helpers.schema_request(RollbackView.post)
|
||||
response_schema = pytest.helpers.schema_response(RollbackView.post)
|
||||
|
||||
payload = {"package": "ahriman", "version": "version"}
|
||||
assert not request_schema.validate(payload)
|
||||
response = await client.post("/api/v1/service/rollback", json=payload)
|
||||
assert response.ok
|
||||
rollback_mock.assert_called_once_with(repository_id, "ahriman", "version", "username", hold=True)
|
||||
|
||||
json = await response.json()
|
||||
assert json["process_id"] == "abc"
|
||||
assert not response_schema.validate(json)
|
||||
|
||||
|
||||
async def test_post_empty(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call raise 400 on empty request
|
||||
"""
|
||||
rollback_mock = mocker.patch("ahriman.core.spawn.Spawn.packages_rollback")
|
||||
response_schema = pytest.helpers.schema_response(RollbackView.post, code=400)
|
||||
|
||||
response = await client.post("/api/v1/service/rollback", json={"package": "", "version": "version"})
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
||||
rollback_mock.assert_not_called()
|
||||
|
||||
response = await client.post("/api/v1/service/rollback", json={"package": "ahriman", "version": ""})
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
||||
rollback_mock.assert_not_called()
|
||||
|
||||
response = await client.post("/api/v1/service/rollback", json={})
|
||||
assert response.status == 400
|
||||
assert not response_schema.validate(await response.json())
|
||||
rollback_mock.assert_not_called()
|
||||
Reference in New Issue
Block a user