Add tests (#1) (#5)

* add models tests (#1)

also replace single quote to double one to confort PEP docstring
+ move _check_output to class properties to make it available for
mocking

* alpm tests implementation

* try to replace os with pathlib

* update tests for pathlib

* fix includes glob and trim version from dependencies

* build_tools package tests

* repository component tests

* add sign tests

* complete status tests

* handle exceptions in actual_version calls

* complete core tests

* move configuration to root conftest

* application tests

* complete application tests

* change copyright to more generic one

* base web tests

* complete web tests

* complete testkit

also add argument parsers test
This commit is contained in:
2021-03-28 15:30:51 +03:00
committed by GitHub
parent 69499b2d0a
commit 60b8477cde
139 changed files with 4606 additions and 1124 deletions

View File

@ -0,0 +1,30 @@
import argparse
import pytest
from pytest_mock import MockerFixture
from ahriman.application.ahriman import _parser
from ahriman.application.application import Application
from ahriman.application.lock import Lock
from ahriman.core.configuration import Configuration
@pytest.fixture
def application(configuration: Configuration, mocker: MockerFixture) -> Application:
mocker.patch("pathlib.Path.mkdir")
return Application("x86_64", configuration)
@pytest.fixture
def args() -> argparse.Namespace:
return argparse.Namespace(lock=None, force=False, unsafe=False, no_report=True)
@pytest.fixture
def lock(args: argparse.Namespace, configuration: Configuration) -> Lock:
return Lock(args, "x86_64", configuration)
@pytest.fixture
def parser() -> argparse.ArgumentParser:
return _parser()

View File

@ -0,0 +1,27 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Handler
from ahriman.core.configuration import Configuration
def test_call(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must call inside lock
"""
mocker.patch("ahriman.application.handlers.Handler.run")
enter_mock = mocker.patch("ahriman.application.lock.Lock.__enter__")
exit_mock = mocker.patch("ahriman.application.lock.Lock.__exit__")
assert Handler._call(args, "x86_64", configuration)
enter_mock.assert_called_once()
exit_mock.assert_called_once()
def test_call_exception(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must process exception
"""
mocker.patch("ahriman.application.lock.Lock.__enter__", side_effect=Exception())
assert not Handler._call(args, "x86_64", configuration)

View File

@ -0,0 +1,19 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Add
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args.package = []
args.without_dependencies = False
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.add")
Add.run(args, "x86_64", configuration)
application_mock.assert_called_once()

View File

@ -0,0 +1,22 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Clean
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args.no_build = False
args.no_cache = False
args.no_chroot = False
args.no_manual = False
args.no_packages = False
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.clean")
Clean.run(args, "x86_64", configuration)
application_mock.assert_called_once()

View File

@ -0,0 +1,17 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Dump
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.core.configuration.Configuration.dump")
Dump.run(args, "x86_64", configuration)
application_mock.assert_called_once()

View File

@ -0,0 +1,19 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Rebuild
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
mocker.patch("pathlib.Path.mkdir")
application_packages_mock = mocker.patch("ahriman.core.repository.repository.Repository.packages")
application_mock = mocker.patch("ahriman.application.application.Application.update")
Rebuild.run(args, "x86_64", configuration)
application_packages_mock.assert_called_once()
application_mock.assert_called_once()

View File

@ -0,0 +1,18 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Remove
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args.package = []
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.remove")
Remove.run(args, "x86_64", configuration)
application_mock.assert_called_once()

View File

@ -0,0 +1,18 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Report
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args.target = []
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.report")
Report.run(args, "x86_64", configuration)
application_mock.assert_called_once()

View File

@ -0,0 +1,22 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Status
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args.ahriman = True
args.package = []
args.without_dependencies = False
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.core.status.client.Client.get_self")
packages_mock = mocker.patch("ahriman.core.status.client.Client.get")
Status.run(args, "x86_64", configuration)
application_mock.assert_called_once()
packages_mock.assert_called_once()

View File

@ -0,0 +1,18 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Sync
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args.target = []
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.sync")
Sync.run(args, "x86_64", configuration)
application_mock.assert_called_once()

View File

@ -0,0 +1,40 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Update
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
args.package = []
args.dry_run = False
args.no_aur = False
args.no_manual = False
args.no_vcs = False
mocker.patch("pathlib.Path.mkdir")
application_mock = mocker.patch("ahriman.application.application.Application.update")
updates_mock = mocker.patch("ahriman.application.application.Application.get_updates")
Update.run(args, "x86_64", configuration)
application_mock.assert_called_once()
updates_mock.assert_called_once()
def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run simplified command
"""
args.package = []
args.dry_run = True
args.no_aur = False
args.no_manual = False
args.no_vcs = False
mocker.patch("pathlib.Path.mkdir")
updates_mock = mocker.patch("ahriman.application.application.Application.get_updates")
Update.run(args, "x86_64", configuration)
updates_mock.assert_called_once()

View File

@ -0,0 +1,19 @@
import argparse
from pytest_mock import MockerFixture
from ahriman.application.handlers import Web
from ahriman.core.configuration import Configuration
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
"""
must run command
"""
mocker.patch("pathlib.Path.mkdir")
setup_mock = mocker.patch("ahriman.web.web.setup_service")
run_mock = mocker.patch("ahriman.web.web.run_server")
Web.run(args, "x86_64", configuration)
setup_mock.assert_called_once()
run_mock.assert_called_once()

View File

@ -0,0 +1,55 @@
import argparse
def test_parser(parser: argparse.ArgumentParser) -> None:
"""
must parse valid command line
"""
parser.parse_args(["-a", "x86_64", "config"])
def test_multiple_architectures(parser: argparse.ArgumentParser) -> None:
"""
must accept multiple architectures
"""
args = parser.parse_args(["-a", "x86_64", "-a", "i686", "config"])
assert len(args.architecture) == 2
def test_subparsers_check(parser: argparse.ArgumentParser) -> None:
"""
check command must imply no_aur, no_manual and dry_run
"""
args = parser.parse_args(["-a", "x86_64", "check"])
assert not args.no_aur
assert args.no_manual
assert args.dry_run
def test_subparsers_config(parser: argparse.ArgumentParser) -> None:
"""
config command must imply lock, no_report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "config"])
assert args.lock is None
assert args.no_report
assert args.unsafe
def test_subparsers_status(parser: argparse.ArgumentParser) -> None:
"""
status command must imply lock, no_report and unsafe
"""
args = parser.parse_args(["-a", "x86_64", "status"])
assert args.lock is None
assert args.no_report
assert args.unsafe
def test_subparsers_web(parser: argparse.ArgumentParser) -> None:
"""
web command must imply lock and no_report
"""
args = parser.parse_args(["-a", "x86_64", "web"])
assert args.lock is None
assert args.no_report

View File

@ -0,0 +1,237 @@
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.application.application import Application
from ahriman.core.tree import Leaf, Tree
from ahriman.models.package import Package
def test_finalize(application: Application, mocker: MockerFixture) -> None:
"""
must report and sync at the last
"""
report_mock = mocker.patch("ahriman.application.application.Application.report")
sync_mock = mocker.patch("ahriman.application.application.Application.sync")
application._finalize()
report_mock.assert_called_once()
sync_mock.assert_called_once()
def test_get_updates_all(application: Application, mocker: MockerFixture) -> None:
"""
must get updates for all
"""
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
application.get_updates([], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
updates_aur_mock.assert_called_with([], False)
updates_manual_mock.assert_called_once()
def test_get_updates_disabled(application: Application, mocker: MockerFixture) -> None:
"""
must get updates without anything
"""
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
application.get_updates([], no_aur=True, no_manual=True, no_vcs=False, log_fn=print)
updates_aur_mock.assert_not_called()
updates_manual_mock.assert_not_called()
def test_get_updates_no_aur(application: Application, mocker: MockerFixture) -> None:
"""
must get updates without aur
"""
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
application.get_updates([], no_aur=True, no_manual=False, no_vcs=False, log_fn=print)
updates_aur_mock.assert_not_called()
updates_manual_mock.assert_called_once()
def test_get_updates_no_manual(application: Application, mocker: MockerFixture) -> None:
"""
must get updates without manual
"""
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
application.get_updates([], no_aur=False, no_manual=True, no_vcs=False, log_fn=print)
updates_aur_mock.assert_called_with([], False)
updates_manual_mock.assert_not_called()
def test_get_updates_no_vcs(application: Application, mocker: MockerFixture) -> None:
"""
must get updates without VCS
"""
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
application.get_updates([], no_aur=False, no_manual=False, no_vcs=True, log_fn=print)
updates_aur_mock.assert_called_with([], True)
updates_manual_mock.assert_called_once()
def test_get_updates_with_filter(application: Application, mocker: MockerFixture) -> None:
"""
must get updates without VCS
"""
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
application.get_updates(["filter"], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
updates_aur_mock.assert_called_with(["filter"], False)
updates_manual_mock.assert_called_once()
def test_add_directory(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add packages from directory
"""
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
mocker.patch("pathlib.Path.is_dir", return_value=True)
iterdir_mock = mocker.patch("pathlib.Path.iterdir",
return_value=[package.filepath for package in package_ahriman.packages.values()])
move_mock = mocker.patch("shutil.move")
application.add([package_ahriman.base], False)
iterdir_mock.assert_called_once()
move_mock.assert_called_once()
def test_add_manual(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add package from AUR
"""
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
fetch_mock = mocker.patch("ahriman.core.build_tools.task.Task.fetch")
application.add([package_ahriman.base], True)
fetch_mock.assert_called_once()
def test_add_manual_with_dependencies(application: Application, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must add package from AUR with dependencies
"""
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies")
application.add([package_ahriman.base], False)
dependencies_mock.assert_called_once()
def test_add_package(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add package from archive
"""
mocker.patch("ahriman.application.application.Application._known_packages", return_value=set())
mocker.patch("pathlib.Path.is_file", return_value=True)
move_mock = mocker.patch("shutil.move")
application.add([package_ahriman.base], False)
move_mock.assert_called_once()
def test_clean_build(application: Application, mocker: MockerFixture) -> None:
"""
must clean build directory
"""
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_build")
application.clean(False, True, True, True, True)
clear_mock.assert_called_once()
def test_clean_cache(application: Application, mocker: MockerFixture) -> None:
"""
must clean cache directory
"""
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_cache")
application.clean(True, False, True, True, True)
clear_mock.assert_called_once()
def test_clean_chroot(application: Application, mocker: MockerFixture) -> None:
"""
must clean chroot directory
"""
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_chroot")
application.clean(True, True, False, True, True)
clear_mock.assert_called_once()
def test_clean_manual(application: Application, mocker: MockerFixture) -> None:
"""
must clean manual directory
"""
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual")
application.clean(True, True, True, False, True)
clear_mock.assert_called_once()
def test_clean_packages(application: Application, mocker: MockerFixture) -> None:
"""
must clean packages directory
"""
clear_mock = mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages")
application.clean(True, True, True, True, False)
clear_mock.assert_called_once()
def test_remove(application: Application, mocker: MockerFixture) -> None:
"""
must remove package
"""
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_remove")
finalize_mock = mocker.patch("ahriman.application.application.Application._finalize")
application.remove([])
executor_mock.assert_called_once()
finalize_mock.assert_called_once()
def test_report(application: Application, mocker: MockerFixture) -> None:
"""
must generate report
"""
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_report")
application.report(None)
executor_mock.assert_called_once()
def test_sync(application: Application, mocker: MockerFixture) -> None:
"""
must sync to remote
"""
executor_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_sync")
application.sync(None)
executor_mock.assert_called_once()
def test_update(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package updates
"""
paths = [package.filepath for package in package_ahriman.packages.values()]
tree = Tree([Leaf(package_ahriman, set())])
mocker.patch("ahriman.core.tree.Tree.load", return_value=tree)
mocker.patch("ahriman.core.repository.repository.Repository.packages_built", return_value=[])
build_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_build", return_value=paths)
update_mock = mocker.patch("ahriman.core.repository.executor.Executor.process_update")
finalize_mock = mocker.patch("ahriman.application.application.Application._finalize")
application.update([package_ahriman])
build_mock.assert_called_once()
update_mock.assert_has_calls([mock.call([]), mock.call(paths)])
finalize_mock.assert_has_calls([mock.call(), mock.call()])

View File

@ -0,0 +1,151 @@
import pytest
import tempfile
from pathlib import Path
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.application.lock import Lock
from ahriman.core.exceptions import DuplicateRun, UnsafeRun
from ahriman.models.build_status import BuildStatusEnum
def test_enter(lock: Lock, mocker: MockerFixture) -> None:
"""
must process with context manager
"""
check_user_mock = mocker.patch("ahriman.application.lock.Lock.check_user")
remove_mock = mocker.patch("ahriman.application.lock.Lock.remove")
create_mock = mocker.patch("ahriman.application.lock.Lock.create")
update_status_mock = mocker.patch("ahriman.core.status.client.Client.update_self")
with lock:
pass
check_user_mock.assert_called_once()
remove_mock.assert_called_once()
create_mock.assert_called_once()
update_status_mock.assert_has_calls([
mock.call(BuildStatusEnum.Building),
mock.call(BuildStatusEnum.Success)
])
def test_exit_with_exception(lock: Lock, mocker: MockerFixture) -> None:
"""
must process with context manager in case if exception raised
"""
mocker.patch("ahriman.application.lock.Lock.check_user")
mocker.patch("ahriman.application.lock.Lock.remove")
mocker.patch("ahriman.application.lock.Lock.create")
update_status_mock = mocker.patch("ahriman.core.status.client.Client.update_self")
with pytest.raises(Exception):
with lock:
raise Exception()
update_status_mock.assert_has_calls([
mock.call(BuildStatusEnum.Building),
mock.call(BuildStatusEnum.Failed)
])
def test_check_user(lock: Lock, mocker: MockerFixture) -> None:
"""
must check user correctly
"""
stat = Path.cwd().stat()
mocker.patch("pathlib.Path.stat", return_value=stat)
mocker.patch("os.getuid", return_value=stat.st_uid)
lock.check_user()
def test_check_user_exception(lock: Lock, mocker: MockerFixture) -> None:
"""
must raise exception if user differs
"""
stat = Path.cwd().stat()
mocker.patch("pathlib.Path.stat")
mocker.patch("os.getuid", return_value=stat.st_uid + 1)
with pytest.raises(UnsafeRun):
lock.check_user()
def test_check_user_unsafe(lock: Lock) -> None:
"""
must skip user check if unsafe flag set
"""
lock.unsafe = True
lock.check_user()
def test_create(lock: Lock) -> None:
"""
must create lock
"""
lock.path = Path(tempfile.mktemp())
lock.create()
assert lock.path.is_file()
lock.path.unlink()
def test_create_exception(lock: Lock) -> None:
"""
must raise exception if file already exists
"""
lock.path = Path(tempfile.mktemp())
lock.path.touch()
with pytest.raises(DuplicateRun):
lock.create()
lock.path.unlink()
def test_create_skip(lock: Lock, mocker: MockerFixture) -> None:
"""
must skip creating if no file set
"""
touch_mock = mocker.patch("pathlib.Path.touch")
lock.create()
touch_mock.assert_not_called()
def test_create_unsafe(lock: Lock) -> None:
"""
must not raise exception if force flag set
"""
lock.force = True
lock.path = Path(tempfile.mktemp())
lock.path.touch()
lock.create()
lock.path.unlink()
def test_remove(lock: Lock) -> None:
"""
must remove lock file
"""
lock.path = Path(tempfile.mktemp())
lock.path.touch()
lock.remove()
assert not lock.path.is_file()
def test_remove_missing(lock: Lock) -> None:
"""
must not fail on lock removal if file is missing
"""
lock.path = Path(tempfile.mktemp())
lock.remove()
def test_remove_skip(lock: Lock, mocker: MockerFixture) -> None:
"""
must skip removal if no file set
"""
unlink_mock = mocker.patch("pathlib.Path.unlink")
lock.remove()
unlink_mock.assert_not_called()

95
tests/ahriman/conftest.py Normal file
View File

@ -0,0 +1,95 @@
import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from typing import Any, Type, TypeVar
from ahriman.core.configuration import Configuration
from ahriman.core.status.watcher import Watcher
from ahriman.models.package import Package
from ahriman.models.package_desciption import PackageDescription
from ahriman.models.repository_paths import RepositoryPaths
T = TypeVar("T")
# helpers
# https://stackoverflow.com/a/21611963
@pytest.helpers.register
def anyvar(cls: Type[T], strict: bool = False) -> T:
class AnyVar(cls):
def __eq__(self, other: Any) -> bool:
return not strict or isinstance(other, cls)
return AnyVar()
# generic fixtures
@pytest.fixture
def configuration(resource_path_root: Path) -> Configuration:
path = resource_path_root / "core" / "ahriman.ini"
return Configuration.from_path(path=path, logfile=False)
@pytest.fixture
def package_ahriman(package_description_ahriman: PackageDescription) -> Package:
packages = {"ahriman": package_description_ahriman}
return Package(
base="ahriman",
version="0.12.1-1",
aur_url="https://aur.archlinux.org",
packages=packages)
@pytest.fixture
def package_python_schedule(
package_description_python_schedule: PackageDescription,
package_description_python2_schedule: PackageDescription) -> Package:
packages = {
"python-schedule": package_description_python_schedule,
"python2-schedule": package_description_python2_schedule
}
return Package(
base="python-schedule",
version="1.0.0-2",
aur_url="https://aur.archlinux.org",
packages=packages)
@pytest.fixture
def package_description_ahriman() -> PackageDescription:
return PackageDescription(
archive_size=4200,
build_date=42,
filename="ahriman-0.12.1-1-any.pkg.tar.zst",
installed_size=4200000)
@pytest.fixture
def package_description_python_schedule() -> PackageDescription:
return PackageDescription(
archive_size=4201,
build_date=421,
filename="python-schedule-1.0.0-2-any.pkg.tar.zst",
installed_size=4200001)
@pytest.fixture
def package_description_python2_schedule() -> PackageDescription:
return PackageDescription(
archive_size=4202,
build_date=422,
filename="python2-schedule-1.0.0-2-any.pkg.tar.zst",
installed_size=4200002)
@pytest.fixture
def repository_paths(configuration: Configuration) -> RepositoryPaths:
return RepositoryPaths(
architecture="x86_64",
root=configuration.getpath("repository", "root"))
@pytest.fixture
def watcher(configuration: Configuration, mocker: MockerFixture) -> Watcher:
mocker.patch("pathlib.Path.mkdir")
return Watcher("x86_64", configuration)

View File

@ -0,0 +1,10 @@
from ahriman.core.alpm.pacman import Pacman
def test_all_packages(pacman: Pacman) -> None:
"""
package list must not be empty
"""
packages = pacman.all_packages()
assert packages
assert "pacman" in packages

View File

@ -0,0 +1,47 @@
import pytest
from pathlib import Path
from pytest_mock import MockerFixture
from ahriman.core.alpm.repo import Repo
def test_repo_path(repo: Repo) -> None:
"""
name must be something like archive name
"""
assert repo.repo_path.name.endswith("db.tar.gz")
def test_repo_add(repo: Repo, mocker: MockerFixture) -> None:
"""
must call repo-add on package addition
"""
check_output_mock = mocker.patch("ahriman.core.alpm.repo.Repo._check_output")
repo.add(Path("path"))
check_output_mock.assert_called_once()
assert check_output_mock.call_args[0][0] == "repo-add"
def test_repo_remove(repo: Repo, mocker: MockerFixture) -> None:
"""
must call repo-remove on package addition
"""
mocker.patch("pathlib.Path.glob", return_value=[])
check_output_mock = mocker.patch("ahriman.core.alpm.repo.Repo._check_output")
repo.remove("package", Path("package.pkg.tar.xz"))
check_output_mock.assert_called_once()
assert check_output_mock.call_args[0][0] == "repo-remove"
def test_repo_remove_fail_no_file(repo: Repo, mocker: MockerFixture) -> None:
"""
must fail on missing file
"""
mocker.patch("pathlib.Path.glob", return_value=[Path("package.pkg.tar.xz")])
mocker.patch("pathlib.Path.unlink", side_effect=FileNotFoundError())
with pytest.raises(FileNotFoundError):
repo.remove("package", Path("package.pkg.tar.xz"))

View File

@ -0,0 +1,64 @@
import pytest
import shutil
from pathlib import Path
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.core.build_tools.task import Task
def test_fetch_existing(mocker: MockerFixture) -> None:
"""
must fetch new package via clone command
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
check_output_mock = mocker.patch("ahriman.core.build_tools.task.Task._check_output")
local = Path("local")
Task.fetch(local, "remote", "master")
check_output_mock.assert_has_calls([
mock.call("git", "fetch", "origin", "master",
exception=pytest.helpers.anyvar(int),
cwd=local, logger=pytest.helpers.anyvar(int)),
mock.call("git", "checkout", "--force", "master",
exception=pytest.helpers.anyvar(int),
cwd=local, logger=pytest.helpers.anyvar(int)),
mock.call("git", "reset", "--hard", "origin/master",
exception=pytest.helpers.anyvar(int),
cwd=local, logger=pytest.helpers.anyvar(int))
])
def test_fetch_new(mocker: MockerFixture) -> None:
"""
must fetch new package via clone command
"""
mocker.patch("pathlib.Path.is_dir", return_value=False)
check_output_mock = mocker.patch("ahriman.core.build_tools.task.Task._check_output")
local = Path("local")
Task.fetch(local, "remote", "master")
check_output_mock.assert_has_calls([
mock.call("git", "clone", "remote", str(local),
exception=pytest.helpers.anyvar(int),
logger=pytest.helpers.anyvar(int)),
mock.call("git", "checkout", "--force", "master",
exception=pytest.helpers.anyvar(int),
cwd=local, logger=pytest.helpers.anyvar(int)),
mock.call("git", "reset", "--hard", "origin/master",
exception=pytest.helpers.anyvar(int),
cwd=local, logger=pytest.helpers.anyvar(int))
])
def test_init_with_cache(task_ahriman: Task, mocker: MockerFixture) -> None:
"""
must copy tree instead of fetch
"""
mocker.patch("pathlib.Path.is_dir", return_value=True)
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
copytree_mock = mocker.patch("shutil.copytree")
task_ahriman.init(None)
copytree_mock.assert_called_once()

View File

@ -0,0 +1,34 @@
import pytest
from ahriman.core.alpm.pacman import Pacman
from ahriman.core.alpm.repo import Repo
from ahriman.core.build_tools.task import Task
from ahriman.core.configuration import Configuration
from ahriman.core.tree import Leaf
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
@pytest.fixture
def leaf_ahriman(package_ahriman: Package) -> Leaf:
return Leaf(package_ahriman, set())
@pytest.fixture
def leaf_python_schedule(package_python_schedule: Package) -> Leaf:
return Leaf(package_python_schedule, set())
@pytest.fixture
def pacman(configuration: Configuration) -> Pacman:
return Pacman(configuration)
@pytest.fixture
def repo(configuration: Configuration, repository_paths: RepositoryPaths) -> Repo:
return Repo(configuration.get("repository", "name"), repository_paths, [])
@pytest.fixture
def task_ahriman(package_ahriman: Package, configuration: Configuration, repository_paths: RepositoryPaths) -> Task:
return Task(package_ahriman, "x86_64", configuration, repository_paths)

View File

View File

View File

@ -0,0 +1,49 @@
import pytest
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.repository.cleaner import Cleaner
from ahriman.core.repository.executor import Executor
from ahriman.core.repository.properties import Properties
from ahriman.core.repository.repository import Repository
from ahriman.core.repository.update_handler import UpdateHandler
@pytest.fixture
def cleaner(configuration: Configuration, mocker: MockerFixture) -> Cleaner:
mocker.patch("pathlib.Path.mkdir")
return Cleaner("x86_64", configuration)
@pytest.fixture
def executor(configuration: Configuration, mocker: MockerFixture) -> Executor:
mocker.patch("pathlib.Path.mkdir")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_build")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_cache")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_chroot")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages")
return Executor("x86_64", configuration)
@pytest.fixture
def repository(configuration: Configuration, mocker: MockerFixture) -> Repository:
mocker.patch("pathlib.Path.mkdir")
return Repository("x86_64", configuration)
@pytest.fixture
def properties(configuration: Configuration) -> Properties:
return Properties("x86_64", configuration)
@pytest.fixture
def update_handler(configuration: Configuration, mocker: MockerFixture) -> UpdateHandler:
mocker.patch("pathlib.Path.mkdir")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_build")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_cache")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_chroot")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_manual")
mocker.patch("ahriman.core.repository.cleaner.Cleaner.clear_packages")
return UpdateHandler("x86_64", configuration)

View File

@ -0,0 +1,68 @@
import shutil
from pathlib import Path
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.core.repository.cleaner import Cleaner
def _mock_clear(mocker: MockerFixture) -> None:
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a"), Path("b"), Path("c")])
mocker.patch("shutil.rmtree")
def _mock_clear_check() -> None:
shutil.rmtree.assert_has_calls([
mock.call(Path("a")),
mock.call(Path("b")),
mock.call(Path("c"))
])
def test_clear_build(cleaner: Cleaner, mocker: MockerFixture) -> None:
"""
must remove directories with sources
"""
_mock_clear(mocker)
cleaner.clear_build()
_mock_clear_check()
def test_clear_cache(cleaner: Cleaner, mocker: MockerFixture) -> None:
"""
must remove every cached sources
"""
_mock_clear(mocker)
cleaner.clear_cache()
_mock_clear_check()
def test_clear_chroot(cleaner: Cleaner, mocker: MockerFixture) -> None:
"""
must clear chroot
"""
_mock_clear(mocker)
cleaner.clear_chroot()
_mock_clear_check()
def test_clear_manual(cleaner: Cleaner, mocker: MockerFixture) -> None:
"""
must clear directory with manual packages
"""
_mock_clear(mocker)
cleaner.clear_manual()
_mock_clear_check()
def test_clear_packages(cleaner: Cleaner, mocker: MockerFixture) -> None:
"""
must delete built packages
"""
mocker.patch("ahriman.core.repository.cleaner.Cleaner.packages_built",
return_value=[Path("a"), Path("b"), Path("c")])
mocker.patch("pathlib.Path.unlink")
cleaner.clear_packages()
Path.unlink.assert_has_calls([mock.call(), mock.call(), mock.call()])

View File

@ -0,0 +1,188 @@
from pathlib import Path
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.core.repository.executor import Executor
from ahriman.models.package import Package
def test_process_build(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must run build process
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages_built", return_value=[package_ahriman])
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
mocker.patch("ahriman.core.build_tools.task.Task.init")
move_mock = mocker.patch("shutil.move")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building")
# must return list of built packages
assert executor.process_build([package_ahriman]) == [package_ahriman]
# must move files (once)
move_mock.assert_called_once()
# must update status
status_client_mock.assert_called_once()
# must clear directory
from ahriman.core.repository.cleaner import Cleaner
Cleaner.clear_build.assert_called_once()
def test_process_build_failure(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must run correct process failed builds
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages_built")
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
mocker.patch("ahriman.core.build_tools.task.Task.init")
mocker.patch("shutil.move", side_effect=Exception())
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed")
executor.process_build([package_ahriman])
status_client_mock.assert_called_once()
def test_process_remove_base(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must run remove process for whole base
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.remove")
executor.process_remove([package_ahriman.base])
# must remove via alpm wrapper
repo_remove_mock.assert_called_once()
# must update status
status_client_mock.assert_called_once()
def test_process_remove_base_multiple(executor: Executor, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must run remove process for whole base with multiple packages
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.remove")
executor.process_remove([package_python_schedule.base])
# must remove via alpm wrapper
repo_remove_mock.assert_has_calls([
mock.call(package, Path(props.filename))
for package, props in package_python_schedule.packages.items()
], any_order=True)
# must update status
status_client_mock.assert_called_once()
def test_process_remove_base_single(executor: Executor, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must run remove process for single package in base
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.remove")
executor.process_remove(["python2-schedule"])
# must remove via alpm wrapper
repo_remove_mock.assert_called_once()
# must not update status
status_client_mock.assert_not_called()
def test_process_remove_nothing(executor: Executor, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must not remove anything if it was not requested
"""
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
executor.process_remove([package_python_schedule.base])
repo_remove_mock.assert_not_called()
def test_process_report_auto(executor: Executor, mocker: MockerFixture) -> None:
"""
must process report in auto mode if no targets supplied
"""
config_getlist_mock = mocker.patch("ahriman.core.configuration.Configuration.getlist")
executor.process_report(None)
config_getlist_mock.assert_called_once()
def test_process_sync_auto(executor: Executor, mocker: MockerFixture) -> None:
"""
must process sync in auto mode if no targets supplied
"""
config_getlist_mock = mocker.patch("ahriman.core.configuration.Configuration.getlist")
executor.process_sync(None)
config_getlist_mock.assert_called_once()
def test_process_update(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must run update process
"""
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
move_mock = mocker.patch("shutil.move")
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
sign_package_mock = mocker.patch("ahriman.core.sign.gpg.GPG.sign_package", side_effect=lambda fn, _: [fn])
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
# must return complete
assert executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()])
# must move files (once)
move_mock.assert_called_once()
# must sign package
sign_package_mock.assert_called_once()
# must add package
repo_add_mock.assert_called_once()
# must update status
status_client_mock.assert_called_once()
# must clear directory
from ahriman.core.repository.cleaner import Cleaner
Cleaner.clear_packages.assert_called_once()
def test_process_update_group(executor: Executor, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must group single packages under one base
"""
mocker.patch("shutil.move")
mocker.patch("ahriman.models.package.Package.load", return_value=package_python_schedule)
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
executor.process_update([Path(package.filename) for package in package_python_schedule.packages.values()])
repo_add_mock.assert_has_calls([
mock.call(executor.paths.repository / package.filename)
for package in package_python_schedule.packages.values()
], any_order=True)
status_client_mock.assert_called_with(package_python_schedule)
def test_process_update_failed(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process update for failed package
"""
mocker.patch("shutil.move", side_effect=Exception())
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed")
executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()])
status_client_mock.assert_called_once()
def test_process_update_failed_on_load(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process update even with failed package load
"""
mocker.patch("shutil.move")
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception())
assert executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()])

View File

@ -0,0 +1,14 @@
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.repository.properties import Properties
def test_create_tree_on_load(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must create tree on load
"""
create_tree_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.create_tree")
Properties("x86_64", configuration)
create_tree_mock.assert_called_once()

View File

@ -0,0 +1,33 @@
from pathlib import Path
from pytest_mock import MockerFixture
from ahriman.core.repository.repository import Repository
from ahriman.models.package import Package
def test_packages(package_ahriman: Package, package_python_schedule: Package,
repository: Repository, mocker: MockerFixture) -> None:
"""
must return all packages grouped by package base
"""
single_packages = [
Package(base=package_python_schedule.base,
version=package_python_schedule.version,
aur_url=package_python_schedule.aur_url,
packages={package: props})
for package, props in package_python_schedule.packages.items()
] + [package_ahriman]
mocker.patch("pathlib.Path.iterdir",
return_value=[Path("a.pkg.tar.xz"), Path("b.pkg.tar.xz"), Path("c.pkg.tar.xz")])
mocker.patch("ahriman.models.package.Package.load", side_effect=single_packages)
packages = repository.packages()
assert len(packages) == 2
assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base}
archives = sum([list(package.packages.keys()) for package in packages], start=[])
assert len(archives) == 3
expected = set(package_ahriman.packages.keys())
expected.update(package_python_schedule.packages.keys())
assert set(archives) == expected

View File

@ -0,0 +1,124 @@
from pytest_mock import MockerFixture
from ahriman.core.repository.update_handler import UpdateHandler
from ahriman.models.package import Package
def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must provide updates with status updates
"""
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending")
assert update_handler.updates_aur([], False) == [package_ahriman]
status_client_mock.assert_called_once()
def test_updates_aur_failed(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must update status via client for failed load
"""
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception())
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed")
update_handler.updates_aur([], False)
status_client_mock.assert_called_once()
def test_updates_aur_filter(update_handler: UpdateHandler, package_ahriman: Package, package_python_schedule: Package,
mocker: MockerFixture) -> None:
"""
must provide updates only for filtered packages
"""
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages",
return_value=[package_ahriman, package_python_schedule])
mocker.patch("ahriman.models.package.Package.is_outdated", return_value=True)
package_load_mock = mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
assert update_handler.updates_aur([package_ahriman.base], False) == [package_ahriman]
package_load_mock.assert_called_once()
def test_updates_aur_ignore(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must skip ignore packages
"""
mocker.patch("ahriman.core.configuration.Configuration.getlist", return_value=[package_ahriman.base])
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
package_load_mock = mocker.patch("ahriman.models.package.Package.load")
update_handler.updates_aur([], False)
package_load_mock.assert_not_called()
def test_updates_aur_ignore_vcs(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must skip VCS packages check if requested
"""
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.is_vcs", return_value=True)
package_is_outdated_mock = mocker.patch("ahriman.models.package.Package.is_outdated")
update_handler.updates_aur([], True)
package_is_outdated_mock.assert_not_called()
def test_updates_manual_clear(update_handler: UpdateHandler, mocker: MockerFixture) -> None:
"""
requesting manual updates must clear packages directory
"""
mocker.patch("pathlib.Path.iterdir", return_value=[])
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages")
update_handler.updates_manual()
from ahriman.core.repository.cleaner import Cleaner
Cleaner.clear_manual.assert_called_once()
def test_updates_manual_status_known(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must create record for known package via reporter
"""
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_pending")
update_handler.updates_manual()
status_client_mock.assert_called_once()
def test_updates_manual_status_unknown(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must create record for unknown package via reporter
"""
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[])
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_unknown")
update_handler.updates_manual()
status_client_mock.assert_called_once()
def test_updates_manual_with_failures(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must process through the packages with failure
"""
mocker.patch("pathlib.Path.iterdir", return_value=[package_ahriman.base])
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[])
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception())
assert update_handler.updates_manual() == []

View File

@ -0,0 +1,9 @@
import pytest
from ahriman.core.configuration import Configuration
from ahriman.core.sign.gpg import GPG
@pytest.fixture
def gpg(configuration: Configuration) -> GPG:
return GPG("x86_64", configuration)

View File

@ -0,0 +1,61 @@
from pathlib import Path
from pytest_mock import MockerFixture
from ahriman.core.sign.gpg import GPG
from ahriman.models.sign_settings import SignSettings
def test_repository_sign_args(gpg: GPG) -> None:
"""
must generate correct sign args
"""
gpg.target = {SignSettings.SignRepository}
assert gpg.repository_sign_args
def test_sign_package(gpg: GPG, mocker: MockerFixture) -> None:
"""
must sign package
"""
result = [Path("a"), Path("a.sig")]
process_mock = mocker.patch("ahriman.core.sign.gpg.process", return_value=result)
for target in ({SignSettings.SignPackages}, {SignSettings.SignPackages, SignSettings.SignRepository}):
gpg.target = target
assert gpg.sign_package(Path("a"), "a") == result
process_mock.assert_called_once()
def test_sign_package_skip(gpg: GPG, mocker: MockerFixture) -> None:
"""
must not sign package if it is not set
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.process")
for target in ({}, {SignSettings.SignRepository}):
gpg.target = target
process_mock.assert_not_called()
def test_sign_repository(gpg: GPG, mocker: MockerFixture) -> None:
"""
must sign repository
"""
result = [Path("a"), Path("a.sig")]
process_mock = mocker.patch("ahriman.core.sign.gpg.process", return_value=result)
for target in ({SignSettings.SignRepository}, {SignSettings.SignPackages, SignSettings.SignRepository}):
gpg.target = target
assert gpg.sign_repository(Path("a")) == result
process_mock.assert_called_once()
def test_sign_repository_skip(gpg: GPG, mocker: MockerFixture) -> None:
"""
must not sign repository if it is not set
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.process")
for target in ({}, {SignSettings.SignPackages}):
gpg.target = target
process_mock.assert_not_called()

View File

@ -0,0 +1,30 @@
import pytest
from typing import Any, Dict
from ahriman.core.status.client import Client
from ahriman.core.status.web_client import WebClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
# helpers
@pytest.helpers.register
def get_package_status(package: Package) -> Dict[str, Any]:
return {"status": BuildStatusEnum.Unknown.value, "package": package.view()}
@pytest.helpers.register
def get_package_status_extended(package: Package) -> Dict[str, Any]:
return {"status": BuildStatus().view(), "package": package.view()}
# fixtures
@pytest.fixture
def client() -> Client:
return Client()
@pytest.fixture
def web_client() -> WebClient:
return WebClient("localhost", 8080)

View File

@ -0,0 +1,116 @@
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.core.status.client import Client
from ahriman.core.status.web_client import WebClient
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package
def test_add(client: Client, package_ahriman: Package) -> None:
"""
must process package addition without errors
"""
client.add(package_ahriman, BuildStatusEnum.Unknown)
def test_get(client: Client, package_ahriman: Package) -> None:
"""
must return empty package list
"""
assert client.get(package_ahriman.base) == []
assert client.get(None) == []
def test_get_self(client: Client) -> None:
"""
must return unknown status for service
"""
assert client.get_self().status == BuildStatusEnum.Unknown
def test_remove(client: Client, package_ahriman: Package) -> None:
"""
must process remove without errors
"""
client.remove(package_ahriman.base)
def test_update(client: Client, package_ahriman: Package) -> None:
"""
must update package status without errors
"""
client.update(package_ahriman.base, BuildStatusEnum.Unknown)
def test_update_self(client: Client) -> None:
"""
must update self status without errors
"""
client.update_self(BuildStatusEnum.Unknown)
def test_set_building(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set building status to the package
"""
update_mock = mocker.patch("ahriman.core.status.client.Client.update")
client.set_building(package_ahriman.base)
update_mock.assert_called_with(package_ahriman.base, BuildStatusEnum.Building)
def test_set_failed(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set failed status to the package
"""
update_mock = mocker.patch("ahriman.core.status.client.Client.update")
client.set_failed(package_ahriman.base)
update_mock.assert_called_with(package_ahriman.base, BuildStatusEnum.Failed)
def test_set_pending(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set building status to the package
"""
update_mock = mocker.patch("ahriman.core.status.client.Client.update")
client.set_pending(package_ahriman.base)
update_mock.assert_called_with(package_ahriman.base, BuildStatusEnum.Pending)
def test_set_success(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set success status to the package
"""
add_mock = mocker.patch("ahriman.core.status.client.Client.add")
client.set_success(package_ahriman)
add_mock.assert_called_with(package_ahriman, BuildStatusEnum.Success)
def test_set_unknown(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add new package with unknown status
"""
add_mock = mocker.patch("ahriman.core.status.client.Client.add")
client.set_unknown(package_ahriman)
add_mock.assert_called_with(package_ahriman, BuildStatusEnum.Unknown)
def test_load_dummy_client(configuration: Configuration) -> None:
"""
must load dummy client if no settings set
"""
assert isinstance(Client.load("x86_64", configuration), Client)
def test_load_full_client(configuration: Configuration) -> None:
"""
must load full client if no settings set
"""
configuration.set("web", "host", "localhost")
configuration.set("web", "port", "8080")
assert isinstance(Client.load("x86_64", configuration), WebClient)

View File

@ -0,0 +1,219 @@
import pytest
import tempfile
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import PropertyMock
from ahriman.core.exceptions import UnknownPackage
from ahriman.core.status.watcher import Watcher
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
def test_cache_load(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must load state from cache
"""
response = {"packages": [pytest.helpers.get_package_status_extended(package_ahriman)]}
mocker.patch("pathlib.Path.is_file", return_value=True)
mocker.patch("pathlib.Path.open")
mocker.patch("json.load", return_value=response)
watcher.known = {package_ahriman.base: (None, None)}
watcher._cache_load()
package, status = watcher.known[package_ahriman.base]
assert package == package_ahriman
assert status.status == BuildStatusEnum.Unknown
def test_cache_load_json_error(watcher: Watcher, mocker: MockerFixture) -> None:
"""
must not fail on json errors
"""
mocker.patch("pathlib.Path.is_file", return_value=True)
mocker.patch("pathlib.Path.open")
mocker.patch("json.load", side_effect=Exception())
watcher._cache_load()
assert not watcher.known
def test_cache_load_no_file(watcher: Watcher, mocker: MockerFixture) -> None:
"""
must not fail on missing file
"""
mocker.patch("pathlib.Path.is_file", return_value=False)
watcher._cache_load()
assert not watcher.known
def test_cache_load_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must not load unknown package
"""
response = {"packages": [pytest.helpers.get_package_status_extended(package_ahriman)]}
mocker.patch("pathlib.Path.is_file", return_value=True)
mocker.patch("pathlib.Path.open")
mocker.patch("json.load", return_value=response)
watcher._cache_load()
assert not watcher.known
def test_cache_save(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must save state to cache
"""
mocker.patch("pathlib.Path.open")
json_mock = mocker.patch("json.dump")
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher._cache_save()
json_mock.assert_called_once()
def test_cache_save_failed(watcher: Watcher, mocker: MockerFixture) -> None:
"""
must not fail on dumping packages
"""
mocker.patch("pathlib.Path.open")
mocker.patch("json.dump", side_effect=Exception())
watcher._cache_save()
def test_cache_save_load(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must save state to cache which can be loaded later
"""
dump_file = Path(tempfile.mktemp())
mocker.patch("ahriman.core.status.watcher.Watcher.cache_path",
new_callable=PropertyMock, return_value=dump_file)
known_current = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.known = known_current
watcher._cache_save()
watcher.known = {package_ahriman.base: (None, None)}
watcher._cache_load()
assert watcher.known == known_current
dump_file.unlink()
def test_get(watcher: Watcher, package_ahriman: Package) -> None:
"""
must return package status
"""
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
package, status = watcher.get(package_ahriman.base)
assert package == package_ahriman
assert status.status == BuildStatusEnum.Unknown
def test_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
"""
must fail on unknown package
"""
with pytest.raises(UnknownPackage):
watcher.get(package_ahriman.base)
def test_load(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must correctly load packages
"""
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
cache_mock = mocker.patch("ahriman.core.status.watcher.Watcher._cache_load")
watcher.load()
cache_mock.assert_called_once()
package, status = watcher.known[package_ahriman.base]
assert package == package_ahriman
assert status.status == BuildStatusEnum.Unknown
def test_load_known(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must correctly load packages with known statuses
"""
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
mocker.patch("ahriman.core.status.watcher.Watcher._cache_load")
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus(BuildStatusEnum.Success))}
watcher.load()
_, status = watcher.known[package_ahriman.base]
assert status.status == BuildStatusEnum.Success
def test_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must remove package base
"""
cache_mock = mocker.patch("ahriman.core.status.watcher.Watcher._cache_save")
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.remove(package_ahriman.base)
assert not watcher.known
cache_mock.assert_called_once()
def test_remove_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must not fail on unknown base removal
"""
cache_mock = mocker.patch("ahriman.core.status.watcher.Watcher._cache_save")
watcher.remove(package_ahriman.base)
cache_mock.assert_called_once()
def test_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package status
"""
cache_mock = mocker.patch("ahriman.core.status.watcher.Watcher._cache_save")
watcher.update(package_ahriman.base, BuildStatusEnum.Unknown, package_ahriman)
cache_mock.assert_called_once()
package, status = watcher.known[package_ahriman.base]
assert package == package_ahriman
assert status.status == BuildStatusEnum.Unknown
def test_update_ping(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package status only for known package
"""
cache_mock = mocker.patch("ahriman.core.status.watcher.Watcher._cache_save")
watcher.known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.update(package_ahriman.base, BuildStatusEnum.Success, None)
cache_mock.assert_called_once()
package, status = watcher.known[package_ahriman.base]
assert package == package_ahriman
assert status.status == BuildStatusEnum.Success
def test_update_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must fail on unknown package status update only
"""
cache_mock = mocker.patch("ahriman.core.status.watcher.Watcher._cache_save")
with pytest.raises(UnknownPackage):
watcher.update(package_ahriman.base, BuildStatusEnum.Unknown, None)
cache_mock.assert_called_once()
def test_update_self(watcher: Watcher) -> None:
"""
must update service status
"""
watcher.update_self(BuildStatusEnum.Success)
assert watcher.status.status == BuildStatusEnum.Success

View File

@ -0,0 +1,163 @@
import json
import pytest
from pytest_mock import MockerFixture
from requests import Response
from ahriman.core.status.web_client import WebClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
def test_ahriman_url(web_client: WebClient) -> None:
"""
must generate service status url correctly
"""
assert web_client._ahriman_url().startswith(f"http://{web_client.host}:{web_client.port}")
assert web_client._ahriman_url().endswith("/api/v1/ahriman")
def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
"""
must generate package status correctly
"""
assert web_client._package_url(package_ahriman.base).startswith(f"http://{web_client.host}:{web_client.port}")
assert web_client._package_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}")
def test_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package addition
"""
requests_mock = mocker.patch("requests.post")
payload = pytest.helpers.get_package_status(package_ahriman)
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
requests_mock.assert_called_with(pytest.helpers.anyvar(str, True), json=payload)
def test_add_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during addition
"""
mocker.patch("requests.post", side_effect=Exception())
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
def test_get_all(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return all packages status
"""
response = [pytest.helpers.get_package_status_extended(package_ahriman)]
response_obj = Response()
response_obj._content = json.dumps(response).encode("utf8")
response_obj.status_code = 200
requests_mock = mocker.patch("requests.get", return_value=response_obj)
result = web_client.get(None)
requests_mock.assert_called_once()
assert len(result) == len(response)
assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
def test_get_failed(web_client: WebClient, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during status getting
"""
mocker.patch("requests.get", side_effect=Exception())
assert web_client.get(None) == []
def test_get_single(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return single package status
"""
response = [pytest.helpers.get_package_status_extended(package_ahriman)]
response_obj = Response()
response_obj._content = json.dumps(response).encode("utf8")
response_obj.status_code = 200
requests_mock = mocker.patch("requests.get", return_value=response_obj)
result = web_client.get(package_ahriman.base)
requests_mock.assert_called_once()
assert len(result) == len(response)
assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
def test_get_self(web_client: WebClient, mocker: MockerFixture) -> None:
"""
must return service status
"""
response_obj = Response()
response_obj._content = json.dumps(BuildStatus().view()).encode("utf8")
response_obj.status_code = 200
requests_mock = mocker.patch("requests.get", return_value=response_obj)
result = web_client.get_self()
requests_mock.assert_called_once()
assert result.status == BuildStatusEnum.Unknown
def test_get_self_failed(web_client: WebClient, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during service status getting
"""
mocker.patch("requests.get", side_effect=Exception())
assert web_client.get_self().status == BuildStatusEnum.Unknown
def test_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package removal
"""
requests_mock = mocker.patch("requests.delete")
web_client.remove(package_ahriman.base)
requests_mock.assert_called_with(pytest.helpers.anyvar(str, True))
def test_remove_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during removal
"""
mocker.patch("requests.delete", side_effect=Exception())
web_client.remove(package_ahriman.base)
def test_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package update
"""
requests_mock = mocker.patch("requests.post")
web_client.update(package_ahriman.base, BuildStatusEnum.Unknown)
requests_mock.assert_called_with(pytest.helpers.anyvar(str, True), json={"status": BuildStatusEnum.Unknown.value})
def test_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during update
"""
mocker.patch("requests.post", side_effect=Exception())
web_client.update(package_ahriman.base, BuildStatusEnum.Unknown)
def test_update_self(web_client: WebClient, mocker: MockerFixture) -> None:
"""
must process service update
"""
requests_mock = mocker.patch("requests.post")
web_client.update_self(BuildStatusEnum.Unknown)
requests_mock.assert_called_with(pytest.helpers.anyvar(str, True), json={"status": BuildStatusEnum.Unknown.value})
def test_update_self_failed(web_client: WebClient, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during service update
"""
mocker.patch("requests.post", side_effect=Exception())
web_client.update_self(BuildStatusEnum.Unknown)

View File

@ -0,0 +1,129 @@
from pathlib import Path
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
def test_from_path(mocker: MockerFixture) -> None:
"""
must load configuration
"""
read_mock = mocker.patch("configparser.RawConfigParser.read")
load_includes_mock = mocker.patch("ahriman.core.configuration.Configuration.load_includes")
load_logging_mock = mocker.patch("ahriman.core.configuration.Configuration.load_logging")
path = Path("path")
config = Configuration.from_path(path, True)
assert config.path == path
read_mock.assert_called_with(path)
load_includes_mock.assert_called_once()
load_logging_mock.assert_called_once()
def test_absolute_path_for_absolute(configuration: Configuration) -> None:
"""
must not change path for absolute path in settings
"""
path = Path("/a/b/c")
configuration.set("build", "path", str(path))
assert configuration.getpath("build", "path") == path
def test_absolute_path_for_relative(configuration: Configuration) -> None:
"""
must prepend root path to relative path
"""
path = Path("a")
configuration.set("build", "path", str(path))
result = configuration.getpath("build", "path")
assert result.is_absolute()
assert result.parent == configuration.path.parent
assert result.name == path.name
def test_dump(configuration: Configuration) -> None:
"""
dump must not be empty
"""
assert configuration.dump("x86_64")
def test_dump_architecture_specific(configuration: Configuration) -> None:
"""
dump must contain architecture specific settings
"""
configuration.add_section("build_x86_64")
configuration.set("build_x86_64", "archbuild_flags", "")
dump = configuration.dump("x86_64")
assert dump
assert "build" not in dump
assert "build_x86_64" in dump
def test_getlist(configuration: Configuration) -> None:
"""
must return list of string correctly
"""
configuration.set("build", "test_list", "a b c")
assert configuration.getlist("build", "test_list") == ["a", "b", "c"]
def test_getlist_empty(configuration: Configuration) -> None:
"""
must return list of string correctly for non-existing option
"""
assert configuration.getlist("build", "test_list") == []
configuration.set("build", "test_list", "")
assert configuration.getlist("build", "test_list") == []
def test_getlist_single(configuration: Configuration) -> None:
"""
must return list of strings for single string
"""
configuration.set("build", "test_list", "a")
assert configuration.getlist("build", "test_list") == ["a"]
def test_get_section_name(configuration: Configuration) -> None:
"""
must return architecture specific group
"""
configuration.add_section("build_x86_64")
configuration.set("build_x86_64", "archbuild_flags", "")
assert configuration.get_section_name("build", "x86_64") == "build_x86_64"
def test_get_section_name_missing(configuration: Configuration) -> None:
"""
must return default group if architecture depending group does not exist
"""
assert configuration.get_section_name("prefix", "suffix") == "prefix"
assert configuration.get_section_name("build", "x86_64") == "build"
def test_load_includes_missing(configuration: Configuration) -> None:
"""
must not fail if not include directory found
"""
configuration.set("settings", "include", "path")
configuration.load_includes()
def test_load_logging_fallback(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must fallback to stderr without errors
"""
mocker.patch("logging.config.fileConfig", side_effect=PermissionError())
configuration.load_logging(True)
def test_load_logging_stderr(configuration: Configuration, mocker: MockerFixture) -> None:
"""
must use stderr if flag set
"""
logging_mock = mocker.patch("logging.config.fileConfig")
configuration.load_logging(False)
logging_mock.assert_not_called()

View File

@ -0,0 +1,78 @@
from pytest_mock import MockerFixture
from ahriman.core.tree import Leaf, Tree
from ahriman.models.package import Package
def test_leaf_is_root_empty(leaf_ahriman: Leaf) -> None:
"""
must be root for empty packages list
"""
assert leaf_ahriman.is_root([])
def test_leaf_is_root_false(leaf_ahriman: Leaf, leaf_python_schedule: Leaf) -> None:
"""
must be root for empty dependencies list or if does not depend on packages
"""
assert leaf_ahriman.is_root([leaf_python_schedule])
leaf_ahriman.dependencies = {"ahriman-dependency"}
assert leaf_ahriman.is_root([leaf_python_schedule])
def test_leaf_is_root_true(leaf_ahriman: Leaf, leaf_python_schedule: Leaf) -> None:
"""
must not be root if depends on packages
"""
leaf_ahriman.dependencies = {"python-schedule"}
assert not leaf_ahriman.is_root([leaf_python_schedule])
leaf_ahriman.dependencies = {"python2-schedule"}
assert not leaf_ahriman.is_root([leaf_python_schedule])
leaf_ahriman.dependencies = set(leaf_python_schedule.package.packages.keys())
assert not leaf_ahriman.is_root([leaf_python_schedule])
def test_leaf_load(package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must load with dependencies
"""
tempdir_mock = mocker.patch("tempfile.mkdtemp")
fetch_mock = mocker.patch("ahriman.core.build_tools.task.Task.fetch")
dependencies_mock = mocker.patch("ahriman.models.package.Package.dependencies", return_value={"ahriman-dependency"})
rmtree_mock = mocker.patch("shutil.rmtree")
leaf = Leaf.load(package_ahriman)
assert leaf.package == package_ahriman
assert leaf.dependencies == {"ahriman-dependency"}
tempdir_mock.assert_called_once()
fetch_mock.assert_called_once()
dependencies_mock.assert_called_once()
rmtree_mock.assert_called_once()
def test_tree_levels(leaf_ahriman: Leaf, leaf_python_schedule: Leaf, mocker: MockerFixture) -> None:
"""
must generate correct levels in the simples case
"""
leaf_ahriman.dependencies = set(leaf_python_schedule.package.packages.keys())
tree = Tree([leaf_ahriman, leaf_python_schedule])
assert len(tree.levels()) == 2
first, second = tree.levels()
assert first == [leaf_python_schedule.package]
assert second == [leaf_ahriman.package]
def test_tree_load(package_ahriman: Package, package_python_schedule: Package, mocker: MockerFixture) -> None:
"""
must package list
"""
mocker.patch("tempfile.mkdtemp")
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
mocker.patch("ahriman.models.package.Package.dependencies")
mocker.patch("shutil.rmtree")
tree = Tree.load([package_ahriman, package_python_schedule])
assert len(tree.leaves) == 2

View File

@ -0,0 +1,131 @@
import logging
import pytest
import subprocess
from pytest_mock import MockerFixture
from ahriman.core.util import check_output, package_like, pretty_datetime, pretty_size
from ahriman.models.package import Package
def test_check_output(mocker: MockerFixture) -> None:
"""
must run command and log result
"""
logger_mock = mocker.patch("logging.Logger.debug")
assert check_output("echo", "hello", exception=None) == "hello"
logger_mock.assert_not_called()
assert check_output("echo", "hello", exception=None, logger=logging.getLogger("")) == "hello"
logger_mock.assert_called_once()
def test_check_output_failure(mocker: MockerFixture) -> None:
"""
must process exception correctly
"""
logger_mock = mocker.patch("logging.Logger.debug")
mocker.patch("subprocess.check_output", side_effect=subprocess.CalledProcessError(1, "echo"))
with pytest.raises(subprocess.CalledProcessError):
check_output("echo", "hello", exception=None)
logger_mock.assert_not_called()
with pytest.raises(subprocess.CalledProcessError):
check_output("echo", "hello", exception=None, logger=logging.getLogger(""))
logger_mock.assert_not_called()
def test_check_output_failure_log(mocker: MockerFixture) -> None:
"""
must process exception correctly and log it
"""
logger_mock = mocker.patch("logging.Logger.debug")
mocker.patch("subprocess.check_output", side_effect=subprocess.CalledProcessError(1, "echo", output=b"result"))
with pytest.raises(subprocess.CalledProcessError):
check_output("echo", "hello", exception=None, logger=logging.getLogger(""))
logger_mock.assert_called_once()
def test_package_like(package_ahriman: Package) -> None:
"""
package_like must return true for archives
"""
assert package_like(package_ahriman.packages[package_ahriman.base].filepath)
def test_package_like_sig(package_ahriman: Package) -> None:
"""
package_like must return false for signature files
"""
package_file = package_ahriman.packages[package_ahriman.base].filepath
sig_file = package_file.parent / f"{package_file.name}.sig"
assert not package_like(sig_file)
def test_pretty_datetime() -> None:
"""
must generate string from timestamp value
"""
assert pretty_datetime(0) == "1970-01-01 00:00:00"
def test_pretty_datetime_empty() -> None:
"""
must generate empty string from None timestamp
"""
assert pretty_datetime(None) == ""
def test_pretty_size_bytes() -> None:
"""
must generate bytes string for bytes value
"""
value, abbrev = pretty_size(42).split()
assert value == "42.0"
assert abbrev == "B"
def test_pretty_size_kbytes() -> None:
"""
must generate kibibytes string for kibibytes value
"""
value, abbrev = pretty_size(42 * 1024).split()
assert value == "42.0"
assert abbrev == "KiB"
def test_pretty_size_mbytes() -> None:
"""
must generate mebibytes string for mebibytes value
"""
value, abbrev = pretty_size(42 * 1024 * 1024).split()
assert value == "42.0"
assert abbrev == "MiB"
def test_pretty_size_gbytes() -> None:
"""
must generate gibibytes string for gibibytes value
"""
value, abbrev = pretty_size(42 * 1024 * 1024 * 1024).split()
assert value == "42.0"
assert abbrev == "GiB"
def test_pretty_size_pbytes() -> None:
"""
must generate pebibytes string for pebibytes value
"""
value, abbrev = pretty_size(42 * 1024 * 1024 * 1024 * 1024).split()
assert value == "43008.0"
assert abbrev == "GiB"
def test_pretty_size_empty() -> None:
"""
must generate empty string for None value
"""
assert pretty_size(None) == ""

View File

View File

View File

@ -0,0 +1,19 @@
import pytest
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
from ahriman.models.package_desciption import PackageDescription
@pytest.fixture
def build_status_failed() -> BuildStatus:
return BuildStatus(BuildStatusEnum.Failed, 42)
@pytest.fixture
def package_tpacpi_bat_git() -> Package:
return Package(
base="tpacpi-bat-git",
version="3.1.r12.g4959b52-1",
aur_url="https://aur.archlinux.org",
packages={"tpacpi-bat-git": PackageDescription()})

View File

@ -0,0 +1,38 @@
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
def test_build_status_enum_badges_color() -> None:
"""
status color must be one of shields.io supported
"""
SUPPORTED_COLORS = [
"brightgreen", "green", "yellowgreen", "yellow", "orange", "red", "blue", "lightgrey",
"success", "important", "critical", "informational", "inactive", "blueviolet"
]
for status in BuildStatusEnum:
assert status.badges_color() in SUPPORTED_COLORS
def test_build_status_init_1() -> None:
"""
must construct status object from None
"""
status = BuildStatus()
assert status.status == BuildStatusEnum.Unknown
assert status.timestamp > 0
def test_build_status_init_2(build_status_failed: BuildStatus) -> None:
"""
must construct status object from objects
"""
status = BuildStatus(BuildStatusEnum.Failed, 42)
assert status == build_status_failed
def test_build_status_from_json_view(build_status_failed: BuildStatus) -> None:
"""
must construct same object from json
"""
assert BuildStatus.from_json(build_status_failed.view()) == build_status_failed

View File

@ -0,0 +1,130 @@
from pathlib import Path
from pytest_mock import MockerFixture
from ahriman.models.package import Package
from ahriman.models.repository_paths import RepositoryPaths
def test_git_url(package_ahriman: Package) -> None:
"""
must generate valid git url
"""
assert package_ahriman.git_url.endswith(".git")
assert package_ahriman.git_url.startswith(package_ahriman.aur_url)
assert package_ahriman.base in package_ahriman.git_url
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
"""
assert not package_ahriman.is_vcs
def test_is_vcs_true(package_tpacpi_bat_git: Package) -> None:
"""
tpacpi-bat-git must be VCS package
"""
assert package_tpacpi_bat_git.is_vcs
def test_web_url(package_ahriman: Package) -> None:
"""
must generate valid web url
"""
assert package_ahriman.web_url.startswith(package_ahriman.aur_url)
assert package_ahriman.base in package_ahriman.web_url
def test_from_json_view_1(package_ahriman: Package) -> None:
"""
must construct same object from json
"""
assert Package.from_json(package_ahriman.view()) == package_ahriman
def test_from_json_view_2(package_python_schedule: Package) -> None:
"""
must construct same object from json
"""
assert Package.from_json(package_python_schedule.view()) == package_python_schedule
def test_from_json_view_3(package_tpacpi_bat_git: Package) -> None:
"""
must construct same object from json
"""
assert Package.from_json(package_tpacpi_bat_git.view()) == package_tpacpi_bat_git
def test_dependencies_with_version(mocker: MockerFixture, resource_path_root: Path) -> None:
"""
must load correct list of dependencies with version
"""
srcinfo = (resource_path_root / "models" / "package_yay_srcinfo").read_text()
mocker.patch("pathlib.Path.read_text", return_value=srcinfo)
assert Package.dependencies(Path("path")) == {"git", "go", "pacman"}
def test_actual_version(package_ahriman: Package, repository_paths: RepositoryPaths) -> None:
"""
must return same actual_version as version is
"""
assert package_ahriman.actual_version(repository_paths) == package_ahriman.version
def test_actual_version_vcs(package_tpacpi_bat_git: Package, repository_paths: RepositoryPaths,
mocker: MockerFixture, resource_path_root: Path) -> None:
"""
must return valid actual_version for VCS package
"""
srcinfo = (resource_path_root / "models" / "package_tpacpi-bat-git_srcinfo").read_text()
mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
assert package_tpacpi_bat_git.actual_version(repository_paths) == "3.1.r13.g4959b52-1"
def test_actual_version_vcs_failed(package_tpacpi_bat_git: Package, repository_paths: RepositoryPaths,
mocker: MockerFixture) -> None:
"""
must return same version in case if exception occurred
"""
mocker.patch("ahriman.models.package.Package._check_output", side_effect=Exception())
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
assert package_tpacpi_bat_git.actual_version(repository_paths) == package_tpacpi_bat_git.version
def test_is_outdated_false(package_ahriman: Package, repository_paths: RepositoryPaths) -> None:
"""
must be not outdated for the same package
"""
assert not package_ahriman.is_outdated(package_ahriman, repository_paths)
def test_is_outdated_true(package_ahriman: Package, repository_paths: RepositoryPaths) -> None:
"""
must be outdated for the new version
"""
other = Package.from_json(package_ahriman.view())
other.version = other.version.replace("-1", "-2")
assert package_ahriman.is_outdated(other, repository_paths)

View File

@ -0,0 +1,17 @@
from ahriman.models.package_desciption import PackageDescription
def test_filepath(package_description_ahriman: PackageDescription) -> None:
"""
must generate correct filepath if set
"""
assert package_description_ahriman.filepath is not None
assert package_description_ahriman.filepath.name == package_description_ahriman.filename
def test_filepath_empty(package_description_ahriman: PackageDescription) -> None:
"""
must return None for missing filename
"""
package_description_ahriman.filename = None
assert package_description_ahriman.filepath is None

View File

@ -0,0 +1,20 @@
import pytest
from ahriman.core.exceptions import InvalidOption
from ahriman.models.report_settings import ReportSettings
def test_from_option_invalid() -> None:
"""
must raise exception on invalid option
"""
with pytest.raises(InvalidOption, match=".* `invalid`$"):
ReportSettings.from_option("invalid")
def test_from_option_valid() -> None:
"""
must return value from valid options
"""
assert ReportSettings.from_option("html") == ReportSettings.HTML
assert ReportSettings.from_option("HTML") == ReportSettings.HTML

View File

@ -0,0 +1,23 @@
from pytest_mock import MockerFixture
from unittest import mock
from ahriman.models.repository_paths import RepositoryPaths
def test_create_tree(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must create whole tree
"""
paths = {
prop
for prop in dir(repository_paths)
if not prop.startswith("_") and prop not in ("architecture", "create_tree", "root")
}
mkdir_mock = mocker.patch("pathlib.Path.mkdir")
repository_paths.create_tree()
mkdir_mock.assert_has_calls(
[
mock.call(mode=0o755, parents=True, exist_ok=True)
for _ in paths
])

View File

@ -0,0 +1,26 @@
import pytest
from ahriman.core.exceptions import InvalidOption
from ahriman.models.sign_settings import SignSettings
def test_from_option_invalid() -> None:
"""
must raise exception on invalid option
"""
with pytest.raises(InvalidOption, match=".* `invalid`$"):
SignSettings.from_option("invalid")
def test_from_option_valid() -> None:
"""
must return value from valid options
"""
assert SignSettings.from_option("package") == SignSettings.SignPackages
assert SignSettings.from_option("PACKAGE") == SignSettings.SignPackages
assert SignSettings.from_option("packages") == SignSettings.SignPackages
assert SignSettings.from_option("sign-package") == SignSettings.SignPackages
assert SignSettings.from_option("repository") == SignSettings.SignRepository
assert SignSettings.from_option("REPOSITORY") == SignSettings.SignRepository
assert SignSettings.from_option("sign-repository") == SignSettings.SignRepository

View File

@ -0,0 +1,23 @@
import pytest
from ahriman.core.exceptions import InvalidOption
from ahriman.models.upload_settings import UploadSettings
def test_from_option_invalid() -> None:
"""
must raise exception on invalid option
"""
with pytest.raises(InvalidOption, match=".* `invalid`$"):
UploadSettings.from_option("invalid")
def test_from_option_valid() -> None:
"""
must return value from valid options
"""
assert UploadSettings.from_option("rsync") == UploadSettings.Rsync
assert UploadSettings.from_option("RSYNC") == UploadSettings.Rsync
assert UploadSettings.from_option("s3") == UploadSettings.S3
assert UploadSettings.from_option("S3") == UploadSettings.S3

View File

@ -0,0 +1,13 @@
import pytest
from aiohttp import web
from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration
from ahriman.web.web import setup_service
@pytest.fixture
def application(configuration: Configuration, mocker: MockerFixture) -> web.Application:
mocker.patch("pathlib.Path.mkdir")
return setup_service("x86_64", configuration)

View File

View File

@ -0,0 +1,45 @@
import pytest
from aiohttp import web
from pytest_mock import MockerFixture
from ahriman.core.exceptions import InitializeException
from ahriman.core.status.watcher import Watcher
from ahriman.web.web import on_startup, run_server
async def test_on_startup(application: web.Application, watcher: Watcher, mocker: MockerFixture) -> None:
"""
must call load method
"""
mocker.patch("aiohttp.web.Application.__getitem__", return_value=watcher)
load_mock = mocker.patch("ahriman.core.status.watcher.Watcher.load")
await on_startup(application)
load_mock.assert_called_once()
async def test_on_startup_exception(application: web.Application, watcher: Watcher, mocker: MockerFixture) -> None:
"""
must throw exception on load error
"""
mocker.patch("aiohttp.web.Application.__getitem__", return_value=watcher)
mocker.patch("ahriman.core.status.watcher.Watcher.load", side_effect=Exception())
with pytest.raises(InitializeException):
await on_startup(application)
def test_run(application: web.Application, mocker: MockerFixture) -> None:
"""
must run application
"""
host = "localhost"
port = 8080
application["config"].set("web", "host", host)
application["config"].set("web", "port", str(port))
run_app_mock = mocker.patch("aiohttp.web.run_app")
run_server(application)
run_app_mock.assert_called_with(application, host=host, port=port,
handle_signals=False, access_log=pytest.helpers.anyvar(int))

View File

@ -0,0 +1,14 @@
import pytest
from aiohttp import web
from asyncio import BaseEventLoop
from pytest_aiohttp import TestClient
from pytest_mock import MockerFixture
from typing import Any
@pytest.fixture
def client(application: web.Application, loop: BaseEventLoop,
aiohttp_client: Any, mocker: MockerFixture) -> TestClient:
mocker.patch("pathlib.Path.iterdir", return_value=[])
return loop.run_until_complete(aiohttp_client(application))

View File

@ -0,0 +1,37 @@
from aiohttp.test_utils import TestClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
async def test_get(client: TestClient) -> None:
"""
must return valid service status
"""
response = await client.get("/api/v1/ahriman")
status = BuildStatus.from_json(await response.json())
assert response.status == 200
assert status.status == BuildStatusEnum.Unknown
async def test_post(client: TestClient) -> None:
"""
must update service status correctly
"""
payload = {"status": BuildStatusEnum.Success.value}
post_response = await client.post("/api/v1/ahriman", json=payload)
assert post_response.status == 204
response = await client.get("/api/v1/ahriman")
status = BuildStatus.from_json(await response.json())
assert response.status == 200
assert status.status == BuildStatusEnum.Success
async def test_post_exception(client: TestClient) -> None:
"""
must raise exception on invalid payload
"""
post_response = await client.post("/api/v1/ahriman", json={})
assert post_response.status == 400

View File

@ -0,0 +1,19 @@
from pytest_aiohttp import TestClient
async def test_get(client: TestClient) -> None:
"""
must generate status page correctly (/)
"""
response = await client.get("/")
assert response.status == 200
assert await response.text()
async def test_get_index(client: TestClient) -> None:
"""
must generate status page correctly (/index.html)
"""
response = await client.get("/index.html")
assert response.status == 200
assert await response.text()

View File

@ -0,0 +1,117 @@
from pytest_aiohttp import TestClient
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.package import Package
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must return status for specific package
"""
await client.post(f"/api/v1/packages/{package_ahriman.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
await client.post(f"/api/v1/packages/{package_python_schedule.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
assert response.status == 200
packages = [Package.from_json(item["package"]) for item in await response.json()]
assert packages
assert {package.base for package in packages} == {package_ahriman.base}
async def test_get_not_found(client: TestClient, package_ahriman: Package) -> None:
"""
must return Not Found for unknown package
"""
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
assert response.status == 404
async def test_delete(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must delete single base
"""
await client.post(f"/api/v1/packages/{package_ahriman.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
await client.post(f"/api/v1/packages/{package_python_schedule.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
response = await client.delete(f"/api/v1/packages/{package_ahriman.base}")
assert response.status == 204
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
assert response.status == 404
response = await client.get(f"/api/v1/packages/{package_python_schedule.base}")
assert response.status == 200
async def test_delete_unknown(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must suppress errors on unknown package deletion
"""
await client.post(f"/api/v1/packages/{package_python_schedule.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
response = await client.delete(f"/api/v1/packages/{package_ahriman.base}")
assert response.status == 204
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
assert response.status == 404
response = await client.get(f"/api/v1/packages/{package_python_schedule.base}")
assert response.status == 200
async def test_post(client: TestClient, package_ahriman: Package) -> None:
"""
must update package status
"""
post_response = await client.post(
f"/api/v1/packages/{package_ahriman.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
assert post_response.status == 204
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
assert response.status == 200
async def test_post_exception(client: TestClient, package_ahriman: Package) -> None:
"""
must raise exception on invalid payload
"""
post_response = await client.post(f"/api/v1/packages/{package_ahriman.base}", json={})
assert post_response.status == 400
async def test_post_light(client: TestClient, package_ahriman: Package) -> None:
"""
must update package status only
"""
post_response = await client.post(
f"/api/v1/packages/{package_ahriman.base}",
json={"status": BuildStatusEnum.Unknown.value, "package": package_ahriman.view()})
assert post_response.status == 204
post_response = await client.post(
f"/api/v1/packages/{package_ahriman.base}", json={"status": BuildStatusEnum.Success.value})
assert post_response.status == 204
response = await client.get(f"/api/v1/packages/{package_ahriman.base}")
assert response.status == 200
statuses = {
Package.from_json(item["package"]).base: BuildStatus.from_json(item["status"])
for item in await response.json()
}
assert statuses[package_ahriman.base].status == BuildStatusEnum.Success
async def test_post_not_found(client: TestClient, package_ahriman: Package) -> None:
"""
must raise exception on status update for unknown package
"""
post_response = await client.post(
f"/api/v1/packages/{package_ahriman.base}", json={"status": BuildStatusEnum.Success.value})
assert post_response.status == 400

View File

@ -0,0 +1,32 @@
from pytest_aiohttp import TestClient
from pytest_mock import MockerFixture
from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
"""
must return status for all packages
"""
await client.post(f"/api/v1/packages/{package_ahriman.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
await client.post(f"/api/v1/packages/{package_python_schedule.base}",
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
response = await client.get("/api/v1/packages")
assert response.status == 200
packages = [Package.from_json(item["package"]) for item in await response.json()]
assert packages
assert {package.base for package in packages} == {package_ahriman.base, package_python_schedule.base}
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
"""
must be able to reload packages
"""
load_mock = mocker.patch("ahriman.core.status.watcher.Watcher.load")
response = await client.post("/api/v1/packages")
assert response.status == 204
load_mock.assert_called_once()

View File

@ -0,0 +1,44 @@
[settings]
logging = logging.ini
[alpm]
aur_url = https://aur.archlinux.org
database = /var/lib/pacman
repositories = core extra community multilib
root = /
[build]
archbuild_flags =
build_command = extra-x86_64-build
ignore_packages =
makechrootpkg_flags =
makepkg_flags = --skippgpcheck
[repository]
name = aur-clone
root = /var/lib/ahriman
[sign]
target =
key =
[report]
target =
[html]
path =
homepage =
link_path =
template_path = ../web/templates/repo-index.jinja2
[upload]
target =
[rsync]
remote =
[s3]
bucket =
[web]
templates = ../web/templates

View File

@ -0,0 +1,59 @@
[loggers]
keys = root,builder,build_details,http
[handlers]
keys = console_handler,build_file_handler,file_handler,http_handler
[formatters]
keys = generic_format
[handler_console_handler]
class = StreamHandler
level = DEBUG
formatter = generic_format
args = (sys.stderr,)
[handler_file_handler]
class = logging.handlers.RotatingFileHandler
level = DEBUG
formatter = generic_format
args = ("/var/log/ahriman/ahriman.log", "a", 20971520, 20)
[handler_build_file_handler]
class = logging.handlers.RotatingFileHandler
level = DEBUG
formatter = generic_format
args = ("/var/log/ahriman/build.log", "a", 20971520, 20)
[handler_http_handler]
class = logging.handlers.RotatingFileHandler
level = DEBUG
formatter = generic_format
args = ("/var/log/ahriman/http.log", "a", 20971520, 20)
[formatter_generic_format]
format = [%(levelname)s %(asctime)s] [%(filename)s:%(lineno)d] [%(funcName)s]: %(message)s
datefmt =
[logger_root]
level = DEBUG
handlers = file_handler
qualname = root
[logger_builder]
level = DEBUG
handlers = file_handler
qualname = builder
propagate = 0
[logger_build_details]
level = DEBUG
handlers = build_file_handler
qualname = build_details
propagate = 0
[logger_http]
level = DEBUG
handlers = http_handler
qualname = http
propagate = 0

View File

@ -0,0 +1,17 @@
pkgbase = tpacpi-bat-git
pkgdesc = A Perl script with ACPI calls for recent ThinkPads which are not supported by tp_smapi
pkgver = 3.1.r13.g4959b52
pkgrel = 1
url = https://github.com/teleshoes/tpacpi-bat
arch = any
license = GPL3
makedepends = git
depends = perl
depends = acpi_call
provides = tpacpi-bat
conflicts = tpacpi-bat
backup = etc/conf.d/tpacpi
source = git+https://github.com/teleshoes/tpacpi-bat.git
b2sums = SKIP
pkgname = tpacpi-bat-git

View File

@ -0,0 +1,21 @@
pkgbase = yay
pkgdesc = Yet another yogurt. Pacman wrapper and AUR helper written in go.
pkgver = 10.2.0
pkgrel = 1
url = https://github.com/Jguer/yay
arch = i686
arch = pentium4
arch = x86_64
arch = arm
arch = armv7h
arch = armv6h
arch = aarch64
license = GPL3
makedepends = go
depends = pacman>5
depends = git
optdepends = sudo
source = yay-10.2.0.tar.gz::https://github.com/Jguer/yay/archive/v10.2.0.tar.gz
sha256sums = 755d049ec09cc20bdcbb004b12ab4e35ba3bb94a7dce9dfa544d24f87deda8aa
pkgname = yay

View File

@ -0,0 +1,54 @@
<!doctype html>
<html lang="en">
<head>
<title>{{ repository|e }}</title>
{% include "style.jinja2" %}
{% include "sorttable.jinja2" %}
{% include "search.jinja2" %}
</head>
<body>
<div class="root">
<h1>ahriman
<img src="https://img.shields.io/badge/version-{{ version|e }}-informational" alt="{{ version|e }}">
<img src="https://img.shields.io/badge/architecture-{{ architecture|e }}-informational" alt="{{ architecture|e }}">
<img src="https://img.shields.io/badge/service%20status-{{ service.status|e }}-{{ service.status_color|e }}" alt="{{ service.status|e }}" title="{{ service.timestamp|e }}">
</h1>
{% include "search-line.jinja2" %}
<section class="element">
<table class="sortable search-table">
<tr class="header">
<th>package base</th>
<th>packages</th>
<th>version</th>
<th>last update</th>
<th>status</th>
</tr>
{% for package in packages %}
<tr class="package">
<td class="include-search"><a href="{{ package.web_url|e }}" title="{{ package.base|e }}">{{ package.base|e }}</a></td>
<td class="include-search">{{ package.packages|join("<br>"|safe) }}</td>
<td>{{ package.version|e }}</td>
<td>{{ package.timestamp|e }}</td>
<td class="status package-{{ package.status|e }}">{{ package.status|e }}</td>
</tr>
{% endfor %}
</table>
</section>
<footer>
<ul class="navigation">
<li><a href="https://github.com/arcan1s/ahriman" title="sources">ahriman</a></li>
<li><a href="https://github.com/arcan1s/ahriman/releases" title="releases list">releases</a></li>
<li><a href="https://github.com/arcan1s/ahriman/issues" title="issues tracker">report a bug</a></li>
</ul>
</footer>
</div>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!doctype html>
<html lang="en">
<head>
<title>{{ repository|e }}</title>
{% include "style.jinja2" %}
{% include "sorttable.jinja2" %}
{% include "search.jinja2" %}
</head>
<body>
<div class="root">
<h1>Archlinux user repository</h1>
<section class="element">
{% if pgp_key is not none %}
<p>This repository is signed with <a href="http://keys.gnupg.net/pks/lookup?search=0x{{ pgp_key|e }}&fingerprint=on&op=index" title="key search">{{ pgp_key|e }}</a> by default.</p>
{% endif %}
<code>
$ cat /etc/pacman.conf<br>
[{{ repository|e }}]<br>
Server = {{ link_path|e }}<br>
SigLevel = Database{% if has_repo_signed %}Required{% else %}Never{% endif %} Package{% if has_package_signed %}Required{% else %}Never{% endif %} TrustedOnly
</code>
</section>
{% include "search-line.jinja2" %}
<section class="element">
<table class="sortable search-table">
<tr class="header">
<th>package</th>
<th>version</th>
<th>archive size</th>
<th>installed size</th>
<th>build date</th>
</tr>
{% for package in packages %}
<tr class="package">
<td class="include-search"><a href="{{ link_path|e }}/{{ package.filename|e }}" title="{{ package.name|e }}">{{ package.name|e }}</a></td>
<td>{{ package.version|e }}</td>
<td>{{ package.archive_size|e }}</td>
<td>{{ package.installed_size|e }}</td>
<td>{{ package.build_date|e }}</td>
</tr>
{% endfor %}
</table>
</section>
<footer>
<ul class="navigation">
{% if homepage is not none %}
<li><a href="{{ homepage|e }}" title="homepage">Homepage</a></li>
{% endif %}
</ul>
</footer>
</div>
</body>
</html>

View File

@ -0,0 +1,3 @@
<section class="element">
<input type="search" id="search" onkeyup="searchInTable()" placeholder="search for package" title="search for package"/>
</section>

View File

@ -0,0 +1,25 @@
<script type="text/javascript">
function searchInTable() {
const input = document.getElementById("search");
const filter = input.value.toLowerCase();
const tables = document.getElementsByClassName("search-table");
for (let i = 0; i < tables.length; i++) {
const tr = tables[i].getElementsByTagName("tr");
// from 1 coz of header
for (let i = 1; i < tr.length; i++) {
let td = tr[i].getElementsByClassName("include-search");
let display = "none";
for (let j = 0; j < td.length; j++) {
if (td[j].tagName.toLowerCase() === "td") {
if (td[j].innerHTML.toLowerCase().indexOf(filter) > -1) {
display = "";
break;
}
}
}
tr[i].style.display = display;
}
}
}
</script>

View File

@ -0,0 +1 @@
<script src="https://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>

View File

@ -0,0 +1,136 @@
<style>
:root {
--color-building: 255, 255, 146;
--color-failed: 255, 94, 94;
--color-pending: 255, 255, 146;
--color-success: 94, 255, 94;
--color-unknown: 225, 225, 225;
--color-header: 200, 200, 255;
--color-hover: 255, 255, 225;
--color-line-blue: 235, 235, 255;
--color-line-white: 255, 255, 255;
}
@keyframes blink-building {
0% { background-color: rgba(var(--color-building), 1.0); }
10% { background-color: rgba(var(--color-building), 0.9); }
20% { background-color: rgba(var(--color-building), 0.8); }
30% { background-color: rgba(var(--color-building), 0.7); }
40% { background-color: rgba(var(--color-building), 0.6); }
50% { background-color: rgba(var(--color-building), 0.5); }
60% { background-color: rgba(var(--color-building), 0.4); }
70% { background-color: rgba(var(--color-building), 0.3); }
80% { background-color: rgba(var(--color-building), 0.2); }
90% { background-color: rgba(var(--color-building), 0.1); }
100% { background-color: rgba(var(--color-building), 0.0); }
}
div.root {
width: 70%;
padding: 15px 15% 0;
}
section.element, footer {
width: 100%;
padding: 10px 0;
}
code, input, table {
width: inherit;
}
th, td {
padding: 5px;
}
tr.package:nth-child(odd) {
background-color: rgba(var(--color-line-white), 1.0);
}
tr.package:nth-child(even) {
background-color: rgba(var(--color-line-blue), 1.0);
}
tr.package:hover {
background-color: rgba(var(--color-hover), 1.0);
}
tr.header{
background-color: rgba(var(--color-header), 1.0);
}
td.status {
text-align: center;
}
td.package-unknown {
background-color: rgba(var(--color-unknown), 1.0);
}
td.package-pending {
background-color: rgba(var(--color-pending), 1.0);
}
td.package-building {
background-color: rgba(var(--color-building), 1.0);
animation-name: blink-building;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
td.package-failed {
background-color: rgba(var(--color-failed), 1.0);
}
td.package-success {
background-color: rgba(var(--color-success), 1.0);
}
li.service-unknown {
background-color: rgba(var(--color-unknown), 1.0);
}
li.service-building {
background-color: rgba(var(--color-building), 1.0);
animation-name: blink-building;
animation-duration: 1s;
animation-timing-function: linear;
animation-iteration-count: infinite;
animation-direction: alternate;
}
li.service-failed {
background-color: rgba(var(--color-failed), 1.0);
}
li.service-success {
background-color: rgba(var(--color-success), 1.0);
}
ul.navigation {
list-style-type: none;
margin: 0;
padding: 0;
overflow: hidden;
background-color: rgba(var(--color-header), 1.0);
}
ul.navigation li {
float: left;
}
ul.navigation li.status {
display: block;
text-align: center;
text-decoration: none;
padding: 14px 16px;
}
ul.navigation li a {
display: block;
color: black;
text-align: center;
text-decoration: none;
padding: 14px 16px;
}
ul.navigation li a:hover {
background-color: rgba(var(--color-hover), 1.0);
}
</style>