Compare commits

...

9 Commits
2.1.0 ... 2.2.2

Author SHA1 Message Date
e441d93a56 Release 2.2.2 2022-09-17 04:05:06 +03:00
664b6369bb skip architecture list patching in case if any architecture is set 2022-09-17 04:04:28 +03:00
4f6bd29ff4 Release 2.2.1 2022-09-14 04:49:08 +03:00
f6d9ea480a docs update 2022-09-14 04:48:11 +03:00
08f62842ba Release 2.2.0 2022-09-14 03:28:28 +03:00
1912790ccc Make optional dependencies trully optional (#67)
The issue appears when there is no boto, jinja and some other libraries
are not installed because the classes which use these libraries are
still being imported inside the package file. The fix removes those
imports from package root, because they should not be here, in fact,
content of report and upload packages must be imported only inside the
trigger class and only if they are actually required

This commit also adds setuptools as required dependency since it is used
for some parsers (previously it was provided dependency)
2022-09-11 01:44:06 +03:00
cf3c48ffeb patch architecture list in runtime (#66) 2022-08-09 15:18:20 +03:00
6633766cc3 frozen dataclasses 2022-07-26 14:40:28 +03:00
f73d1eb424 reduce docker image size a bit 2022-07-18 11:42:26 +03:00
80 changed files with 3234 additions and 2934 deletions

View File

@ -1 +1 @@
skips: ['B101', 'B105', 'B404'] skips: ['B101', 'B105', 'B106', 'B404']

View File

@ -8,7 +8,7 @@ echo -e '[arcanisrepo]\nServer = http://repo.arcanis.me/$arch\nSigLevel = Never'
# refresh the image # refresh the image
pacman --noconfirm -Syu pacman --noconfirm -Syu
# main dependencies # main dependencies
pacman --noconfirm -Sy base-devel devtools git pyalpm python-aur python-passlib python-srcinfo sudo pacman --noconfirm -Sy base-devel devtools git pyalpm python-aur python-passlib python-setuptools python-srcinfo sudo
# make dependencies # make dependencies
pacman --noconfirm -Sy python-build python-installer python-wheel pacman --noconfirm -Sy python-build python-installer python-wheel
# optional dependencies # optional dependencies

View File

@ -4,7 +4,7 @@
set -ex set -ex
# install dependencies # install dependencies
pacman --noconfirm -Syu base-devel python-pip python-tox pacman --noconfirm -Syu base-devel python-pip python-setuptools python-tox
# run test and check targets # run test and check targets
make check tests make check tests

View File

@ -1,4 +1,4 @@
FROM archlinux:base-devel FROM archlinux:base
# image configuration # image configuration
ENV AHRIMAN_ARCHITECTURE="x86_64" ENV AHRIMAN_ARCHITECTURE="x86_64"
@ -13,24 +13,22 @@ ENV AHRIMAN_REPOSITORY_ROOT="/var/lib/ahriman/ahriman"
ENV AHRIMAN_USER="ahriman" ENV AHRIMAN_USER="ahriman"
# install environment # install environment
## install git which is required for AUR interaction and go for yay ## install minimal required packages
RUN pacman --noconfirm -Syu git go RUN pacman --noconfirm -Syu binutils fakeroot git make sudo
## create build user ## create build user
RUN useradd -m -d /home/build -s /usr/bin/nologin build && \ RUN useradd -m -d /home/build -s /usr/bin/nologin build && \
echo "build ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/build echo "build ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/build
## install AUR helper COPY "docker/install-aur-package.sh" "/usr/local/bin/install-aur-package"
RUN YAY_DIR="$(runuser -u build -- mktemp -d)" && \
git clone https://aur.archlinux.org/yay.git "$YAY_DIR" && \
cd "$YAY_DIR" && \
runuser -u build -- makepkg --noconfirm --install && \
cd - && rm -r "$YAY_DIR"
## install package dependencies ## install package dependencies
RUN runuser -u build -- yay --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-requests python-srcinfo && \ ## darcs is not installed by reasons, because it requires a lot haskell packages which dramatically increase image size
runuser -u build -- yay --noconfirm -Sy python-build python-installer python-wheel && \ RUN pacman --noconfirm -Sy devtools git pyalpm python-inflection python-passlib python-requests python-setuptools python-srcinfo && \
runuser -u build -- yay --noconfirm -Sy breezy darcs mercurial python-aioauth-client python-aiohttp \ pacman --noconfirm -Sy python-build python-installer python-wheel && \
python-aiohttp-debugtoolbar python-aiohttp-jinja2 python-aiohttp-security \ pacman --noconfirm -Sy breezy mercurial python-aiohttp python-boto3 python-cryptography python-jinja rsync subversion && \
python-aiohttp-session python-boto3 python-cryptography python-jinja \ runuser -u build -- install-aur-package python-aioauth-client python-aiohttp-jinja2 python-aiohttp-debugtoolbar \
rsync subversion python-aiohttp-session python-aiohttp-security
# cleanup unused
RUN find "/var/cache/pacman/pkg" -type f -delete
# install ahriman # install ahriman
## copy tree ## copy tree
@ -41,7 +39,7 @@ RUN cd "/home/build/ahriman" && \
cp ./*-src.tar.xz "package/archlinux" && \ cp ./*-src.tar.xz "package/archlinux" && \
cd "package/archlinux" && \ cd "package/archlinux" && \
runuser -u build -- makepkg --noconfirm --install --skipchecksums && \ runuser -u build -- makepkg --noconfirm --install --skipchecksums && \
cd - && rm -r "/home/build/ahriman" cd / && rm -r "/home/build/ahriman"
VOLUME ["/var/lib/ahriman"] VOLUME ["/var/lib/ahriman"]

View File

@ -30,6 +30,6 @@ For installation details kindly refer to the [documentation](https://ahriman.rea
Every available option is described in the [documentation](https://ahriman.readthedocs.io/en/latest/configuration.html). Every available option is described in the [documentation](https://ahriman.readthedocs.io/en/latest/configuration.html).
The application provides reasonable defaults which allow to use it out-of-box, though additional steps (like configuring build toolchain and sudoers) is recommended and can be easily achieved by following install instructions. The application provides reasonable defaults which allow to use it out-of-box; however additional steps (like configuring build toolchain and sudoers) are recommended and can be easily achieved by following install instructions.
## [FAQ](https://ahriman.readthedocs.io/en/latest/faq.html) ## [FAQ](https://ahriman.readthedocs.io/en/latest/faq.html)

View File

@ -33,7 +33,7 @@ fi
ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" repo-setup "${AHRIMAN_SETUP_ARGS[@]}" ahriman "${AHRIMAN_DEFAULT_ARGS[@]}" repo-setup "${AHRIMAN_SETUP_ARGS[@]}"
# refresh database # refresh database
runuser -u build -- yay --noconfirm -Syy &> /dev/null pacman -Syy &> /dev/null
# create machine-id which is required by build tools # create machine-id which is required by build tools
systemd-machine-id-setup &> /dev/null systemd-machine-id-setup &> /dev/null

12
docker/install-aur-package.sh Executable file
View File

@ -0,0 +1,12 @@
#!/bin/bash
set -e
for PACKAGE in "$@"; do
BUILD_DIR="$(mktemp -d)"
git clone https://aur.archlinux.org/"$PACKAGE".git "$BUILD_DIR"
cd "$BUILD_DIR"
makepkg --noconfirm --install --rmdeps --syncdeps
cd /
rm -r "$BUILD_DIR"
done

File diff suppressed because it is too large Load Diff

Before

Width:  |  Height:  |  Size: 537 KiB

After

Width:  |  Height:  |  Size: 537 KiB

View File

@ -38,6 +38,14 @@ ahriman.core.exceptions module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.lazy\_logging module
---------------------------------
.. automodule:: ahriman.core.lazy_logging
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.spawn module ahriman.core.spawn module
------------------------- -------------------------

View File

@ -92,6 +92,14 @@ ahriman.models.package\_source module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.models.pkgbuild\_patch module
-------------------------------------
.. automodule:: ahriman.models.pkgbuild_patch
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.property module ahriman.models.property module
------------------------------ ------------------------------

View File

@ -138,6 +138,7 @@ Section name must be either ``telegram`` (plus optional architecture name, e.g.
* ``link_path`` - prefix for HTML links, string, required. * ``link_path`` - prefix for HTML links, string, required.
* ``template_path`` - path to Jinja2 template, string, required. * ``template_path`` - path to Jinja2 template, string, required.
* ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``. * ``template_type`` - ``parse_mode`` to be passed to telegram API, one of ``MarkdownV2``, ``HTML``, ``Markdown``, string, optional, default ``HTML``.
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
``upload`` group ``upload`` group
---------------- ----------------
@ -167,6 +168,7 @@ This feature requires Github key creation (see below). Section name must be eith
#. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support). #. Generate new token. Required scope is ``public_repo`` (or ``repo`` for private repository support).
* ``repository`` - Github repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme). * ``repository`` - Github repository name, string, required. Repository must be created before any action and must have active branch (e.g. with readme).
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
* ``username`` - Github authorization user, string, required. Basically the same as ``owner``. * ``username`` - Github authorization user, string, required. Basically the same as ``owner``.
``rsync`` type ``rsync`` type

View File

@ -240,7 +240,7 @@ The default action (in case if no arguments provided) is ``repo-update``. Basica
docker run -v /path/to/local/repo:/var/lib/ahriman -v /etc/ahriman.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest docker run -v /path/to/local/repo:/var/lib/ahriman -v /etc/ahriman.ini:/etc/ahriman.ini.d/10-overrides.ini arcan1s/ahriman:latest
By default, it runs ``repo-update``, but it can be overwritten to any other command you would like to, e.g.: The action can be specified during run, e.g.:
.. code-block:: shell .. code-block:: shell

View File

@ -1,13 +1,13 @@
# Maintainer: Evgeniy Alekseev # Maintainer: Evgeniy Alekseev
pkgname='ahriman' pkgname='ahriman'
pkgver=2.1.0 pkgver=2.2.2
pkgrel=1 pkgrel=1
pkgdesc="ArcH linux ReposItory MANager" pkgdesc="ArcH linux ReposItory MANager"
arch=('any') arch=('any')
url="https://github.com/arcan1s/ahriman" url="https://github.com/arcan1s/ahriman"
license=('GPL3') license=('GPL3')
depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-requests' 'python-srcinfo') depends=('devtools' 'git' 'pyalpm' 'python-inflection' 'python-passlib' 'python-requests' 'python-setuptools' 'python-srcinfo')
makedepends=('python-build' 'python-installer' 'python-wheel') makedepends=('python-build' 'python-installer' 'python-wheel')
optdepends=('breezy: -bzr packages support' optdepends=('breezy: -bzr packages support'
'darcs: -darcs packages support' 'darcs: -darcs packages support'

View File

@ -20,7 +20,7 @@ archbuild_flags =
build_command = extra-x86_64-build build_command = extra-x86_64-build
ignore_packages = ignore_packages =
makechrootpkg_flags = makechrootpkg_flags =
makepkg_flags = --nocolor makepkg_flags = --nocolor --ignorearch
triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger triggers = ahriman.core.report.ReportTrigger ahriman.core.upload.UploadTrigger
[repository] [repository]

View File

@ -32,6 +32,7 @@ setup(
"inflection", "inflection",
"passlib", "passlib",
"requests", "requests",
"setuptools",
"srcinfo", "srcinfo",
], ],
setup_requires=[ setup_requires=[

View File

@ -129,7 +129,7 @@ class ApplicationPackages(ApplicationProperties):
source(str): remote URL of the package archive source(str): remote URL of the package archive
""" """
dst = self.repository.paths.packages / Path(source).name # URL is path, is not it? dst = self.repository.paths.packages / Path(source).name # URL is path, is not it?
response = requests.get(source, stream=True) response = requests.get(source, stream=True, timeout=None) # timeout=None to suppress pylint warns
response.raise_for_status() response.raise_for_status()
with dst.open("wb") as local_file: with dst.open("wb") as local_file:

View File

@ -149,7 +149,7 @@ class Users(Handler):
Returns: Returns:
User: built user descriptor User: built user descriptor
""" """
user = User(args.username, args.password, args.role) password = args.password
if user.password is None: if password is None:
user.password = getpass.getpass() password = getpass.getpass()
return user return User(username=args.username, password=password, access=args.role)

View File

@ -36,11 +36,13 @@ class AUR(Remote):
DEFAULT_AUR_URL(str): (class attribute) default AUR url DEFAULT_AUR_URL(str): (class attribute) default AUR url
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
""" """
DEFAULT_AUR_URL = "https://aur.archlinux.org" DEFAULT_AUR_URL = "https://aur.archlinux.org"
DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc" DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc"
DEFAULT_RPC_VERSION = "5" DEFAULT_RPC_VERSION = "5"
DEFAULT_TIMEOUT = 30
@staticmethod @staticmethod
def parse_response(response: Dict[str, Any]) -> List[AURPackage]: def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
@ -113,7 +115,7 @@ class AUR(Remote):
query[key] = value query[key] = value
try: try:
response = requests.get(self.DEFAULT_RPC_URL, params=query) response = requests.get(self.DEFAULT_RPC_URL, params=query, timeout=self.DEFAULT_TIMEOUT)
response.raise_for_status() response.raise_for_status()
return self.parse_response(response.json()) return self.parse_response(response.json())
except requests.HTTPError as e: except requests.HTTPError as e:

View File

@ -36,11 +36,13 @@ class Official(Remote):
DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url DEFAULT_ARCHLINUX_URL(str): (class attribute) default archlinux url
DEFAULT_SEARCH_REPOSITORIES(List[str]): (class attribute) default list of repositories to search DEFAULT_SEARCH_REPOSITORIES(List[str]): (class attribute) default list of repositories to search
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
""" """
DEFAULT_ARCHLINUX_URL = "https://archlinux.org" DEFAULT_ARCHLINUX_URL = "https://archlinux.org"
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib", "Community"] DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib", "Community"]
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json" DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
DEFAULT_TIMEOUT = 30
@staticmethod @staticmethod
def parse_response(response: Dict[str, Any]) -> List[AURPackage]: def parse_response(response: Dict[str, Any]) -> List[AURPackage]:
@ -101,7 +103,10 @@ class Official(Remote):
List[AURPackage]: response parsed to package list List[AURPackage]: response parsed to package list
""" """
try: try:
response = requests.get(self.DEFAULT_RPC_URL, params={by: args, "repo": self.DEFAULT_SEARCH_REPOSITORIES}) response = requests.get(
self.DEFAULT_RPC_URL,
params={by: args, "repo": self.DEFAULT_SEARCH_REPOSITORIES},
timeout=self.DEFAULT_TIMEOUT)
response.raise_for_status() response.raise_for_status()
return self.parse_response(response.json()) return self.parse_response(response.json())
except requests.HTTPError as e: except requests.HTTPError as e:

View File

@ -25,6 +25,7 @@ from typing import List, Optional
from ahriman.core.lazy_logging import LazyLogging from ahriman.core.lazy_logging import LazyLogging
from ahriman.core.util import check_output, walk from ahriman.core.util import check_output, walk
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
from ahriman.models.remote_source import RemoteSource from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@ -42,6 +43,26 @@ class Sources(LazyLogging):
_check_output = check_output _check_output = check_output
@staticmethod
def extend_architectures(sources_dir: Path, architecture: str) -> None:
"""
extend existing PKGBUILD with repository architecture
Args:
sources_dir(Path): local path to directory with source files
architecture(str): repository architecture
"""
pkgbuild_path = sources_dir / "PKGBUILD"
if not pkgbuild_path.is_file():
return
architectures = Package.supported_architectures(sources_dir)
if "any" in architectures: # makepkg does not like when there is any other arch except for any
return
architectures.add(architecture)
patch = PkgbuildPatch("arch", list(architectures))
patch.write(pkgbuild_path)
@staticmethod @staticmethod
def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None: def fetch(sources_dir: Path, remote: Optional[RemoteSource]) -> None:
""" """
@ -128,10 +149,9 @@ class Sources(LazyLogging):
shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True) shutil.copytree(cache_dir, sources_dir, dirs_exist_ok=True)
instance.fetch(sources_dir, package.remote) instance.fetch(sources_dir, package.remote)
if patch is None: if patch is not None:
instance.logger.info("no patches found") instance.patch_apply(sources_dir, patch)
return instance.extend_architectures(sources_dir, paths.architecture)
instance.patch_apply(sources_dir, patch)
@staticmethod @staticmethod
def patch_create(sources_dir: Path, *pattern: str) -> str: def patch_create(sources_dir: Path, *pattern: str) -> str:

View File

@ -83,7 +83,7 @@ class Migrations(LazyLogging):
module = import_module(f"{__name__}.{module_name}") module = import_module(f"{__name__}.{module_name}")
steps: List[str] = getattr(module, "steps", []) steps: List[str] = getattr(module, "steps", [])
self.logger.debug("found migration %s at index %s with steps count %s", module_name, index, len(steps)) self.logger.debug("found migration %s at index %s with steps count %s", module_name, index, len(steps))
migrations.append(Migration(index, module_name, steps)) migrations.append(Migration(index=index, name=module_name, steps=steps))
return migrations return migrations
@ -97,7 +97,7 @@ class Migrations(LazyLogging):
migrations = self.migrations() migrations = self.migrations()
current_version = self.user_version() current_version = self.user_version()
expected_version = len(migrations) expected_version = len(migrations)
result = MigrationResult(current_version, expected_version) result = MigrationResult(old_version=current_version, new_version=expected_version)
if not result.is_outdated: if not result.is_outdated:
self.logger.info("no migrations required") self.logger.info("no migrations required")

View File

@ -58,7 +58,7 @@ class AuthOperations(Operations):
def run(connection: Connection) -> List[User]: def run(connection: Connection) -> List[User]:
return [ return [
User(cursor["username"], cursor["password"], UserAccess(cursor["access"])) User(username=cursor["username"], password=cursor["password"], access=UserAccess(cursor["access"]))
for cursor in connection.execute( for cursor in connection.execute(
""" """
select * from users select * from users

View File

@ -154,7 +154,11 @@ class PackageOperations(Operations):
Dict[str, Package]: map of the package base to its descriptor (without packages themselves) Dict[str, Package]: map of the package base to its descriptor (without packages themselves)
""" """
return { return {
row["package_base"]: Package(row["package_base"], row["version"], RemoteSource.from_json(row), {}) row["package_base"]: Package(
base=row["package_base"],
version=row["version"],
remote=RemoteSource.from_json(row),
packages={})
for row in connection.execute("""select * from package_bases""") for row in connection.execute("""select * from package_bases""")
} }

View File

@ -18,11 +18,4 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.report.report import Report from ahriman.core.report.report import Report
from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.core.report.console import Console
from ahriman.core.report.email import Email
from ahriman.core.report.html import HTML
from ahriman.core.report.telegram import Telegram
from ahriman.core.report.report_trigger import ReportTrigger from ahriman.core.report.report_trigger import ReportTrigger

View File

@ -25,7 +25,8 @@ from email.mime.text import MIMEText
from typing import Dict, Iterable from typing import Dict, Iterable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report import JinjaTemplate, Report from ahriman.core.report import Report
from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.core.util import pretty_datetime from ahriman.core.util import pretty_datetime
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result

View File

@ -20,7 +20,8 @@
from typing import Iterable from typing import Iterable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report import JinjaTemplate, Report from ahriman.core.report import Report
from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result

View File

@ -84,16 +84,16 @@ class Report(LazyLogging):
section, provider_name = configuration.gettype(target, architecture) section, provider_name = configuration.gettype(target, architecture)
provider = ReportSettings.from_option(provider_name) provider = ReportSettings.from_option(provider_name)
if provider == ReportSettings.HTML: if provider == ReportSettings.HTML:
from ahriman.core.report import HTML from ahriman.core.report.html import HTML
return HTML(architecture, configuration, section) return HTML(architecture, configuration, section)
if provider == ReportSettings.Email: if provider == ReportSettings.Email:
from ahriman.core.report import Email from ahriman.core.report.email import Email
return Email(architecture, configuration, section) return Email(architecture, configuration, section)
if provider == ReportSettings.Console: if provider == ReportSettings.Console:
from ahriman.core.report import Console from ahriman.core.report.console import Console
return Console(architecture, configuration, section) return Console(architecture, configuration, section)
if provider == ReportSettings.Telegram: if provider == ReportSettings.Telegram:
from ahriman.core.report import Telegram from ahriman.core.report.telegram import Telegram
return Telegram(architecture, configuration, section) return Telegram(architecture, configuration, section)
return cls(architecture, configuration) # should never happen return cls(architecture, configuration) # should never happen

View File

@ -23,7 +23,8 @@ import requests
from typing import Iterable from typing import Iterable
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report import JinjaTemplate, Report from ahriman.core.report import Report
from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.core.util import exception_response_text from ahriman.core.util import exception_response_text
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result
@ -40,6 +41,7 @@ class Telegram(Report, JinjaTemplate):
chat_id(str): chat id to post message, either string with @ or integer chat_id(str): chat id to post message, either string with @ or integer
template_path(Path): path to template for built packages template_path(Path): path to template for built packages
template_type(str): template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown template_type(str): template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown
timeout(int): HTTP request timeout in seconds
""" """
TELEGRAM_API_URL = "https://api.telegram.org" TELEGRAM_API_URL = "https://api.telegram.org"
@ -61,6 +63,7 @@ class Telegram(Report, JinjaTemplate):
self.chat_id = configuration.get(section, "chat_id") self.chat_id = configuration.get(section, "chat_id")
self.template_path = configuration.getpath(section, "template_path") self.template_path = configuration.getpath(section, "template_path")
self.template_type = configuration.get(section, "template_type", fallback="HTML") self.template_type = configuration.get(section, "template_type", fallback="HTML")
self.timeout = configuration.getint(section, "timeout", fallback=30)
def _send(self, text: str) -> None: def _send(self, text: str) -> None:
""" """
@ -72,7 +75,8 @@ class Telegram(Report, JinjaTemplate):
try: try:
response = requests.post( response = requests.post(
f"{self.TELEGRAM_API_URL}/bot{self.api_key}/sendMessage", f"{self.TELEGRAM_API_URL}/bot{self.api_key}/sendMessage",
data={"chat_id": self.chat_id, "text": text, "parse_mode": self.template_type}) data={"chat_id": self.chat_id, "text": text, "parse_mode": self.template_type},
timeout=self.timeout)
response.raise_for_status() response.raise_for_status()
except requests.HTTPError as e: except requests.HTTPError as e:
self.logger.exception("could not perform request: %s", exception_response_text(e)) self.logger.exception("could not perform request: %s", exception_response_text(e))

View File

@ -34,6 +34,7 @@ class GPG(LazyLogging):
gnupg wrapper gnupg wrapper
Attributes: Attributes:
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
architecture(str): repository architecture architecture(str): repository architecture
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
default_key(Optional[str]): default PGP key ID to use default_key(Optional[str]): default PGP key ID to use
@ -41,6 +42,7 @@ class GPG(LazyLogging):
""" """
_check_output = check_output _check_output = check_output
DEFAULT_TIMEOUT = 30
def __init__(self, architecture: str, configuration: Configuration) -> None: def __init__(self, architecture: str, configuration: Configuration) -> None:
""" """
@ -120,7 +122,7 @@ class GPG(LazyLogging):
"op": "get", "op": "get",
"options": "mr", "options": "mr",
"search": key "search": key
}) }, timeout=self.DEFAULT_TIMEOUT)
response.raise_for_status() response.raise_for_status()
except requests.exceptions.HTTPError as e: except requests.exceptions.HTTPError as e:
self.logger.exception("could not download key %s from %s: %s", key, server, exception_response_text(e)) self.logger.exception("could not download key %s from %s: %s", key, server, exception_response_text(e))

View File

@ -80,7 +80,7 @@ class Client:
Returns: Returns:
InternalStatus: current internal (web) service status InternalStatus: current internal (web) service status
""" """
return InternalStatus(BuildStatus()) return InternalStatus(status=BuildStatus())
def remove(self, base: str) -> None: def remove(self, base: str) -> None:
""" """

View File

@ -189,7 +189,7 @@ class WebClient(Client, LazyLogging):
self.logger.exception("could not get web service status: %s", exception_response_text(e)) self.logger.exception("could not get web service status: %s", exception_response_text(e))
except Exception: except Exception:
self.logger.exception("could not get web service status") self.logger.exception("could not get web service status")
return InternalStatus(BuildStatus()) return InternalStatus(status=BuildStatus())
def remove(self, base: str) -> None: def remove(self, base: str) -> None:
""" """

View File

@ -18,10 +18,4 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from ahriman.core.upload.upload import Upload from ahriman.core.upload.upload import Upload
from ahriman.core.upload.http_upload import HttpUpload
from ahriman.core.upload.github import Github
from ahriman.core.upload.rsync import Rsync
from ahriman.core.upload.s3 import S3
from ahriman.core.upload.upload_trigger import UploadTrigger from ahriman.core.upload.upload_trigger import UploadTrigger

View File

@ -24,7 +24,7 @@ from pathlib import Path
from typing import Any, Dict, Iterable, Optional from typing import Any, Dict, Iterable, Optional
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.upload import HttpUpload from ahriman.core.upload.http_upload import HttpUpload
from ahriman.core.util import walk from ahriman.core.util import walk
from ahriman.models.package import Package from ahriman.models.package import Package

View File

@ -34,6 +34,7 @@ class HttpUpload(Upload):
Attributes: Attributes:
auth(Tuple[str, str]): HTTP auth object auth(Tuple[str, str]): HTTP auth object
timeout(int): HTTP request timeout in seconds
""" """
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None: def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
@ -49,6 +50,7 @@ class HttpUpload(Upload):
password = configuration.get(section, "password") password = configuration.get(section, "password")
username = configuration.get(section, "username") username = configuration.get(section, "username")
self.auth = (password, username) self.auth = (password, username)
self.timeout = configuration.getint(section, "timeout", fallback=30)
@staticmethod @staticmethod
def calculate_hash(path: Path) -> str: def calculate_hash(path: Path) -> str:
@ -108,7 +110,7 @@ class HttpUpload(Upload):
requests.Response: request response object requests.Response: request response object
""" """
try: try:
response = requests.request(method, url, auth=self.auth, **kwargs) response = requests.request(method, url, auth=self.auth, timeout=self.timeout, **kwargs)
response.raise_for_status() response.raise_for_status()
except requests.HTTPError as e: except requests.HTTPError as e:
self.logger.exception("could not perform %s request to %s: %s", method, url, exception_response_text(e)) self.logger.exception("could not perform %s request to %s: %s", method, url, exception_response_text(e))

View File

@ -83,13 +83,13 @@ class Upload(LazyLogging):
section, provider_name = configuration.gettype(target, architecture) section, provider_name = configuration.gettype(target, architecture)
provider = UploadSettings.from_option(provider_name) provider = UploadSettings.from_option(provider_name)
if provider == UploadSettings.Rsync: if provider == UploadSettings.Rsync:
from ahriman.core.upload import Rsync from ahriman.core.upload.rsync import Rsync
return Rsync(architecture, configuration, section) return Rsync(architecture, configuration, section)
if provider == UploadSettings.S3: if provider == UploadSettings.S3:
from ahriman.core.upload import S3 from ahriman.core.upload.s3 import S3
return S3(architecture, configuration, section) return S3(architecture, configuration, section)
if provider == UploadSettings.Github: if provider == UploadSettings.Github:
from ahriman.core.upload import Github from ahriman.core.upload.github import Github
return Github(architecture, configuration, section) return Github(architecture, configuration, section)
return cls(architecture, configuration) # should never happen return cls(architecture, configuration) # should never happen

View File

@ -29,7 +29,7 @@ from typing import Any, Callable, Dict, List, Optional, Type
from ahriman.core.util import filter_json, full_version from ahriman.core.util import filter_json, full_version
@dataclass @dataclass(frozen=True, kw_only=True)
class AURPackage: class AURPackage:
""" """
AUR package descriptor AUR package descriptor

View File

@ -47,7 +47,7 @@ class BuildStatusEnum(str, Enum):
Success = "success" Success = "success"
@dataclass @dataclass(frozen=True)
class BuildStatus: class BuildStatus:
""" """
build status holder build status holder
@ -64,7 +64,7 @@ class BuildStatus:
""" """
convert status to enum type convert status to enum type
""" """
self.status = BuildStatusEnum(self.status) object.__setattr__(self, "status", BuildStatusEnum(self.status))
@classmethod @classmethod
def from_json(cls: Type[BuildStatus], dump: Dict[str, Any]) -> BuildStatus: def from_json(cls: Type[BuildStatus], dump: Dict[str, Any]) -> BuildStatus:

View File

@ -27,7 +27,7 @@ from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package from ahriman.models.package import Package
@dataclass @dataclass(frozen=True, kw_only=True)
class Counters: class Counters:
""" """
package counters package counters

View File

@ -26,7 +26,7 @@ from ahriman.models.build_status import BuildStatus
from ahriman.models.counters import Counters from ahriman.models.counters import Counters
@dataclass @dataclass(frozen=True, kw_only=True)
class InternalStatus: class InternalStatus:
""" """
internal server status internal server status

View File

@ -21,7 +21,7 @@ from dataclasses import dataclass
from typing import List from typing import List
@dataclass @dataclass(frozen=True, kw_only=True)
class Migration: class Migration:
""" """
migration implementation migration implementation

View File

@ -22,7 +22,7 @@ from dataclasses import dataclass
from ahriman.core.exceptions import MigrationError from ahriman.core.exceptions import MigrationError
@dataclass @dataclass(frozen=True, kw_only=True)
class MigrationResult: class MigrationResult:
""" """
migration result implementation model migration result implementation model

View File

@ -38,7 +38,7 @@ from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
@dataclass @dataclass(kw_only=True)
class Package(LazyLogging): class Package(LazyLogging):
""" """
package properties representation package properties representation
@ -147,7 +147,7 @@ class Package(LazyLogging):
""" """
package = pacman.handle.load_pkg(str(path)) package = pacman.handle.load_pkg(str(path))
description = PackageDescription.from_package(package, path) description = PackageDescription.from_package(package, path)
return cls(package.base, package.version, remote, {package.name: description}) return cls(base=package.base, version=package.version, remote=remote, packages={package.name: description})
@classmethod @classmethod
def from_aur(cls: Type[Package], name: str, pacman: Pacman) -> Package: def from_aur(cls: Type[Package], name: str, pacman: Pacman) -> Package:
@ -163,7 +163,11 @@ class Package(LazyLogging):
""" """
package = AUR.info(name, pacman=pacman) package = AUR.info(name, pacman=pacman)
remote = RemoteSource.from_source(PackageSource.AUR, package.package_base, package.repository) remote = RemoteSource.from_source(PackageSource.AUR, package.package_base, package.repository)
return cls(package.package_base, package.version, remote, {package.name: PackageDescription()}) return cls(
base=package.package_base,
version=package.version,
remote=remote,
packages={package.name: PackageDescription()})
@classmethod @classmethod
def from_build(cls: Type[Package], path: Path) -> Package: def from_build(cls: Type[Package], path: Path) -> Package:
@ -186,7 +190,7 @@ class Package(LazyLogging):
packages = {key: PackageDescription() for key in srcinfo["packages"]} packages = {key: PackageDescription() for key in srcinfo["packages"]}
version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"]) version = full_version(srcinfo.get("epoch"), srcinfo["pkgver"], srcinfo["pkgrel"])
return cls(srcinfo["pkgbase"], version, None, packages) return cls(base=srcinfo["pkgbase"], version=version, remote=None, packages=packages)
@classmethod @classmethod
def from_json(cls: Type[Package], dump: Dict[str, Any]) -> Package: def from_json(cls: Type[Package], dump: Dict[str, Any]) -> Package:
@ -204,11 +208,7 @@ class Package(LazyLogging):
for key, value in dump.get("packages", {}).items() for key, value in dump.get("packages", {}).items()
} }
remote = dump.get("remote", {}) remote = dump.get("remote", {})
return cls( return cls(base=dump["base"], version=dump["version"], remote=RemoteSource.from_json(remote), packages=packages)
base=dump["base"],
version=dump["version"],
remote=RemoteSource.from_json(remote),
packages=packages)
@classmethod @classmethod
def from_official(cls: Type[Package], name: str, pacman: Pacman, use_syncdb: bool = True) -> Package: def from_official(cls: Type[Package], name: str, pacman: Pacman, use_syncdb: bool = True) -> Package:
@ -225,7 +225,11 @@ class Package(LazyLogging):
""" """
package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name, pacman=pacman) package = OfficialSyncdb.info(name, pacman=pacman) if use_syncdb else Official.info(name, pacman=pacman)
remote = RemoteSource.from_source(PackageSource.Repository, package.package_base, package.repository) remote = RemoteSource.from_source(PackageSource.Repository, package.package_base, package.repository)
return cls(package.package_base, package.version, remote, {package.name: PackageDescription()}) return cls(
base=package.package_base,
version=package.version,
remote=remote,
packages={package.name: PackageDescription()})
@staticmethod @staticmethod
def dependencies(path: Path) -> Set[str]: def dependencies(path: Path) -> Set[str]:
@ -263,6 +267,26 @@ class Package(LazyLogging):
packages = set(srcinfo["packages"].keys()) packages = set(srcinfo["packages"].keys())
return (depends | makedepends) - packages return (depends | makedepends) - packages
@staticmethod
def supported_architectures(path: Path) -> Set[str]:
"""
load supported architectures from package sources
Args:
path(Path): path to package sources directory
Returns:
Set[str]: list of package supported architectures
Raises:
InvalidPackageInfo: if there are parsing errors
"""
srcinfo_source = Package._check_output("makepkg", "--printsrcinfo", exception=None, cwd=path)
srcinfo, errors = parse_srcinfo(srcinfo_source)
if errors:
raise InvalidPackageInfo(errors)
return set(srcinfo.get("arch", []))
def actual_version(self, paths: RepositoryPaths) -> str: def actual_version(self, paths: RepositoryPaths) -> str:
""" """
additional method to handle VCS package versions additional method to handle VCS package versions

View File

@ -27,7 +27,7 @@ from typing import Any, Dict, List, Optional, Type
from ahriman.core.util import filter_json from ahriman.core.util import filter_json
@dataclass @dataclass(kw_only=True)
class PackageDescription: class PackageDescription:
""" """
package specific properties package specific properties

View File

@ -0,0 +1,92 @@
#
# Copyright (c) 2021-2022 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 shlex
from dataclasses import dataclass, field
from pathlib import Path
from typing import List, Union
@dataclass(frozen=True)
class PkgbuildPatch:
"""
wrapper for patching PKBGUILDs
Attributes:
key(str): name of the property in PKGBUILD, e.g. version, url etc
value(Union[str, List[str]]): value of the stored PKGBUILD property. It must be either string or list of string
values
unsafe(bool): if set, value will be not quoted, might break PKGBUILD
"""
key: str
value: Union[str, List[str]]
unsafe: bool = field(default=False, kw_only=True)
@property
def is_function(self) -> bool:
"""
parse key and define whether it function or not
Returns:
bool: True in case if key ends with parentheses and False otherwise
"""
return self.key.endswith("()")
def quote(self, value: str) -> str:
"""
quote value according to the unsafe flag
Args:
value(str): value to be quoted
Returns:
str: quoted string in case if unsafe is False and as is otherwise
"""
return value if self.unsafe else shlex.quote(value)
def serialize(self) -> str:
"""
serialize key-value pair into PKBGBUILD string. List values will be put inside parentheses. All string
values (including the ones inside list values) will be put inside quotes, no shell variables expanding supported
at the moment
Returns:
str: serialized key-value pair, print-friendly
"""
if isinstance(self.value, list): # list like
value = " ".join(map(self.quote, self.value))
return f"""{self.key}=({value})"""
# we suppose that function values are only supported in string-like values
if self.is_function:
return f"{self.key} {self.value}" # no quoting enabled here
return f"""{self.key}={self.quote(self.value)}"""
def write(self, pkgbuild_path: Path) -> None:
"""
write serialized value into PKGBUILD by specified path
Args:
pkgbuild_path(Path): path to PKGBUILD file
"""
with pkgbuild_path.open("a") as pkgbuild:
pkgbuild.write("\n") # in case if file ends without new line we are appending it at the end
pkgbuild.write(self.serialize())
pkgbuild.write("\n") # append new line after the values

View File

@ -17,11 +17,11 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from dataclasses import dataclass from dataclasses import dataclass, field
from typing import Any from typing import Any
@dataclass @dataclass(frozen=True)
class Property: class Property:
""" """
holder of object properties descriptor holder of object properties descriptor
@ -34,4 +34,4 @@ class Property:
name: str name: str
value: Any value: Any
is_required: bool = False is_required: bool = field(default=False, kw_only=True)

View File

@ -27,7 +27,7 @@ from ahriman.core.util import filter_json
from ahriman.models.package_source import PackageSource from ahriman.models.package_source import PackageSource
@dataclass @dataclass(frozen=True, kw_only=True)
class RemoteSource: class RemoteSource:
""" """
remote package source properties remote package source properties
@ -50,7 +50,7 @@ class RemoteSource:
""" """
convert source to enum type convert source to enum type
""" """
self.source = PackageSource(self.source) object.__setattr__(self, "source", PackageSource(self.source))
@property @property
def pkgbuild_dir(self) -> Path: def pkgbuild_dir(self) -> Path:

View File

@ -29,7 +29,7 @@ from typing import Set, Tuple, Type
from ahriman.core.exceptions import InvalidPath from ahriman.core.exceptions import InvalidPath
@dataclass @dataclass(frozen=True)
class RepositoryPaths: class RepositoryPaths:
""" """
repository paths holder. For the most operations with paths you want to use this object repository paths holder. For the most operations with paths you want to use this object

View File

@ -19,7 +19,7 @@
# #
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass, replace
from typing import Optional, Type from typing import Optional, Type
from passlib.pwd import genword as generate_password # type: ignore from passlib.pwd import genword as generate_password # type: ignore
from passlib.handlers.sha2_crypt import sha512_crypt # type: ignore from passlib.handlers.sha2_crypt import sha512_crypt # type: ignore
@ -27,7 +27,7 @@ from passlib.handlers.sha2_crypt import sha512_crypt # type: ignore
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
@dataclass @dataclass(frozen=True, kw_only=True)
class User: class User:
""" """
authorized web user model authorized web user model
@ -82,7 +82,7 @@ class User:
""" """
if username is None or password is None: if username is None or password is None:
return None return None
return cls(username, password, access) return cls(username=username, password=password, access=access)
@staticmethod @staticmethod
def generate_password(length: int) -> str: def generate_password(length: int) -> str:
@ -130,7 +130,7 @@ class User:
# when we do not store any password here # when we do not store any password here
return self return self
password_hash: str = self._HASHER.hash(self.password + salt) password_hash: str = self._HASHER.hash(self.password + salt)
return User(self.username, password_hash, self.access) return replace(self, password=password_hash)
def verify_access(self, required: UserAccess) -> bool: def verify_access(self, required: UserAccess) -> bool:
""" """

View File

@ -25,7 +25,7 @@ from dataclasses import dataclass
from typing import Optional, Type from typing import Optional, Type
@dataclass @dataclass(frozen=True)
class UserIdentity: class UserIdentity:
""" """
user identity used inside web service user identity used inside web service

View File

@ -17,4 +17,4 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
__version__ = "2.1.0" __version__ = "2.2.2"

View File

@ -111,7 +111,7 @@ def test_add_remote(application_packages: ApplicationPackages, package_descripti
application_packages._add_remote(url) application_packages._add_remote(url)
open_mock.assert_called_once_with("wb") open_mock.assert_called_once_with("wb")
request_mock.assert_called_once_with(url, stream=True) request_mock.assert_called_once_with(url, stream=True, timeout=None)
response_mock.raise_for_status.assert_called_once_with() response_mock.raise_for_status.assert_called_once_with()

View File

@ -38,7 +38,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, database: S
must run command must run command
""" """
args = _default_args(args) args = _default_args(args)
user = User(args.username, args.password, args.role) user = User(username=args.username, password=args.password, access=args.role)
mocker.patch("ahriman.core.database.SQLite.load", return_value=database) mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.models.user.User.hash_password", return_value=user) mocker.patch("ahriman.models.user.User.hash_password", return_value=user)
get_auth_configuration_mock = mocker.patch("ahriman.application.handlers.Users.configuration_get") get_auth_configuration_mock = mocker.patch("ahriman.application.handlers.Users.configuration_get")

View File

@ -426,7 +426,7 @@ def user() -> User:
Returns: Returns:
User: user descriptor instance User: user descriptor instance
""" """
return User("user", "pa55w0rd", UserAccess.Reporter) return User(username="user", password="pa55w0rd", access=UserAccess.Reporter)
@pytest.fixture @pytest.fixture

View File

@ -78,7 +78,9 @@ def test_make_request(aur: AUR, aur_package_ahriman: AURPackage,
assert aur.make_request("info", "ahriman") == [aur_package_ahriman] assert aur.make_request("info", "ahriman") == [aur_package_ahriman]
request_mock.assert_called_once_with( request_mock.assert_called_once_with(
"https://aur.archlinux.org/rpc", params={"v": "5", "type": "info", "arg": ["ahriman"]}) "https://aur.archlinux.org/rpc",
params={"v": "5", "type": "info", "arg": ["ahriman"]},
timeout=aur.DEFAULT_TIMEOUT)
def test_make_request_multi_arg(aur: AUR, aur_package_ahriman: AURPackage, def test_make_request_multi_arg(aur: AUR, aur_package_ahriman: AURPackage,
@ -92,7 +94,9 @@ def test_make_request_multi_arg(aur: AUR, aur_package_ahriman: AURPackage,
assert aur.make_request("search", "ahriman", "is", "cool") == [aur_package_ahriman] assert aur.make_request("search", "ahriman", "is", "cool") == [aur_package_ahriman]
request_mock.assert_called_once_with( request_mock.assert_called_once_with(
"https://aur.archlinux.org/rpc", params={"v": "5", "type": "search", "arg[]": ["ahriman", "is", "cool"]}) "https://aur.archlinux.org/rpc",
params={"v": "5", "type": "search", "arg[]": ["ahriman", "is", "cool"]},
timeout=aur.DEFAULT_TIMEOUT)
def test_make_request_with_kwargs(aur: AUR, aur_package_ahriman: AURPackage, def test_make_request_with_kwargs(aur: AUR, aur_package_ahriman: AURPackage,
@ -106,7 +110,9 @@ def test_make_request_with_kwargs(aur: AUR, aur_package_ahriman: AURPackage,
assert aur.make_request("search", "ahriman", by="name") == [aur_package_ahriman] assert aur.make_request("search", "ahriman", by="name") == [aur_package_ahriman]
request_mock.assert_called_once_with( request_mock.assert_called_once_with(
"https://aur.archlinux.org/rpc", params={"v": "5", "type": "search", "arg": ["ahriman"], "by": "name"}) "https://aur.archlinux.org/rpc",
params={"v": "5", "type": "search", "arg": ["ahriman"], "by": "name"},
timeout=aur.DEFAULT_TIMEOUT)
def test_make_request_failed(aur: AUR, mocker: MockerFixture) -> None: def test_make_request_failed(aur: AUR, mocker: MockerFixture) -> None:

View File

@ -84,8 +84,10 @@ def test_make_request(official: Official, aur_package_akonadi: AURPackage,
request_mock = mocker.patch("requests.get", return_value=response_mock) request_mock = mocker.patch("requests.get", return_value=response_mock)
assert official.make_request("akonadi", by="q") == [aur_package_akonadi] assert official.make_request("akonadi", by="q") == [aur_package_akonadi]
request_mock.assert_called_once_with("https://archlinux.org/packages/search/json", request_mock.assert_called_once_with(
params={"q": ("akonadi",), "repo": Official.DEFAULT_SEARCH_REPOSITORIES}) "https://archlinux.org/packages/search/json",
params={"q": ("akonadi",), "repo": Official.DEFAULT_SEARCH_REPOSITORIES},
timeout=official.DEFAULT_TIMEOUT)
def test_make_request_failed(official: Official, mocker: MockerFixture) -> None: def test_make_request_failed(official: Official, mocker: MockerFixture) -> None:

View File

@ -10,6 +10,42 @@ from ahriman.models.remote_source import RemoteSource
from ahriman.models.repository_paths import RepositoryPaths from ahriman.models.repository_paths import RepositoryPaths
def test_extend_architectures(mocker: MockerFixture) -> None:
"""
must update available architecture list
"""
mocker.patch("pathlib.Path.is_file", return_value=True)
archs_mock = mocker.patch("ahriman.models.package.Package.supported_architectures", return_value={"x86_64"})
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
Sources.extend_architectures(Path("local"), "i686")
archs_mock.assert_called_once_with(Path("local"))
write_mock.assert_called_once_with(Path("local") / "PKGBUILD")
def test_extend_architectures_any(mocker: MockerFixture) -> None:
"""
must skip architecture patching in case if there is any architecture
"""
mocker.patch("pathlib.Path.is_file", return_value=True)
mocker.patch("ahriman.models.package.Package.supported_architectures", return_value={"any"})
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
Sources.extend_architectures(Path("local"), "i686")
write_mock.assert_not_called()
def test_extend_architectures_skip(mocker: MockerFixture) -> None:
"""
must skip extending list of the architectures in case if no PKGBUILD file found
"""
mocker.patch("pathlib.Path.is_file", return_value=False)
write_mock = mocker.patch("ahriman.models.pkgbuild_patch.PkgbuildPatch.write")
Sources.extend_architectures(Path("local"), "i686")
write_mock.assert_not_called()
def test_fetch_empty(remote_source: RemoteSource, mocker: MockerFixture) -> None: def test_fetch_empty(remote_source: RemoteSource, mocker: MockerFixture) -> None:
""" """
must do nothing in case if no branches available must do nothing in case if no branches available
@ -134,10 +170,12 @@ def test_load(package_ahriman: Package, repository_paths: RepositoryPaths, mocke
mocker.patch("pathlib.Path.is_dir", return_value=False) mocker.patch("pathlib.Path.is_dir", return_value=False)
fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch") fetch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.fetch")
patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply") patch_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.patch_apply")
architectures_mock = mocker.patch("ahriman.core.build_tools.sources.Sources.extend_architectures")
Sources.load(Path("local"), package_ahriman, "patch", repository_paths) Sources.load(Path("local"), package_ahriman, "patch", repository_paths)
fetch_mock.assert_called_once_with(Path("local"), package_ahriman.remote) fetch_mock.assert_called_once_with(Path("local"), package_ahriman.remote)
patch_mock.assert_called_once_with(Path("local"), "patch") patch_mock.assert_called_once_with(Path("local"), "patch")
architectures_mock.assert_called_once_with(Path("local"), repository_paths.architecture)
def test_load_no_patch(package_ahriman: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: def test_load_no_patch(package_ahriman: Package, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:

View File

@ -44,7 +44,7 @@ def test_run(migrations: Migrations, mocker: MockerFixture) -> None:
cursor = MagicMock() cursor = MagicMock()
mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0) mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0)
mocker.patch("ahriman.core.database.migrations.Migrations.migrations", mocker.patch("ahriman.core.database.migrations.Migrations.migrations",
return_value=[Migration(0, "test", ["select 1"])]) return_value=[Migration(index=0, name="test", steps=["select 1"])])
migrations.connection.cursor.return_value = cursor migrations.connection.cursor.return_value = cursor
validate_mock = mocker.patch("ahriman.models.migration_result.MigrationResult.validate") validate_mock = mocker.patch("ahriman.models.migration_result.MigrationResult.validate")
migrate_data_mock = mocker.patch("ahriman.core.database.migrations.migrate_data") migrate_data_mock = mocker.patch("ahriman.core.database.migrations.migrate_data")
@ -58,7 +58,8 @@ def test_run(migrations: Migrations, mocker: MockerFixture) -> None:
mock.call("commit"), mock.call("commit"),
]) ])
cursor.close.assert_called_once_with() cursor.close.assert_called_once_with()
migrate_data_mock.assert_called_once_with(MigrationResult(0, 1), migrations.connection, migrations.configuration) migrate_data_mock.assert_called_once_with(
MigrationResult(old_version=0, new_version=1), migrations.connection, migrations.configuration)
def test_run_migration_exception(migrations: Migrations, mocker: MockerFixture) -> None: def test_run_migration_exception(migrations: Migrations, mocker: MockerFixture) -> None:
@ -69,7 +70,7 @@ def test_run_migration_exception(migrations: Migrations, mocker: MockerFixture)
mocker.patch("logging.Logger.info", side_effect=Exception()) mocker.patch("logging.Logger.info", side_effect=Exception())
mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0) mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0)
mocker.patch("ahriman.core.database.migrations.Migrations.migrations", mocker.patch("ahriman.core.database.migrations.Migrations.migrations",
return_value=[Migration(0, "test", ["select 1"])]) return_value=[Migration(index=0, name="test", steps=["select 1"])])
mocker.patch("ahriman.models.migration_result.MigrationResult.validate") mocker.patch("ahriman.models.migration_result.MigrationResult.validate")
migrations.connection.cursor.return_value = cursor migrations.connection.cursor.return_value = cursor
@ -90,7 +91,7 @@ def test_run_sql_exception(migrations: Migrations, mocker: MockerFixture) -> Non
cursor.execute.side_effect = Exception() cursor.execute.side_effect = Exception()
mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0) mocker.patch("ahriman.core.database.migrations.Migrations.user_version", return_value=0)
mocker.patch("ahriman.core.database.migrations.Migrations.migrations", mocker.patch("ahriman.core.database.migrations.Migrations.migrations",
return_value=[Migration(0, "test", ["select 1"])]) return_value=[Migration(index=0, name="test", steps=["select 1"])])
mocker.patch("ahriman.models.migration_result.MigrationResult.validate") mocker.patch("ahriman.models.migration_result.MigrationResult.validate")
migrations.connection.cursor.return_value = cursor migrations.connection.cursor.return_value = cursor

View File

@ -16,21 +16,21 @@ def test_user_list(database: SQLite, user: User) -> None:
must return all users must return all users
""" """
database.user_update(user) database.user_update(user)
database.user_update(User(user.password, user.username, user.access)) database.user_update(User(username=user.password, password=user.username, access=user.access))
users = database.user_list(None, None) users = database.user_list(None, None)
assert len(users) == 2 assert len(users) == 2
assert user in users assert user in users
assert User(user.password, user.username, user.access) in users assert User(username=user.password, password=user.username, access=user.access) in users
def test_user_list_filter_by_username(database: SQLite) -> None: def test_user_list_filter_by_username(database: SQLite) -> None:
""" """
must return users filtered by its id must return users filtered by its id
""" """
first = User("1", "", UserAccess.Read) first = User(username="1", password="", access=UserAccess.Read)
second = User("2", "", UserAccess.Full) second = User(username="2", password="", access=UserAccess.Full)
third = User("3", "", UserAccess.Read) third = User(username="3", password="", access=UserAccess.Read)
database.user_update(first) database.user_update(first)
database.user_update(second) database.user_update(second)
@ -45,9 +45,9 @@ def test_user_list_filter_by_access(database: SQLite) -> None:
""" """
must return users filtered by its access must return users filtered by its access
""" """
first = User("1", "", UserAccess.Read) first = User(username="1", password="", access=UserAccess.Read)
second = User("2", "", UserAccess.Full) second = User(username="2", password="", access=UserAccess.Full)
third = User("3", "", UserAccess.Read) third = User(username="3", password="", access=UserAccess.Read)
database.user_update(first) database.user_update(first)
database.user_update(second) database.user_update(second)
@ -63,9 +63,9 @@ def test_user_list_filter_by_username_access(database: SQLite) -> None:
""" """
must return users filtered by its access and username must return users filtered by its access and username
""" """
first = User("1", "", UserAccess.Read) first = User(username="1", password="", access=UserAccess.Read)
second = User("2", "", UserAccess.Full) second = User(username="2", password="", access=UserAccess.Full)
third = User("3", "", UserAccess.Read) third = User(username="3", password="", access=UserAccess.Read)
database.user_update(first) database.user_update(first)
database.user_update(second) database.user_update(second)
@ -91,7 +91,6 @@ def test_user_update(database: SQLite, user: User) -> None:
database.user_update(user) database.user_update(user)
assert database.user_get(user.username) == user assert database.user_get(user.username) == user
new_user = user.hash_password("salt") new_user = User(username=user.username, password=user.hash_password("salt").password, access=UserAccess.Full)
new_user.access = UserAccess.Full
database.user_update(new_user) database.user_update(new_user)
assert database.user_get(new_user.username) == new_user assert database.user_get(new_user.username) == new_user

View File

@ -2,7 +2,7 @@ from pytest_mock import MockerFixture
from unittest import mock from unittest import mock
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report import Console from ahriman.core.report.console import Console
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result

View File

@ -3,7 +3,7 @@ import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report import Email from ahriman.core.report.email import Email
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result
@ -90,7 +90,7 @@ def test_generate(configuration: Configuration, package_ahriman: Package, mocker
""" """
must generate report must generate report
""" """
send_mock = mocker.patch("ahriman.core.report.Email._send") send_mock = mocker.patch("ahriman.core.report.email.Email._send")
report = Email("x86_64", configuration, "email") report = Email("x86_64", configuration, "email")
report.generate([package_ahriman], Result()) report.generate([package_ahriman], Result())
@ -102,7 +102,7 @@ def test_generate_with_built(configuration: Configuration, package_ahriman: Pack
""" """
must generate report with built packages must generate report with built packages
""" """
send_mock = mocker.patch("ahriman.core.report.Email._send") send_mock = mocker.patch("ahriman.core.report.email.Email._send")
report = Email("x86_64", configuration, "email") report = Email("x86_64", configuration, "email")
report.generate([package_ahriman], result) report.generate([package_ahriman], result)
@ -117,7 +117,7 @@ def test_generate_with_built_and_full_path(
""" """
must generate report with built packages and full packages lists must generate report with built packages and full packages lists
""" """
send_mock = mocker.patch("ahriman.core.report.Email._send") send_mock = mocker.patch("ahriman.core.report.email.Email._send")
report = Email("x86_64", configuration, "email") report = Email("x86_64", configuration, "email")
report.full_template_path = report.template_path report.full_template_path = report.template_path
@ -130,7 +130,7 @@ def test_generate_no_empty(configuration: Configuration, package_ahriman: Packag
must not generate report with built packages if no_empty_report is set must not generate report with built packages if no_empty_report is set
""" """
configuration.set_option("email", "no_empty_report", "yes") configuration.set_option("email", "no_empty_report", "yes")
send_mock = mocker.patch("ahriman.core.report.Email._send") send_mock = mocker.patch("ahriman.core.report.email.Email._send")
report = Email("x86_64", configuration, "email") report = Email("x86_64", configuration, "email")
report.generate([package_ahriman], Result()) report.generate([package_ahriman], Result())
@ -143,7 +143,7 @@ def test_generate_no_empty_with_built(configuration: Configuration, package_ahri
must generate report with built packages if no_empty_report is set must generate report with built packages if no_empty_report is set
""" """
configuration.set_option("email", "no_empty_report", "yes") configuration.set_option("email", "no_empty_report", "yes")
send_mock = mocker.patch("ahriman.core.report.Email._send") send_mock = mocker.patch("ahriman.core.report.email.Email._send")
report = Email("x86_64", configuration, "email") report = Email("x86_64", configuration, "email")
report.generate([package_ahriman], result) report.generate([package_ahriman], result)

View File

@ -3,7 +3,7 @@ import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report import HTML from ahriman.core.report.html import HTML
from ahriman.models.package import Package from ahriman.models.package import Package

View File

@ -1,5 +1,5 @@
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report import JinjaTemplate from ahriman.core.report.jinja_template import JinjaTemplate
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result

View File

@ -13,7 +13,7 @@ def test_report_failure(configuration: Configuration, mocker: MockerFixture) ->
""" """
must raise ReportFailed on errors must raise ReportFailed on errors
""" """
mocker.patch("ahriman.core.report.HTML.generate", side_effect=Exception()) mocker.patch("ahriman.core.report.html.HTML.generate", side_effect=Exception())
with pytest.raises(ReportFailed): with pytest.raises(ReportFailed):
Report.load("x86_64", configuration, "html").run([], Result()) Report.load("x86_64", configuration, "html").run([], Result())
@ -32,7 +32,7 @@ def test_report_console(configuration: Configuration, result: Result, mocker: Mo
""" """
must generate console report must generate console report
""" """
report_mock = mocker.patch("ahriman.core.report.Console.generate") report_mock = mocker.patch("ahriman.core.report.console.Console.generate")
Report.load("x86_64", configuration, "console").run(result, []) Report.load("x86_64", configuration, "console").run(result, [])
report_mock.assert_called_once_with([], result) report_mock.assert_called_once_with([], result)
@ -41,7 +41,7 @@ def test_report_email(configuration: Configuration, result: Result, mocker: Mock
""" """
must generate email report must generate email report
""" """
report_mock = mocker.patch("ahriman.core.report.Email.generate") report_mock = mocker.patch("ahriman.core.report.email.Email.generate")
Report.load("x86_64", configuration, "email").run(result, []) Report.load("x86_64", configuration, "email").run(result, [])
report_mock.assert_called_once_with([], result) report_mock.assert_called_once_with([], result)
@ -50,7 +50,7 @@ def test_report_html(configuration: Configuration, result: Result, mocker: Mocke
""" """
must generate html report must generate html report
""" """
report_mock = mocker.patch("ahriman.core.report.HTML.generate") report_mock = mocker.patch("ahriman.core.report.html.HTML.generate")
Report.load("x86_64", configuration, "html").run(result, []) Report.load("x86_64", configuration, "html").run(result, [])
report_mock.assert_called_once_with([], result) report_mock.assert_called_once_with([], result)
@ -59,6 +59,6 @@ def test_report_telegram(configuration: Configuration, result: Result, mocker: M
""" """
must generate telegram report must generate telegram report
""" """
report_mock = mocker.patch("ahriman.core.report.Telegram.generate") report_mock = mocker.patch("ahriman.core.report.telegram.Telegram.generate")
Report.load("x86_64", configuration, "telegram").run(result, []) Report.load("x86_64", configuration, "telegram").run(result, [])
report_mock.assert_called_once_with([], result) report_mock.assert_called_once_with([], result)

View File

@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
from unittest import mock from unittest import mock
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.report import Telegram from ahriman.core.report.telegram import Telegram
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.result import Result from ahriman.models.result import Result
@ -20,7 +20,8 @@ def test_send(configuration: Configuration, mocker: MockerFixture) -> None:
report._send("a text") report._send("a text")
request_mock.assert_called_once_with( request_mock.assert_called_once_with(
pytest.helpers.anyvar(str, strict=True), pytest.helpers.anyvar(str, strict=True),
data={"chat_id": pytest.helpers.anyvar(str, strict=True), "text": "a text", "parse_mode": "HTML"}) data={"chat_id": pytest.helpers.anyvar(str, strict=True), "text": "a text", "parse_mode": "HTML"},
timeout=report.timeout)
def test_send_failed(configuration: Configuration, mocker: MockerFixture) -> None: def test_send_failed(configuration: Configuration, mocker: MockerFixture) -> None:
@ -50,7 +51,7 @@ def test_generate(configuration: Configuration, package_ahriman: Package, result
""" """
must generate report must generate report
""" """
send_mock = mocker.patch("ahriman.core.report.Telegram._send") send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
report = Telegram("x86_64", configuration, "telegram") report = Telegram("x86_64", configuration, "telegram")
report.generate([package_ahriman], result) report.generate([package_ahriman], result)
@ -62,7 +63,7 @@ def test_generate_big_text_without_spaces(configuration: Configuration, package_
""" """
must raise ValueError in case if there are no new lines in text must raise ValueError in case if there are no new lines in text
""" """
mocker.patch("ahriman.core.report.JinjaTemplate.make_html", return_value="ab" * 4096) mocker.patch("ahriman.core.report.jinja_template.JinjaTemplate.make_html", return_value="ab" * 4096)
report = Telegram("x86_64", configuration, "telegram") report = Telegram("x86_64", configuration, "telegram")
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -74,8 +75,8 @@ def test_generate_big_text(configuration: Configuration, package_ahriman: Packag
""" """
must generate report with big text must generate report with big text
""" """
mocker.patch("ahriman.core.report.JinjaTemplate.make_html", return_value="a\n" * 4096) mocker.patch("ahriman.core.report.jinja_template.JinjaTemplate.make_html", return_value="a\n" * 4096)
send_mock = mocker.patch("ahriman.core.report.Telegram._send") send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
report = Telegram("x86_64", configuration, "telegram") report = Telegram("x86_64", configuration, "telegram")
report.generate([package_ahriman], result) report.generate([package_ahriman], result)
@ -89,8 +90,8 @@ def test_generate_very_big_text(configuration: Configuration, package_ahriman: P
""" """
must generate report with very big text must generate report with very big text
""" """
mocker.patch("ahriman.core.report.JinjaTemplate.make_html", return_value="ab\n" * 4096) mocker.patch("ahriman.core.report.jinja_template.JinjaTemplate.make_html", return_value="ab\n" * 4096)
send_mock = mocker.patch("ahriman.core.report.Telegram._send") send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
report = Telegram("x86_64", configuration, "telegram") report = Telegram("x86_64", configuration, "telegram")
report.generate([package_ahriman], result) report.generate([package_ahriman], result)
@ -105,7 +106,7 @@ def test_generate_no_empty(configuration: Configuration, package_ahriman: Packag
""" """
must generate report must generate report
""" """
send_mock = mocker.patch("ahriman.core.report.Telegram._send") send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
report = Telegram("x86_64", configuration, "telegram") report = Telegram("x86_64", configuration, "telegram")
report.generate([package_ahriman], Result()) report.generate([package_ahriman], Result())

View File

@ -83,7 +83,9 @@ def test_key_download(gpg: GPG, mocker: MockerFixture) -> None:
requests_mock = mocker.patch("requests.get") requests_mock = mocker.patch("requests.get")
gpg.key_download("pgp.mit.edu", "0xE989490C") gpg.key_download("pgp.mit.edu", "0xE989490C")
requests_mock.assert_called_once_with( requests_mock.assert_called_once_with(
"http://pgp.mit.edu/pks/lookup", params={"op": "get", "options": "mr", "search": "0xE989490C"}) "http://pgp.mit.edu/pks/lookup",
params={"op": "get", "options": "mr", "search": "0xE989490C"},
timeout=gpg.DEFAULT_TIMEOUT)
def test_key_download_failure(gpg: GPG, mocker: MockerFixture) -> None: def test_key_download_failure(gpg: GPG, mocker: MockerFixture) -> None:

View File

@ -51,9 +51,8 @@ def test_get_internal(client: Client) -> None:
""" """
must return dummy status for web service must return dummy status for web service
""" """
expected = InternalStatus(BuildStatus())
actual = client.get_internal() actual = client.get_internal()
actual.status.timestamp = expected.status.timestamp expected = InternalStatus(status=BuildStatus(timestamp=actual.status.timestamp))
assert actual == expected assert actual == expected

View File

@ -164,8 +164,9 @@ def test_get_internal(web_client: WebClient, mocker: MockerFixture) -> None:
""" """
must return web service status must return web service status
""" """
status = InternalStatus(status=BuildStatus(), architecture="x86_64")
response_obj = Response() response_obj = Response()
response_obj._content = json.dumps(InternalStatus(BuildStatus(), architecture="x86_64").view()).encode("utf8") response_obj._content = json.dumps(status.view()).encode("utf8")
response_obj.status_code = 200 response_obj.status_code = 200
requests_mock = mocker.patch("requests.Session.get", return_value=response_obj) requests_mock = mocker.patch("requests.Session.get", return_value=response_obj)

View File

@ -5,7 +5,9 @@ from typing import Any, Dict, List
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.upload import Github, Rsync, S3 from ahriman.core.upload.github import Github
from ahriman.core.upload.rsync import Rsync
from ahriman.core.upload.s3 import S3
_s3_object = namedtuple("s3_object", ["key", "e_tag", "delete"]) _s3_object = namedtuple("s3_object", ["key", "e_tag", "delete"])

View File

@ -6,14 +6,14 @@ from pytest_mock import MockerFixture
from typing import Any, Dict from typing import Any, Dict
from unittest import mock from unittest import mock
from ahriman.core.upload import Github from ahriman.core.upload.github import Github
def test_asset_remove(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None: def test_asset_remove(github: Github, github_release: Dict[str, Any], mocker: MockerFixture) -> None:
""" """
must remove asset from the release must remove asset from the release
""" """
request_mock = mocker.patch("ahriman.core.upload.Github._request") request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
github.asset_remove(github_release, "asset_name") github.asset_remove(github_release, "asset_name")
request_mock.assert_called_once_with("DELETE", "asset_url") request_mock.assert_called_once_with("DELETE", "asset_url")
@ -22,7 +22,7 @@ def test_asset_remove_unknown(github: Github, github_release: Dict[str, Any], mo
""" """
must not fail if no asset found must not fail if no asset found
""" """
request_mock = mocker.patch("ahriman.core.upload.Github._request") request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
github.asset_remove(github_release, "unknown_asset_name") github.asset_remove(github_release, "unknown_asset_name")
request_mock.assert_not_called() request_mock.assert_not_called()
@ -32,8 +32,8 @@ def test_asset_upload(github: Github, github_release: Dict[str, Any], mocker: Mo
must upload asset to the repository must upload asset to the repository
""" """
mocker.patch("pathlib.Path.open", return_value=b"") mocker.patch("pathlib.Path.open", return_value=b"")
request_mock = mocker.patch("ahriman.core.upload.Github._request") request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
remove_mock = mocker.patch("ahriman.core.upload.Github.asset_remove") remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
github.asset_upload(github_release, Path("/root/new.tar.xz")) github.asset_upload(github_release, Path("/root/new.tar.xz"))
request_mock.assert_called_once_with("POST", "upload_url", params={"name": "new.tar.xz"}, request_mock.assert_called_once_with("POST", "upload_url", params={"name": "new.tar.xz"},
@ -46,8 +46,8 @@ def test_asset_upload_with_removal(github: Github, github_release: Dict[str, Any
must remove existing file before upload must remove existing file before upload
""" """
mocker.patch("pathlib.Path.open", return_value=b"") mocker.patch("pathlib.Path.open", return_value=b"")
mocker.patch("ahriman.core.upload.Github._request") mocker.patch("ahriman.core.upload.github.Github._request")
remove_mock = mocker.patch("ahriman.core.upload.Github.asset_remove") remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
github.asset_upload(github_release, Path("asset_name")) github.asset_upload(github_release, Path("asset_name"))
github.asset_upload(github_release, Path("/root/asset_name")) github.asset_upload(github_release, Path("/root/asset_name"))
@ -62,9 +62,9 @@ def test_asset_upload_empty_mimetype(github: Github, github_release: Dict[str, A
must upload asset to the repository with empty mime type if cannot guess it must upload asset to the repository with empty mime type if cannot guess it
""" """
mocker.patch("pathlib.Path.open", return_value=b"") mocker.patch("pathlib.Path.open", return_value=b"")
mocker.patch("ahriman.core.upload.Github.asset_remove") mocker.patch("ahriman.core.upload.github.Github.asset_remove")
mocker.patch("mimetypes.guess_type", return_value=(None, None)) mocker.patch("mimetypes.guess_type", return_value=(None, None))
request_mock = mocker.patch("ahriman.core.upload.Github._request") request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
github.asset_upload(github_release, Path("/root/new.tar.xz")) github.asset_upload(github_release, Path("/root/new.tar.xz"))
request_mock.assert_called_once_with("POST", "upload_url", params={"name": "new.tar.xz"}, request_mock.assert_called_once_with("POST", "upload_url", params={"name": "new.tar.xz"},
@ -84,7 +84,7 @@ def test_files_remove(github: Github, github_release: Dict[str, Any], mocker: Mo
""" """
must remove files from the remote must remove files from the remote
""" """
remove_mock = mocker.patch("ahriman.core.upload.Github.asset_remove") remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
github.files_remove(github_release, {Path("a"): "a"}, {"a": "a", "b": "b"}) github.files_remove(github_release, {Path("a"): "a"}, {"a": "a", "b": "b"})
remove_mock.assert_called_once_with(github_release, "b") remove_mock.assert_called_once_with(github_release, "b")
@ -93,7 +93,7 @@ def test_files_remove_empty(github: Github, github_release: Dict[str, Any], mock
""" """
must remove nothing if nothing changed must remove nothing if nothing changed
""" """
remove_mock = mocker.patch("ahriman.core.upload.Github.asset_remove") remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
github.files_remove(github_release, {Path("a"): "a"}, {"a": "a"}) github.files_remove(github_release, {Path("a"): "a"}, {"a": "a"})
remove_mock.assert_not_called() remove_mock.assert_not_called()
@ -102,7 +102,7 @@ def test_files_upload(github: Github, github_release: Dict[str, Any], mocker: Mo
""" """
must upload files to the remote must upload files to the remote
""" """
upload_mock = mocker.patch("ahriman.core.upload.Github.asset_upload") upload_mock = mocker.patch("ahriman.core.upload.github.Github.asset_upload")
github.files_upload(github_release, {Path("a"): "a", Path("b"): "c", Path("c"): "c"}, {"a": "a", "b": "b"}) github.files_upload(github_release, {Path("a"): "a", Path("b"): "c", Path("c"): "c"}, {"a": "a", "b": "b"})
upload_mock.assert_has_calls([ upload_mock.assert_has_calls([
mock.call(github_release, Path("b")), mock.call(github_release, Path("b")),
@ -114,7 +114,7 @@ def test_files_upload_empty(github: Github, github_release: Dict[str, Any], mock
""" """
must upload nothing if nothing changed must upload nothing if nothing changed
""" """
upload_mock = mocker.patch("ahriman.core.upload.Github.asset_upload") upload_mock = mocker.patch("ahriman.core.upload.github.Github.asset_upload")
github.files_upload(github_release, {Path("a"): "a"}, {"a": "a"}) github.files_upload(github_release, {Path("a"): "a"}, {"a": "a"})
upload_mock.assert_not_called() upload_mock.assert_not_called()
@ -123,7 +123,7 @@ def test_release_create(github: Github, mocker: MockerFixture) -> None:
""" """
must create release must create release
""" """
request_mock = mocker.patch("ahriman.core.upload.Github._request") request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
github.release_create() github.release_create()
request_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), request_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
json={"tag_name": github.architecture, "name": github.architecture}) json={"tag_name": github.architecture, "name": github.architecture})
@ -133,7 +133,7 @@ def test_release_get(github: Github, mocker: MockerFixture) -> None:
""" """
must get release must get release
""" """
request_mock = mocker.patch("ahriman.core.upload.Github._request") request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
github.release_get() github.release_get()
request_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True)) request_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True))
@ -144,7 +144,7 @@ def test_release_get_empty(github: Github, mocker: MockerFixture) -> None:
""" """
response = requests.Response() response = requests.Response()
response.status_code = 404 response.status_code = 404
mocker.patch("ahriman.core.upload.Github._request", side_effect=requests.HTTPError(response=response)) mocker.patch("ahriman.core.upload.github.Github._request", side_effect=requests.HTTPError(response=response))
assert github.release_get() is None assert github.release_get() is None
@ -152,7 +152,7 @@ def test_release_get_exception(github: Github, mocker: MockerFixture) -> None:
""" """
must re-raise non HTTPError exception must re-raise non HTTPError exception
""" """
mocker.patch("ahriman.core.upload.Github._request", side_effect=Exception()) mocker.patch("ahriman.core.upload.github.Github._request", side_effect=Exception())
with pytest.raises(Exception): with pytest.raises(Exception):
github.release_get() github.release_get()
@ -162,7 +162,7 @@ def test_release_get_exception_http_error(github: Github, mocker: MockerFixture)
must re-raise HTTPError exception with code differs from 404 must re-raise HTTPError exception with code differs from 404
""" """
exception = requests.HTTPError(response=requests.Response()) exception = requests.HTTPError(response=requests.Response())
mocker.patch("ahriman.core.upload.Github._request", side_effect=exception) mocker.patch("ahriman.core.upload.github.Github._request", side_effect=exception)
with pytest.raises(requests.HTTPError): with pytest.raises(requests.HTTPError):
github.release_get() github.release_get()
@ -171,7 +171,7 @@ def test_release_update(github: Github, github_release: Dict[str, Any], mocker:
""" """
must update release must update release
""" """
request_mock = mocker.patch("ahriman.core.upload.Github._request") request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
github.release_update(github_release, "body") github.release_update(github_release, "body")
request_mock.assert_called_once_with("POST", "release_url", json={"body": "body"}) request_mock.assert_called_once_with("POST", "release_url", json={"body": "body"})
@ -180,12 +180,12 @@ def test_release_sync(github: Github, mocker: MockerFixture) -> None:
""" """
must run sync command must run sync command
""" """
release_get_mock = mocker.patch("ahriman.core.upload.Github.release_get", return_value={}) release_get_mock = mocker.patch("ahriman.core.upload.github.Github.release_get", return_value={})
get_hashes_mock = mocker.patch("ahriman.core.upload.Github.get_hashes", return_value={}) get_hashes_mock = mocker.patch("ahriman.core.upload.github.Github.get_hashes", return_value={})
get_local_files_mock = mocker.patch("ahriman.core.upload.Github.get_local_files", return_value={}) get_local_files_mock = mocker.patch("ahriman.core.upload.github.Github.get_local_files", return_value={})
files_upload_mock = mocker.patch("ahriman.core.upload.Github.files_upload") files_upload_mock = mocker.patch("ahriman.core.upload.github.Github.files_upload")
files_remove_mock = mocker.patch("ahriman.core.upload.Github.files_remove") files_remove_mock = mocker.patch("ahriman.core.upload.github.Github.files_remove")
release_update_mock = mocker.patch("ahriman.core.upload.Github.release_update") release_update_mock = mocker.patch("ahriman.core.upload.github.Github.release_update")
github.sync(Path("local"), []) github.sync(Path("local"), [])
release_get_mock.assert_called_once_with() release_get_mock.assert_called_once_with()
@ -200,13 +200,13 @@ def test_release_sync_create_release(github: Github, mocker: MockerFixture) -> N
""" """
must create release in case if it does not exist must create release in case if it does not exist
""" """
mocker.patch("ahriman.core.upload.Github.release_get", return_value=None) mocker.patch("ahriman.core.upload.github.Github.release_get", return_value=None)
mocker.patch("ahriman.core.upload.Github.get_hashes") mocker.patch("ahriman.core.upload.github.Github.get_hashes")
mocker.patch("ahriman.core.upload.Github.get_local_files") mocker.patch("ahriman.core.upload.github.Github.get_local_files")
mocker.patch("ahriman.core.upload.Github.files_upload") mocker.patch("ahriman.core.upload.github.Github.files_upload")
mocker.patch("ahriman.core.upload.Github.files_remove") mocker.patch("ahriman.core.upload.github.Github.files_remove")
mocker.patch("ahriman.core.upload.Github.release_update") mocker.patch("ahriman.core.upload.github.Github.release_update")
release_create_mock = mocker.patch("ahriman.core.upload.Github.release_create") release_create_mock = mocker.patch("ahriman.core.upload.github.Github.release_create")
github.sync(Path("local"), []) github.sync(Path("local"), [])
release_create_mock.assert_called_once_with() release_create_mock.assert_called_once_with()

View File

@ -5,7 +5,8 @@ from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.core.upload import Github, HttpUpload from ahriman.core.upload.github import Github
from ahriman.core.upload.http_upload import HttpUpload
def test_calculate_hash_empty(resource_path_root: Path) -> None: def test_calculate_hash_empty(resource_path_root: Path) -> None:
@ -49,7 +50,7 @@ def test_request(github: Github, mocker: MockerFixture) -> None:
request_mock = mocker.patch("requests.request", return_value=response_mock) request_mock = mocker.patch("requests.request", return_value=response_mock)
github._request("GET", "url", arg="arg") github._request("GET", "url", arg="arg")
request_mock.assert_called_once_with("GET", "url", auth=github.auth, arg="arg") request_mock.assert_called_once_with("GET", "url", auth=github.auth, timeout=github.timeout, arg="arg")
response_mock.raise_for_status.assert_called_once_with() response_mock.raise_for_status.assert_called_once_with()

View File

@ -1,13 +1,13 @@
from pathlib import Path from pathlib import Path
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from ahriman.core.upload import Rsync from ahriman.core.upload.rsync import Rsync
def test_sync(rsync: Rsync, mocker: MockerFixture) -> None: def test_sync(rsync: Rsync, mocker: MockerFixture) -> None:
""" """
must run sync command must run sync command
""" """
check_output_mock = mocker.patch("ahriman.core.upload.Rsync._check_output") check_output_mock = mocker.patch("ahriman.core.upload.rsync.Rsync._check_output")
rsync.sync(Path("path"), []) rsync.sync(Path("path"), [])
check_output_mock.assert_called_once_with(*rsync.command, "path", rsync.remote, exception=None, logger=rsync.logger) check_output_mock.assert_called_once_with(*rsync.command, "path", rsync.remote, exception=None, logger=rsync.logger)

View File

@ -4,7 +4,7 @@ from typing import Any, List, Optional, Tuple
from unittest import mock from unittest import mock
from unittest.mock import MagicMock from unittest.mock import MagicMock
from ahriman.core.upload import S3 from ahriman.core.upload.s3 import S3
_chunk_size = 8 * 1024 * 1024 _chunk_size = 8 * 1024 * 1024
@ -104,10 +104,10 @@ def test_sync(s3: S3, mocker: MockerFixture) -> None:
""" """
must run sync command must run sync command
""" """
local_files_mock = mocker.patch("ahriman.core.upload.S3.get_local_files", return_value=["a"]) local_files_mock = mocker.patch("ahriman.core.upload.s3.S3.get_local_files", return_value=["a"])
remote_objects_mock = mocker.patch("ahriman.core.upload.S3.get_remote_objects", return_value=["b"]) remote_objects_mock = mocker.patch("ahriman.core.upload.s3.S3.get_remote_objects", return_value=["b"])
remove_files_mock = mocker.patch("ahriman.core.upload.S3.files_remove") remove_files_mock = mocker.patch("ahriman.core.upload.s3.S3.files_remove")
upload_files_mock = mocker.patch("ahriman.core.upload.S3.files_upload") upload_files_mock = mocker.patch("ahriman.core.upload.s3.S3.files_upload")
s3.sync(Path("root"), []) s3.sync(Path("root"), [])
remote_objects_mock.assert_called_once_with() remote_objects_mock.assert_called_once_with()

View File

@ -13,7 +13,7 @@ def test_upload_failure(configuration: Configuration, mocker: MockerFixture) ->
""" """
must raise SyncFailed on errors must raise SyncFailed on errors
""" """
mocker.patch("ahriman.core.upload.Rsync.sync", side_effect=Exception()) mocker.patch("ahriman.core.upload.rsync.Rsync.sync", side_effect=Exception())
with pytest.raises(SyncFailed): with pytest.raises(SyncFailed):
Upload.load("x86_64", configuration, "rsync").run(Path("path"), []) Upload.load("x86_64", configuration, "rsync").run(Path("path"), [])
@ -32,7 +32,7 @@ def test_upload_rsync(configuration: Configuration, mocker: MockerFixture) -> No
""" """
must upload via rsync must upload via rsync
""" """
upload_mock = mocker.patch("ahriman.core.upload.Rsync.sync") upload_mock = mocker.patch("ahriman.core.upload.rsync.Rsync.sync")
Upload.load("x86_64", configuration, "rsync").run(Path("path"), []) Upload.load("x86_64", configuration, "rsync").run(Path("path"), [])
upload_mock.assert_called_once_with(Path("path"), []) upload_mock.assert_called_once_with(Path("path"), [])
@ -41,7 +41,7 @@ def test_upload_s3(configuration: Configuration, mocker: MockerFixture) -> None:
""" """
must upload via s3 must upload via s3
""" """
upload_mock = mocker.patch("ahriman.core.upload.S3.sync") upload_mock = mocker.patch("ahriman.core.upload.s3.S3.sync")
Upload.load("x86_64", configuration, "customs3").run(Path("path"), []) Upload.load("x86_64", configuration, "customs3").run(Path("path"), [])
upload_mock.assert_called_once_with(Path("path"), []) upload_mock.assert_called_once_with(Path("path"), [])
@ -50,6 +50,6 @@ def test_upload_github(configuration: Configuration, mocker: MockerFixture) -> N
""" """
must upload via github must upload via github
""" """
upload_mock = mocker.patch("ahriman.core.upload.Github.sync") upload_mock = mocker.patch("ahriman.core.upload.github.Github.sync")
Upload.load("x86_64", configuration, "github").run(Path("path"), []) Upload.load("x86_64", configuration, "github").run(Path("path"), [])
upload_mock.assert_called_once_with(Path("path"), []) upload_mock.assert_called_once_with(Path("path"), [])

View File

@ -61,11 +61,11 @@ def test_from_pacman(pyalpm_package_ahriman: pyalpm.Package, aur_package_ahriman
""" """
model = AURPackage.from_pacman(pyalpm_package_ahriman) model = AURPackage.from_pacman(pyalpm_package_ahriman)
# some fields are missing so we are changing them # some fields are missing so we are changing them
model.id = aur_package_ahriman.id object.__setattr__(model, "id", aur_package_ahriman.id)
model.package_base_id = aur_package_ahriman.package_base_id object.__setattr__(model, "package_base_id", aur_package_ahriman.package_base_id)
model.first_submitted = aur_package_ahriman.first_submitted object.__setattr__(model, "first_submitted", aur_package_ahriman.first_submitted)
model.url_path = aur_package_ahriman.url_path object.__setattr__(model, "url_path", aur_package_ahriman.url_path)
model.maintainer = aur_package_ahriman.maintainer object.__setattr__(model, "maintainer", aur_package_ahriman.maintainer)
assert model == aur_package_ahriman assert model == aur_package_ahriman

View File

@ -1,4 +1,3 @@
import datetime
import time import time
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
@ -53,43 +52,3 @@ def test_build_status_eq(build_status_failed: BuildStatus) -> None:
""" """
other = BuildStatus.from_json(build_status_failed.view()) other = BuildStatus.from_json(build_status_failed.view())
assert other == build_status_failed assert other == build_status_failed
def test_build_status_eq_self(build_status_failed: BuildStatus) -> None:
"""
must be equal itself
"""
assert build_status_failed == build_status_failed
def test_build_status_ne_by_status(build_status_failed: BuildStatus) -> None:
"""
must be not equal by status
"""
other = BuildStatus.from_json(build_status_failed.view())
other.status = BuildStatusEnum.Success
assert build_status_failed != other
def test_build_status_ne_by_timestamp(build_status_failed: BuildStatus) -> None:
"""
must be not equal by timestamp
"""
other = BuildStatus.from_json(build_status_failed.view())
other.timestamp = datetime.datetime.utcnow().timestamp()
assert build_status_failed != other
def test_build_status_ne_other(build_status_failed: BuildStatus) -> None:
"""
must be not equal to random object
"""
assert build_status_failed != object()
def test_build_status_repr(build_status_failed: BuildStatus) -> None:
"""
must return string in __repr__ function
"""
assert build_status_failed.__repr__()
assert isinstance(build_status_failed.__repr__(), str)

View File

@ -154,7 +154,7 @@ def test_from_official(package_ahriman: Package, aur_package_ahriman: AURPackage
def test_dependencies_failed(mocker: MockerFixture) -> None: def test_dependencies_failed(mocker: MockerFixture) -> None:
""" """
must raise exception if there are errors during srcinfo load must raise exception if there are errors during srcinfo load for dependencies
""" """
mocker.patch("ahriman.models.package.Package._check_output", return_value="") mocker.patch("ahriman.models.package.Package._check_output", return_value="")
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"])) mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
@ -183,6 +183,27 @@ def test_dependencies_with_version_and_overlap(mocker: MockerFixture, resource_p
assert Package.dependencies(Path("path")) == {"glibc", "doxygen", "binutils", "git", "libmpc", "python", "zstd"} assert Package.dependencies(Path("path")) == {"glibc", "doxygen", "binutils", "git", "libmpc", "python", "zstd"}
def test_supported_architectures(mocker: MockerFixture, resource_path_root: Path) -> None:
"""
must generate list of available architectures
"""
srcinfo = (resource_path_root / "models" / "package_yay_srcinfo").read_text()
mocker.patch("ahriman.models.package.Package._check_output", return_value=srcinfo)
assert Package.supported_architectures(Path("path")) == \
{"i686", "pentium4", "x86_64", "arm", "armv7h", "armv6h", "aarch64"}
def test_supported_architectures_failed(mocker: MockerFixture) -> None:
"""
must raise exception if there are errors during srcinfo load for architectures
"""
mocker.patch("ahriman.models.package.Package._check_output", return_value="")
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
with pytest.raises(InvalidPackageInfo):
Package.supported_architectures(Path("path"))
def test_actual_version(package_ahriman: Package, repository_paths: RepositoryPaths) -> None: def test_actual_version(package_ahriman: Package, repository_paths: RepositoryPaths) -> None:
""" """
must return same actual_version as version is must return same actual_version as version is

View File

@ -0,0 +1,61 @@
from pathlib import Path
from pytest_mock import MockerFixture
from unittest.mock import MagicMock, call
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_is_function() -> None:
"""
must correctly define key as function
"""
assert not PkgbuildPatch("key", "value").is_function
assert PkgbuildPatch("key()", "value").is_function
def test_quote() -> None:
"""
must quote strings if unsafe flag is not set
"""
assert PkgbuildPatch("key", "value").quote("value") == """value"""
assert PkgbuildPatch("key", "va'lue").quote("va'lue") == """'va'"'"'lue'"""
assert PkgbuildPatch("key", "va'lue", unsafe=True).quote("va'lue") == """va'lue"""
def test_serialize() -> None:
"""
must correctly serialize string values
"""
assert PkgbuildPatch("key", "value").serialize() == "key=value"
assert PkgbuildPatch("key", "42").serialize() == "key=42"
assert PkgbuildPatch("key", "4'2").serialize() == """key='4'"'"'2'"""
assert PkgbuildPatch("key", "4'2", unsafe=True).serialize() == "key=4'2"
def test_serialize_function() -> None:
"""
must correctly serialize function values
"""
assert PkgbuildPatch("key()", "{ value }", unsafe=True).serialize() == "key() { value }"
def test_serialize_list() -> None:
"""
must correctly serialize list values
"""
assert PkgbuildPatch("arch", ["i686", "x86_64"]).serialize() == """arch=(i686 x86_64)"""
assert PkgbuildPatch("key", ["val'ue", "val\"ue2"]).serialize() == """key=('val'"'"'ue' 'val"ue2')"""
assert PkgbuildPatch("key", ["val'ue", "val\"ue2"], unsafe=True).serialize() == """key=(val'ue val"ue2)"""
def test_write(mocker: MockerFixture) -> None:
"""
must write serialized value to the file
"""
file_mock = MagicMock()
open_mock = mocker.patch("pathlib.Path.open")
open_mock.return_value.__enter__.return_value = file_mock
PkgbuildPatch("key", "value").write(Path("PKGBUILD"))
open_mock.assert_called_once_with("a")
file_mock.write.assert_has_calls([call("\n"), call("""key=value"""), call("\n")])

View File

@ -69,7 +69,7 @@ def test_chown(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None
""" """
must correctly set owner for the directory must correctly set owner for the directory
""" """
repository_paths.owner = _get_owner(repository_paths.root, same=False) object.__setattr__(repository_paths, "owner", _get_owner(repository_paths.root, same=False))
mocker.patch.object(RepositoryPaths, "root_owner", (42, 42)) mocker.patch.object(RepositoryPaths, "root_owner", (42, 42))
chown_mock = mocker.patch("os.chown") chown_mock = mocker.patch("os.chown")
@ -82,7 +82,7 @@ def test_chown_parent(repository_paths: RepositoryPaths, mocker: MockerFixture)
""" """
must correctly set owner for the directory including parents must correctly set owner for the directory including parents
""" """
repository_paths.owner = _get_owner(repository_paths.root, same=False) object.__setattr__(repository_paths, "owner", _get_owner(repository_paths.root, same=False))
mocker.patch.object(RepositoryPaths, "root_owner", (42, 42)) mocker.patch.object(RepositoryPaths, "root_owner", (42, 42))
chown_mock = mocker.patch("os.chown") chown_mock = mocker.patch("os.chown")
@ -98,7 +98,7 @@ def test_chown_skip(repository_paths: RepositoryPaths, mocker: MockerFixture) ->
""" """
must skip ownership set in case if it is same as root must skip ownership set in case if it is same as root
""" """
repository_paths.owner = _get_owner(repository_paths.root, same=True) object.__setattr__(repository_paths, "owner", _get_owner(repository_paths.root, same=True))
mocker.patch.object(RepositoryPaths, "root_owner", (42, 42)) mocker.patch.object(RepositoryPaths, "root_owner", (42, 42))
chown_mock = mocker.patch("os.chown") chown_mock = mocker.patch("os.chown")

View File

@ -1,3 +1,5 @@
from dataclasses import replace
from ahriman.models.user import User from ahriman.models.user import User
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
@ -6,10 +8,10 @@ def test_from_option(user: User) -> None:
""" """
must generate user from options must generate user from options
""" """
user.access = UserAccess.Read user = replace(user, access=UserAccess.Read)
assert User.from_option(user.username, user.password) == user assert User.from_option(user.username, user.password) == user
# default is read access # default is read access
user.access = UserAccess.Full user = replace(user, access=UserAccess.Full)
assert User.from_option(user.username, user.password) != user assert User.from_option(user.username, user.password) != user
assert User.from_option(user.username, user.password, user.access) == user assert User.from_option(user.username, user.password, user.access) == user
@ -40,7 +42,7 @@ def test_check_credentials_empty_hash(user: User) -> None:
""" """
current_password = user.password current_password = user.password
assert not user.check_credentials(current_password, "salt") assert not user.check_credentials(current_password, "salt")
user.password = "" user = replace(user, password="")
assert not user.check_credentials(current_password, "salt") assert not user.check_credentials(current_password, "salt")
@ -48,9 +50,9 @@ def test_hash_password_empty_hash(user: User) -> None:
""" """
must return empty string after hash in case if password not set must return empty string after hash in case if password not set
""" """
user.password = "" user = replace(user, password="")
assert user.hash_password("salt") == user assert user.hash_password("salt") == user
user.password = None user = replace(user, password=None)
assert user.hash_password("salt") == user assert user.hash_password("salt") == user
@ -71,7 +73,7 @@ def test_verify_access_read(user: User) -> None:
""" """
user with read access must be able to only request read user with read access must be able to only request read
""" """
user.access = UserAccess.Read user = replace(user, access=UserAccess.Read)
assert user.verify_access(UserAccess.Read) assert user.verify_access(UserAccess.Read)
assert not user.verify_access(UserAccess.Full) assert not user.verify_access(UserAccess.Full)
@ -80,7 +82,7 @@ def test_verify_access_write(user: User) -> None:
""" """
user with write access must be able to do anything user with write access must be able to do anything
""" """
user.access = UserAccess.Full user = replace(user, access=UserAccess.Full)
assert user.verify_access(UserAccess.Read) assert user.verify_access(UserAccess.Read)
assert user.verify_access(UserAccess.Full) assert user.verify_access(UserAccess.Full)

View File

@ -13,7 +13,7 @@ def test_from_identity_expired(user_identity: UserIdentity) -> None:
""" """
must construct None from expired identity must construct None from expired identity
""" """
user_identity.expire_at -= 60 user_identity = UserIdentity(username=user_identity.username, expire_at=user_identity.expire_at - 60)
assert UserIdentity.from_identity(f"{user_identity.username} {user_identity.expire_at}") is None assert UserIdentity.from_identity(f"{user_identity.username} {user_identity.expire_at}") is None
@ -53,7 +53,7 @@ def test_is_expired(user_identity: UserIdentity) -> None:
""" """
assert not user_identity.is_expired() assert not user_identity.is_expired()
user_identity.expire_at -= 60 user_identity = UserIdentity(username=user_identity.username, expire_at=user_identity.expire_at - 60)
assert user_identity.is_expired() assert user_identity.is_expired()