Files
ahriman/tools/pytest_plugins/ahriman_fixtures/__init__.py
T
arcanis f37c5bd2a2 refactor: implement generic optional import mechanisms
this project is actively using optional imports, which leads to
duplicate handling here and there. This commit implements logic in
special method to reduce complexity and simplify tests paths
2026-07-22 16:33:59 +03:00

572 lines
15 KiB
Python

#
# Copyright (c) 2021-2025 ahriman team.
#
# This file is part of ahriman
# (see https://github.com/arcan1s/ahriman).
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
import argparse
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
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()
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()}
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.hookimpl(trylast=True)
def pytest_configure() -> None:
"""
register helpers after pytest-helpers-namespace has initialized
"""
for helper in (anyvar, get_package_status, get_package_status_extended):
pytest.helpers.register(helper)
# 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 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)
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 gpg(configuration: Configuration) -> GPG:
"""
fixture for empty GPG
Args:
configuration(Configuration): configuration fixture
Returns:
GPG: GPG test instance
"""
return GPG(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 parser() -> argparse.ArgumentParser:
"""
fixture for command line arguments parser
Returns:
argparse.ArgumentParser: command line arguments parser instance
"""
return _parser()
@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)