"""Shared pytest fixtures.""" import argparse import datetime import pytest from dataclasses import replace from pathlib import Path from pytest_mock import MockerFixture from sqlite3 import Cursor from typing import Any, TypeVar from unittest.mock import MagicMock from ahriman.application.ahriman import _parser from ahriman.core.alpm.remote import AUR from ahriman.core.configuration import Configuration from ahriman.core.database import SQLite from ahriman.core.database.migrations import Migrations from ahriman.core.log.log_loader import LogLoader from ahriman.core.repository import Repository from ahriman.core.repository.package_info import PackageInfo from ahriman.core.sign.gpg import GPG from ahriman.core.spawn import Spawn from ahriman.core.status import Client from ahriman.core.status.event_bus import EventBus from ahriman.core.status.watcher import Watcher from ahriman.models.aur_package import AURPackage from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.migration import Migration from ahriman.models.package import Package from ahriman.models.package_description import PackageDescription from ahriman.models.package_source import PackageSource from ahriman.models.remote_source import RemoteSource from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.result import Result from ahriman.models.user import User from ahriman.models.user_access import UserAccess T = TypeVar("T") # helpers # https://stackoverflow.com/a/21611963 @pytest.helpers.register def anyvar(cls: type[T], strict: bool = False) -> T: """ any value helper for mocker calls check Args: cls(type[T]): type of the variable to check strict(bool, optional): if True then check type of supplied argument (Default value = False) Returns: T: any wrapper """ class AnyVar(cls): """ any value wrapper """ def __eq__(self, other: Any) -> bool: """ compare object to other Args: other(Any): other object to compare Returns: bool: True in case if objects are equal """ return not strict or isinstance(other, cls) return AnyVar() @pytest.helpers.register def get_package_status(package: Package) -> dict[str, Any]: """ helper to extract package status from package Args: package(Package): package object Returns: dict[str, Any]: simplified package status map (with only status and view) """ return {"status": BuildStatusEnum.Unknown.value, "package": package.view()} @pytest.helpers.register def get_package_status_extended(package: Package) -> dict[str, Any]: """ helper to extract package status from package Args: package(Package): package object Returns: dict[str, Any]: full package status map (with timestamped build status and view) """ return {"status": BuildStatus().view(), "package": package.view()} @pytest.helpers.register def import_error(package: str, components: list[str], mocker: MockerFixture) -> MagicMock: """ mock import error Args: package(str): package name to import components(list[str]): component to import if any (e.g. from ... import ...) mocker(MockerFixture): mocker object Returns: MagicMock: mocked object """ import builtins _import = builtins.__import__ def test_import(name: str, globals: Any, locals: Any, from_list: list[str], level: Any): if name == package and (not components or any(component in from_list for component in components)): raise ImportError return _import(name, globals, locals, from_list, level) return mocker.patch.object(builtins, "__import__", test_import) # generic fixtures @pytest.fixture(autouse=True) def register_log_context() -> None: """ register log context variables and factory """ LogLoader.register_context() @pytest.fixture def args() -> argparse.Namespace: """ fixture for command line arguments Returns: argparse.Namespace: command line arguments test instance """ return argparse.Namespace(architecture=None, lock=None, force=False, unsafe=False, report=False, repository=None, repository_id=None, wait_timeout=-1) @pytest.fixture def gpg(configuration: Configuration) -> GPG: """ fixture for empty GPG Args: configuration(Configuration): configuration fixture Returns: GPG: GPG test instance """ return GPG(configuration) @pytest.fixture def parser() -> argparse.ArgumentParser: """ fixture for command line arguments parser Returns: argparse.ArgumentParser: command line arguments parser instance """ return _parser() @pytest.fixture def aur_package_ahriman() -> AURPackage: """ fixture for AUR package Returns: AURPackage: AUR package test instance """ return AURPackage( id=1197565, name="ahriman", package_base_id=165427, package_base="ahriman", version="2.6.0-1", description="ArcH linux ReposItory MANager", num_votes=0, popularity=0, first_submitted=datetime.datetime.fromtimestamp(1618008285, datetime.UTC), last_modified=datetime.datetime.fromtimestamp(1673826351, datetime.UTC), url_path="/cgit/aur.git/snapshot/ahriman.tar.gz", url="https://github.com/arcan1s/ahriman", out_of_date=None, maintainer="arcanis", submitter="arcanis", depends=[ "devtools", "git", "pyalpm", "python-cerberus", "python-inflection", "python-passlib", "python-requests", "python-setuptools", "python-srcinfo", ], make_depends=[ "python-build", "python-installer", "python-wheel", ], opt_depends=[ "breezy", "darcs", "mercurial", "python-aioauth-client", "python-aiohttp", "python-aiohttp-debugtoolbar", "python-aiohttp-jinja2", "python-aiohttp-security", "python-aiohttp-session", "python-boto3", "python-cryptography", "python-requests-unixsocket", "python-jinja", "rsync", "subversion", ], check_depends=[ "python-pytest", ], conflicts=[], provides=[], license=["GPL3"], keywords=[], groups=[], ) @pytest.fixture def configuration(repository_id: RepositoryId, tmp_path: Path, resource_path_root: Path) -> Configuration: """ configuration fixture Args: repository_id(RepositoryId): repository identifier fixture tmp_path(Path): temporary path used by the fixture as root resource_path_root(Path): resource path root directory Returns: Configuration: configuration test instance """ path = resource_path_root / "core" / "ahriman.ini" instance = Configuration.from_path(path, repository_id) settings_root = resource_path_root.parent.parent / "ahriman-core" / "package" / "share" / "ahriman" / "settings" logging_path = settings_root / "ahriman.ini.d" / "logging.ini" instance.set_option("settings", "include", str(settings_root / "ahriman.ini.d")) instance.set_option("settings", "logging", str(logging_path)) for include_root in ( settings_root / "ahriman.ini.d", resource_path_root.parent.parent / "ahriman-triggers" / "package" / "share" / "ahriman" / "settings" / "ahriman.ini.d", resource_path_root.parent.parent / "ahriman-web" / "package" / "share" / "ahriman" / "settings" / "ahriman.ini.d", ): for include in sorted(include_root.glob("*.ini")): if include != logging_path: instance.read(include) core_templates = resource_path_root.parent.parent / "ahriman-core" / "package" / "share" / "ahriman" / "templates" web_templates = resource_path_root.parent.parent / "ahriman-web" / "package" / "share" / "ahriman" / "templates" for section in ("email", "html", "rss", "telegram"): instance.set_option(section, "templates", str(core_templates)) instance.set_option("keyring", "target", "keyring") instance.set_option("mirrorlist", "target", "mirrorlist") instance.set_option("web", "templates", str(web_templates)) instance.set_option("web", "static_path", str(web_templates / "static")) instance.set_option("web", "autorefresh_intervals", "") instance.set_option("repository", "root", str(tmp_path)) instance.set_option("settings", "database", str(tmp_path / "ahriman.db")) return instance @pytest.fixture def database(configuration: Configuration, mocker: MockerFixture) -> SQLite: """ database fixture Args: configuration(Configuration): configuration fixture mocker(MockerFixture): mocker object Returns: SQLite: database test instance """ original_method = Migrations.perform_migration def perform_migration(self: Migrations, cursor: Cursor, migration: Migration) -> None: original_method(self, cursor, replace(migration, migrate_data=lambda *args: None)) mocker.patch.object(Migrations, "perform_migration", autospec=True, side_effect=perform_migration) return SQLite.load(configuration) @pytest.fixture def local_client(database: SQLite, configuration: Configuration) -> Client: """ local status client Args: database(SQLite): database fixture configuration(Configuration): configuration fixture Returns: Client: local status client test instance """ _, repository_id = configuration.check_loaded() return Client.load(repository_id, configuration, database, report=False) @pytest.fixture def package_ahriman(package_description_ahriman: PackageDescription, remote_source: RemoteSource) -> Package: """ package fixture Args: package_description_ahriman(PackageDescription): description fixture remote_source(RemoteSource): remote source fixture Returns: Package: package test instance """ packages = {"ahriman": package_description_ahriman} return Package( base="ahriman", version="2.6.0-1", remote=remote_source, packages=packages, packager="packager") @pytest.fixture def package_python_schedule( package_description_python_schedule: PackageDescription, package_description_python2_schedule: PackageDescription) -> Package: """ multi package fixture Args: package_description_python_schedule(PackageDescription): description fixture package_description_python2_schedule(PackageDescription): description fixture Returns: Package: multi package test instance """ packages = { "python-schedule": package_description_python_schedule, "python2-schedule": package_description_python2_schedule } return Package( base="python-schedule", version="1.0.0-2", remote=RemoteSource( source=PackageSource.AUR, git_url=AUR.remote_git_url("python-schedule", "aur"), web_url=AUR.remote_web_url("python-schedule"), path=".", branch="master", ), packages=packages) @pytest.fixture def package_description_ahriman() -> PackageDescription: """ package description fixture Returns: PackageDescription: package description test instance """ return PackageDescription( architecture="x86_64", archive_size=4200, build_date=42, depends=[ "devtools", "git", "pyalpm", "python-cerberus", "python-inflection", "python-passlib", "python-requests", "python-setuptools", "python-srcinfo", ], make_depends=[ "python-build", "python-installer", "python-wheel", ], opt_depends=[ "breezy", "darcs", "mercurial", "python-aioauth-client", "python-aiohttp", "python-aiohttp-debugtoolbar", "python-aiohttp-jinja2", "python-aiohttp-security", "python-aiohttp-session", "python-boto3", "python-cryptography", "python-requests-unixsocket", "python-jinja", "rsync", "subversion", ], check_depends=[ "python-pytest", ], description="ArcH linux ReposItory MANager", filename="ahriman-2.6.0-1-any.pkg.tar.zst", groups=[], installed_size=4200000, licenses=["GPL3"], url="https://github.com/arcan1s/ahriman", ) @pytest.fixture def package_description_python_schedule() -> PackageDescription: """ package description fixture Returns: PackageDescription: package description test instance """ return PackageDescription( architecture="x86_64", archive_size=4201, build_date=421, depends=["python"], description="Python job scheduling for humans.", filename="python-schedule-1.0.0-2-any.pkg.tar.zst", groups=[], installed_size=4200001, licenses=["MIT"], url="https://github.com/dbader/schedule", ) @pytest.fixture def package_description_python2_schedule() -> PackageDescription: """ package description fixture Returns: PackageDescription: package description test instance """ return PackageDescription( architecture="x86_64", archive_size=4202, build_date=422, depends=["python2"], description="Python job scheduling for humans.", filename="python2-schedule-1.0.0-2-any.pkg.tar.zst", groups=[], installed_size=4200002, licenses=["MIT"], url="https://github.com/dbader/schedule", ) @pytest.fixture def remote_source() -> RemoteSource: """ remote source fixture Returns: RemoteSource: remote source test instance """ return RemoteSource( source=PackageSource.AUR, git_url=AUR.remote_git_url("ahriman", "aur"), web_url=AUR.remote_web_url("ahriman"), path=".", branch="master", ) @pytest.fixture def repository(configuration: Configuration, database: SQLite, mocker: MockerFixture) -> Repository: """ fixture for repository Args: configuration(Configuration): configuration fixture database(SQLite): database fixture mocker(MockerFixture): mocker object Returns: Repository: repository test instance """ mocker.patch("ahriman.core.repository.Repository._set_context") _, repository_id = configuration.check_loaded() return Repository.load(repository_id, configuration, database, report=False) @pytest.fixture def repository_id() -> RepositoryId: """ fixture for repository identifier Returns: RepositoryId: repository identifier test instance """ return RepositoryId("x86_64", "aur") @pytest.fixture def repository_paths(configuration: Configuration) -> RepositoryPaths: """ repository paths fixture Args: configuration(Configuration): configuration fixture Returns: RepositoryPaths: repository paths test instance """ return configuration.repository_paths @pytest.fixture def result(package_ahriman: Package) -> Result: """ result fixture Args: package_ahriman(Package): package fixture Returns: Result: result test instance """ result = Result() result.add_updated(package_ahriman) return result @pytest.fixture def spawner(configuration: Configuration) -> Spawn: """ spawner fixture Args: configuration(Configuration): configuration fixture Returns: Spawn: spawner fixture """ return Spawn(MagicMock(), [ "--configuration", str(configuration.path), ]) @pytest.fixture def user() -> User: """ fixture for user descriptor Returns: User: user descriptor instance """ return User(username="user", password="pa55w0rd", access=UserAccess.Reporter, packager_id="packager", key="key") @pytest.fixture def watcher(local_client: Client) -> Watcher: """ package status watcher fixture Args: local_client(Client): local status client fixture Returns: Watcher: package status watcher test instance """ package_info = PackageInfo() event_bus = EventBus(0) return Watcher(local_client, package_info, event_bus)