Compare commits

...

2 Commits

Author SHA1 Message Date
7769a4a6e0 Release 2.18.3 2025-06-20 17:20:19 +03:00
066d1b1dde refactor: rework few tests and build system
This commit includes the following changes
* Bump github actions
* Update tests github action to check documentation and streamline
  process
* Update test cases to use temporary directories as roots
* Simplify tox.ini
2025-06-20 17:04:57 +03:00
24 changed files with 1444 additions and 1388 deletions

View File

@ -1,6 +0,0 @@
skips:
- B101
- B104
- B105
- B106
- B404

View File

@ -21,18 +21,18 @@ jobs:
packages: write packages: write
steps: steps:
- uses: docker/setup-qemu-action@v2 - uses: docker/setup-qemu-action@v3
- uses: docker/setup-buildx-action@v2 - uses: docker/setup-buildx-action@v3
- name: Login to docker hub - name: Login to docker hub
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
username: ${{ secrets.DOCKERHUB_USERNAME }} username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }} password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to github container registry - name: Login to github container registry
uses: docker/login-action@v2 uses: docker/login-action@v3
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.repository_owner }} username: ${{ github.repository_owner }}
@ -40,7 +40,7 @@ jobs:
- name: Extract docker metadata - name: Extract docker metadata
id: meta id: meta
uses: docker/metadata-action@v3 uses: docker/metadata-action@v5
with: with:
images: | images: |
arcan1s/ahriman arcan1s/ahriman
@ -50,7 +50,7 @@ jobs:
type=edge type=edge
- name: Build an image and push - name: Build an image and push
uses: docker/build-push-action@v4 uses: docker/build-push-action@v6
with: with:
file: docker/Dockerfile file: docker/Dockerfile
push: true push: true

View File

@ -37,8 +37,6 @@ jobs:
- repo:/var/lib/ahriman - repo:/var/lib/ahriman
steps: steps:
- uses: actions/checkout@v3
- run: pacman -Sy - run: pacman -Sy
- name: Init repository - name: Init repository

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Extract version - name: Extract version
id: version id: version
@ -27,8 +27,7 @@ jobs:
token: ${{ secrets.GITHUB_TOKEN }} token: ${{ secrets.GITHUB_TOKEN }}
filter: 'Release \d+\.\d+\.\d+' filter: 'Release \d+\.\d+\.\d+'
- name: Install dependencies - uses: ConorMacBride/install-package@v1.1.0
uses: ConorMacBride/install-package@v1.1.0
with: with:
apt: tox apt: tox
@ -38,7 +37,7 @@ jobs:
VERSION: ${{ steps.version.outputs.VERSION }} VERSION: ${{ steps.version.outputs.VERSION }}
- name: Publish release - name: Publish release
uses: softprops/action-gh-release@v1 uses: softprops/action-gh-release@v2
with: with:
body: | body: |
${{ steps.changelog.outputs.compareurl }} ${{ steps.changelog.outputs.compareurl }}

View File

@ -24,7 +24,7 @@ jobs:
- ${{ github.workspace }}:/build - ${{ github.workspace }}:/build
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup the minimal service in arch linux container - name: Setup the minimal service in arch linux container
run: .github/workflows/setup.sh minimal run: .github/workflows/setup.sh minimal
@ -40,7 +40,7 @@ jobs:
options: --privileged -w /build options: --privileged -w /build
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v4
- name: Setup the service in arch linux container - name: Setup the service in arch linux container
run: .github/workflows/setup.sh run: .github/workflows/setup.sh

View File

@ -1,10 +0,0 @@
#!/bin/bash
# Install dependencies and run test in container
set -ex
# install dependencies
pacman --noconfirm -Syyu base-devel python-tox
# run test and check targets
tox

View File

@ -26,7 +26,16 @@ jobs:
- ${{ github.workspace }}:/build - ${{ github.workspace }}:/build
steps: steps:
- uses: actions/checkout@v3 - run: pacman --noconfirm -Syu base-devel git python-tox
- name: Run check and tests in arch linux container - run: git config --global --add safe.directory *
run: .github/workflows/tests.sh
- uses: actions/checkout@v4
- name: Run check and tests
run: tox
- name: Generate documentation and check if there are untracked changes
run: |
tox -e docs
[ -z "$(git status --porcelain docs/*.rst)" ]

File diff suppressed because it is too large Load Diff

View File

@ -413,10 +413,11 @@ Web application
Web application requires the following python packages to be installed: Web application requires the following python packages to be installed:
* Core part requires ``aiohttp`` (application itself), ``aiohttp_jinja2`` and ``Jinja2`` (HTML generation from templates). * Core part requires ``aiohttp`` (application itself), ``aiohttp_jinja2`` and ``Jinja2`` (HTML generation from templates).
* Additional web features also require ``aiohttp-apispec`` (autogenerated documentation), ``aiohttp_cors`` (CORS support, required by documentation). * Additional web features also require ``aiohttp-apispec`` (autogenerated documentation, optional), ``aiohttp_cors`` (CORS support, required by documentation).
* In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``. * In addition, authorization feature requires ``aiohttp_security``, ``aiohttp_session`` and ``cryptography``.
* In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library. * In addition to base authorization dependencies, OAuth2 also requires ``aioauth-client`` library.
* In addition if you would like to disable authorization for local access (recommended way in order to run the application itself with reporting support), the ``requests-unixsocket2`` library is required. * In addition if you would like to disable authorization for local access (recommended way in order to run the application itself with reporting support), the ``requests-unixsocket2`` library is required.
* Application metrics will be automatically enabled after installing ``aiohttp-openmetrics`` package.
Middlewares Middlewares
^^^^^^^^^^^ ^^^^^^^^^^^

View File

@ -1,36 +1,36 @@
# This file was autogenerated by uv via the following command: # This file was autogenerated by uv via the following command:
# uv pip compile --group ../pyproject.toml:docs --extra s3 --extra validator --extra web --output-file ../docs/requirements.txt ../pyproject.toml # uv pip compile --group pyproject.toml:docs --extra s3 --extra validator --extra web --output-file docs/requirements.txt pyproject.toml
aiohappyeyeballs==2.6.1 aiohappyeyeballs==2.6.1
# via aiohttp # via aiohttp
aiohttp==3.11.18 aiohttp==3.11.18
# via # via
# ahriman (../pyproject.toml) # ahriman (pyproject.toml)
# aiohttp-cors # aiohttp-cors
# aiohttp-jinja2 # aiohttp-jinja2
aiohttp-cors==0.8.1 aiohttp-cors==0.8.1
# via ahriman (../pyproject.toml) # via ahriman (pyproject.toml)
aiohttp-jinja2==1.6 aiohttp-jinja2==1.6
# via ahriman (../pyproject.toml) # via ahriman (pyproject.toml)
aiosignal==1.3.2 aiosignal==1.3.2
# via aiohttp # via aiohttp
alabaster==1.0.0 alabaster==1.0.0
# via sphinx # via sphinx
argparse-manpage==4.6 argparse-manpage==4.6
# via ahriman (../pyproject.toml:docs) # via ahriman (pyproject.toml:docs)
attrs==25.3.0 attrs==25.3.0
# via aiohttp # via aiohttp
babel==2.17.0 babel==2.17.0
# via sphinx # via sphinx
bcrypt==4.3.0 bcrypt==4.3.0
# via ahriman (../pyproject.toml) # via ahriman (pyproject.toml)
boto3==1.38.11 boto3==1.38.11
# via ahriman (../pyproject.toml) # via ahriman (pyproject.toml)
botocore==1.38.11 botocore==1.38.11
# via # via
# boto3 # boto3
# s3transfer # s3transfer
cerberus==1.3.7 cerberus==1.3.7
# via ahriman (../pyproject.toml) # via ahriman (pyproject.toml)
certifi==2025.4.26 certifi==2025.4.26
# via requests # via requests
charset-normalizer==3.4.2 charset-normalizer==3.4.2
@ -51,7 +51,7 @@ idna==3.10
imagesize==1.4.1 imagesize==1.4.1
# via sphinx # via sphinx
inflection==0.5.1 inflection==0.5.1
# via ahriman (../pyproject.toml) # via ahriman (pyproject.toml)
jinja2==3.1.6 jinja2==3.1.6
# via # via
# aiohttp-jinja2 # aiohttp-jinja2
@ -73,37 +73,37 @@ propcache==0.3.1
# aiohttp # aiohttp
# yarl # yarl
pydeps==3.0.1 pydeps==3.0.1
# via ahriman (../pyproject.toml:docs) # via ahriman (pyproject.toml:docs)
pyelftools==0.32 pyelftools==0.32
# via ahriman (../pyproject.toml) # via ahriman (pyproject.toml)
pygments==2.19.1 pygments==2.19.1
# via sphinx # via sphinx
python-dateutil==2.9.0.post0 python-dateutil==2.9.0.post0
# via botocore # via botocore
requests==2.32.3 requests==2.32.3
# via # via
# ahriman (../pyproject.toml) # ahriman (pyproject.toml)
# sphinx # sphinx
roman-numerals-py==3.1.0 roman-numerals-py==3.1.0
# via sphinx # via sphinx
s3transfer==0.12.0 s3transfer==0.12.0
# via boto3 # via boto3
shtab==1.7.2 shtab==1.7.2
# via ahriman (../pyproject.toml:docs) # via ahriman (pyproject.toml:docs)
six==1.17.0 six==1.17.0
# via python-dateutil # via python-dateutil
snowballstemmer==3.0.0.1 snowballstemmer==3.0.0.1
# via sphinx # via sphinx
sphinx==8.2.3 sphinx==8.2.3
# via # via
# ahriman (../pyproject.toml:docs) # ahriman (pyproject.toml:docs)
# sphinx-argparse # sphinx-argparse
# sphinx-rtd-theme # sphinx-rtd-theme
# sphinxcontrib-jquery # sphinxcontrib-jquery
sphinx-argparse==0.5.2 sphinx-argparse==0.5.2
# via ahriman (../pyproject.toml:docs) # via ahriman (pyproject.toml:docs)
sphinx-rtd-theme==3.0.2 sphinx-rtd-theme==3.0.2
# via ahriman (../pyproject.toml:docs) # via ahriman (pyproject.toml:docs)
sphinxcontrib-applehelp==2.0.0 sphinxcontrib-applehelp==2.0.0
# via sphinx # via sphinx
sphinxcontrib-devhelp==2.0.0 sphinxcontrib-devhelp==2.0.0

View File

@ -2,7 +2,7 @@
pkgbase='ahriman' pkgbase='ahriman'
pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web') pkgname=('ahriman' 'ahriman-core' 'ahriman-triggers' 'ahriman-web')
pkgver=2.18.2 pkgver=2.18.3
pkgrel=1 pkgrel=1
pkgdesc="ArcH linux ReposItory MANager" pkgdesc="ArcH linux ReposItory MANager"
arch=('any') arch=('any')

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.18.2" __version__ = "2.18.3"

View File

@ -210,6 +210,17 @@ class Configuration(configparser.RawConfigParser):
raise InitializeError("Configuration path and/or repository id are not set") raise InitializeError("Configuration path and/or repository id are not set")
return self.path, self.repository_id return self.path, self.repository_id
def copy_from(self, configuration: Self) -> None:
"""
copy values from another instance overriding existing
Args:
configuration(Self): configuration instance to merge from
"""
for section in configuration.sections():
for key, value in configuration.items(section):
self.set_option(section, key, value)
def dump(self) -> dict[str, dict[str, str]]: def dump(self) -> dict[str, dict[str, str]]:
""" """
dump configuration to dictionary dump configuration to dictionary
@ -220,6 +231,7 @@ class Configuration(configparser.RawConfigParser):
return { return {
section: dict(self.items(section)) section: dict(self.items(section))
for section in self.sections() for section in self.sections()
if self[section]
} }
# pylint and mypy are too stupid to find these methods # pylint and mypy are too stupid to find these methods

View File

@ -72,8 +72,8 @@ def setup_routes(application: Application, configuration: Configuration) -> None
application(Application): web application instance application(Application): web application instance
configuration(Configuration): configuration instance configuration(Configuration): configuration instance
""" """
application.router.add_static("/static", configuration.getpath("web", "static_path"), name="_static", application.router.add_static("/static", configuration.getpath("web", "static_path"),
follow_symlinks=True) name="_static", follow_symlinks=True)
for route, view in _dynamic_routes(configuration): for route, view in _dynamic_routes(configuration):
application.router.add_view(route, view, name=_identifier(route)) application.router.add_view(route, view, name=_identifier(route))

View File

@ -144,6 +144,7 @@ def test_repositories_extract(args: argparse.Namespace, configuration: Configura
args.architecture = "arch" args.architecture = "arch"
args.configuration = configuration.path args.configuration = configuration.path
args.repository = "repo" args.repository = "repo"
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures") known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories") known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
@ -159,6 +160,7 @@ def test_repositories_extract_repository(args: argparse.Namespace, configuration
""" """
args.architecture = "arch" args.architecture = "arch"
args.configuration = configuration.path args.configuration = configuration.path
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures") known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories", known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
return_value={"repo"}) return_value={"repo"})
@ -175,6 +177,7 @@ def test_repositories_extract_repository_legacy(args: argparse.Namespace, config
""" """
args.architecture = "arch" args.architecture = "arch"
args.configuration = configuration.path args.configuration = configuration.path
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures") known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories", known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
return_value=set()) return_value=set())
@ -191,6 +194,7 @@ def test_repositories_extract_architecture(args: argparse.Namespace, configurati
""" """
args.configuration = configuration.path args.configuration = configuration.path
args.repository = "repo" args.repository = "repo"
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures", known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures",
return_value={"arch"}) return_value={"arch"})
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories") known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
@ -207,6 +211,7 @@ def test_repositories_extract_empty(args: argparse.Namespace, configuration: Con
""" """
args.command = "config" args.command = "config"
args.configuration = configuration.path args.configuration = configuration.path
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures", return_value=set()) mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures", return_value=set())
mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories", return_value=set()) mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories", return_value=set())
@ -221,6 +226,7 @@ def test_repositories_extract_systemd(args: argparse.Namespace, configuration: C
""" """
args.configuration = configuration.path args.configuration = configuration.path
args.repository_id = "i686/some/repo/name" args.repository_id = "i686/some/repo/name"
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures") known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories") known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
@ -236,6 +242,7 @@ def test_repositories_extract_systemd_with_dash(args: argparse.Namespace, config
""" """
args.configuration = configuration.path args.configuration = configuration.path
args.repository_id = "i686-some-repo-name" args.repository_id = "i686-some-repo-name"
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures") known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories") known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories")
@ -251,6 +258,7 @@ def test_repositories_extract_systemd_legacy(args: argparse.Namespace, configura
""" """
args.configuration = configuration.path args.configuration = configuration.path
args.repository_id = "i686" args.repository_id = "i686"
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures") known_architectures_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_architectures")
known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories", known_repositories_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.known_repositories",
return_value=set()) return_value=set())

View File

@ -6,6 +6,7 @@ from pytest_mock import MockerFixture
from ahriman.application.application import Application from ahriman.application.application import Application
from ahriman.application.handlers.copy import Copy from ahriman.application.handlers.copy import Copy
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.repository import Repository from ahriman.core.repository import Repository
from ahriman.models.build_status import BuildStatusEnum from ahriman.models.build_status import BuildStatusEnum
from ahriman.models.package import Package from ahriman.models.package import Package
@ -30,11 +31,12 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository, def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
package_ahriman: Package, mocker: MockerFixture) -> None: database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must run command must run command
""" """
args = _default_args(args) args = _default_args(args)
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman])
application_mock = mocker.patch("ahriman.application.handlers.copy.Copy.copy_package") application_mock = mocker.patch("ahriman.application.handlers.copy.Copy.copy_package")
@ -51,12 +53,13 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
def test_run_remove(args: argparse.Namespace, configuration: Configuration, repository: Repository, def test_run_remove(args: argparse.Namespace, configuration: Configuration, repository: Repository,
package_ahriman: Package, mocker: MockerFixture) -> None: database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must run command and remove packages afterward must run command and remove packages afterward
""" """
args = _default_args(args) args = _default_args(args)
args.remove = True args.remove = True
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman]) mocker.patch("ahriman.core.repository.Repository.packages", return_value=[package_ahriman])
mocker.patch("ahriman.application.handlers.copy.Copy.copy_package") mocker.patch("ahriman.application.handlers.copy.Copy.copy_package")
@ -69,12 +72,14 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, repo
def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, repository: Repository, def test_run_empty_exception(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None: database: SQLite, mocker: MockerFixture) -> None:
""" """
must raise ExitCode exception on empty result must raise ExitCode exception on empty result
""" """
args = _default_args(args) args = _default_args(args)
args.exit_code = True args.exit_code = True
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.core.repository.Repository.packages", return_value=[]) mocker.patch("ahriman.core.repository.Repository.packages", return_value=[])
mocker.patch("ahriman.application.application.Application.update") mocker.patch("ahriman.application.application.Application.update")
check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status") check_mock = mocker.patch("ahriman.application.handlers.handler.Handler.check_status")

View File

@ -9,6 +9,7 @@ from urllib.parse import quote_plus as url_encode
from ahriman.application.handlers.setup import Setup from ahriman.application.handlers.setup import Setup
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.database import SQLite
from ahriman.core.exceptions import MissingArchitectureError from ahriman.core.exceptions import MissingArchitectureError
from ahriman.core.repository import Repository from ahriman.core.repository import Repository
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
@ -44,11 +45,12 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository, def test_run(args: argparse.Namespace, configuration: Configuration, repository: Repository,
repository_paths: RepositoryPaths, mocker: MockerFixture) -> None: database: SQLite, repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
""" """
must run command must run command
""" """
args = _default_args(args) args = _default_args(args)
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
ahriman_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman") ahriman_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman")
devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_devtools") devtools_configuration_mock = mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_devtools")
@ -88,12 +90,13 @@ def test_run_no_architecture_or_repository(configuration: Configuration) -> None
def test_run_with_server(args: argparse.Namespace, configuration: Configuration, repository: Repository, def test_run_with_server(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None: database: SQLite, mocker: MockerFixture) -> None:
""" """
must run command with server specified must run command with server specified
""" """
args = _default_args(args) args = _default_args(args)
args.server = "server" args.server = "server"
mocker.patch("ahriman.core.database.SQLite.load", return_value=database)
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository) mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman") mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_ahriman")
mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_makepkg") mocker.patch("ahriman.application.handlers.setup.Setup.configuration_create_makepkg")

View File

@ -51,7 +51,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, database: S
update_mock.assert_called_once_with(user) update_mock.assert_called_once_with(user)
def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None: def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration, database: SQLite,
mocker: MockerFixture) -> None:
""" """
must process users with empty password salt must process users with empty password salt
""" """
@ -59,6 +60,7 @@ def test_run_empty_salt(args: argparse.Namespace, configuration: Configuration,
args = _default_args(args) args = _default_args(args)
user = User(username=args.username, password=args.password, access=args.role, user = User(username=args.username, password=args.password, access=args.role,
packager_id=args.packager, key=args.key) packager_id=args.packager, key=args.key)
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)
create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user) create_user_mock = mocker.patch("ahriman.application.handlers.users.Users.user_create", return_value=user)
update_mock = mocker.patch("ahriman.core.database.SQLite.user_update") update_mock = mocker.patch("ahriman.core.database.SQLite.user_update")

View File

@ -1575,6 +1575,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, mocker: Moc
args.command = "" args.command = ""
args.handler = Handler args.handler = Handler
mocker.patch("ahriman.core.configuration.Configuration.load", new=lambda self, _: self.copy_from(configuration))
mocker.patch("argparse.ArgumentParser.parse_args", return_value=args) mocker.patch("argparse.ArgumentParser.parse_args", return_value=args)
assert ahriman.run() == 1 assert ahriman.run() == 1

View File

@ -249,19 +249,25 @@ def auth(configuration: Configuration) -> Auth:
@pytest.fixture @pytest.fixture
def configuration(repository_id: RepositoryId, resource_path_root: Path) -> Configuration: def configuration(repository_id: RepositoryId, tmp_path: Path, resource_path_root: Path) -> Configuration:
""" """
configuration fixture configuration fixture
Args: Args:
repository_id(RepositoryId): repository identifier fixture repository_id(RepositoryId): repository identifier fixture
tmp_path(Path): temporary path used by the fixture as root
resource_path_root(Path): resource path root directory resource_path_root(Path): resource path root directory
Returns: Returns:
Configuration: configuration test instance Configuration: configuration test instance
""" """
path = resource_path_root / "core" / "ahriman.ini" path = resource_path_root / "core" / "ahriman.ini"
return Configuration.from_path(path, repository_id)
instance = Configuration.from_path(path, repository_id)
instance.set_option("repository", "root", str(tmp_path))
instance.set_option("settings", "database", str(tmp_path / "ahriman.db"))
return instance
@pytest.fixture @pytest.fixture
@ -275,9 +281,7 @@ def database(configuration: Configuration) -> SQLite:
Returns: Returns:
SQLite: database test instance SQLite: database test instance
""" """
database = SQLite.load(configuration) return SQLite.load(configuration)
yield database
database.path.unlink()
@pytest.fixture @pytest.fixture

View File

@ -102,6 +102,15 @@ def test_check_loaded_architecture(configuration: Configuration) -> None:
configuration.check_loaded() configuration.check_loaded()
def test_copy_from(configuration: Configuration) -> None:
"""
must copy values from another instance
"""
instance = Configuration()
instance.copy_from(configuration)
assert instance.dump() == configuration.dump()
def test_dump(configuration: Configuration) -> None: def test_dump(configuration: Configuration) -> None:
""" """
dump must not be empty dump must not be empty

View File

@ -62,8 +62,8 @@ def test_validate_is_ip_address(validator: Validator, mocker: MockerFixture) ->
validator._validate_is_ip_address([], "field", "localhost") validator._validate_is_ip_address([], "field", "localhost")
validator._validate_is_ip_address([], "field", "127.0.0.1") validator._validate_is_ip_address([], "field", "127.0.0.1")
validator._validate_is_ip_address([], "field", "::") validator._validate_is_ip_address([], "field", "::") # nosec
validator._validate_is_ip_address([], "field", "0.0.0.0") validator._validate_is_ip_address([], "field", "0.0.0.0") # nosec
validator._validate_is_ip_address([], "field", "random string") validator._validate_is_ip_address([], "field", "random string")

View File

@ -1,7 +1,6 @@
[settings] [settings]
include = . include = .
logging = logging.ini logging = logging.ini
database = ../../../ahriman-test.db
[alpm] [alpm]
database = /var/lib/pacman database = /var/lib/pacman
@ -31,7 +30,6 @@ triggers_known = ahriman.core.distributed.WorkerLoaderTrigger ahriman.core.distr
[repository] [repository]
name = aur name = aur
root = ../../../
[sign] [sign]
target = target =

40
tox.ini
View File

@ -6,8 +6,14 @@ labels =
dependencies = -e .[journald,pacman,reports,s3,shell,stats,unixsocket,validator,web,web_api-docs,web_auth,web_oauth2,web_metrics] dependencies = -e .[journald,pacman,reports,s3,shell,stats,unixsocket,validator,web,web_api-docs,web_auth,web_oauth2,web_metrics]
project_name = ahriman project_name = ahriman
[mypy] [flags]
flags = --implicit-reexport --strict --allow-untyped-decorators --allow-subclassing-any autopep8 = --max-line-length 120 -aa --in-place
bandit = --configfile .bandit.yml
manpage = --author "ahriman team" --author-email "" --description "ArcH linux ReposItory MANager" --manual-title "ArcH linux ReposItory MANager" --project-name ahriman --url https://github.com/arcan1s/ahriman
mypy = --implicit-reexport --strict --allow-untyped-decorators --allow-subclassing-any
pydeps = --no-config --cluster
pylint = --rcfile .pylint.toml
shtab = --prefix ahriman --prog ahriman ahriman.application.ahriman._parser
[pytest] [pytest]
addopts = --cov=ahriman --cov-report=term-missing:skip-covered --no-cov-on-fail --cov-fail-under=100 --spec addopts = --cov=ahriman --cov-report=term-missing:skip-covered --no-cov-on-fail --cov-fail-under=100 --spec
@ -33,19 +39,17 @@ setenv =
CFLAGS="-Wno-unterminated-string-initialization" CFLAGS="-Wno-unterminated-string-initialization"
MYPYPATH=src MYPYPATH=src
commands = commands =
autopep8 --exit-code --max-line-length 120 -aa -i -j 0 -r "src/{[tox]project_name}" "tests/{[tox]project_name}" autopep8 {[flags]autopep8} --exit-code --jobs 0 --recursive "src/{[tox]project_name}" "tests/{[tox]project_name}"
pylint --rcfile=.pylint.toml "src/{[tox]project_name}" pylint {[flags]pylint} "src/{[tox]project_name}"
bandit -c .bandit.yml -r "src/{[tox]project_name}" bandit {[flags]bandit} --recursive "src/{[tox]project_name}"
bandit -c .bandit-test.yml -r "tests/{[tox]project_name}" bandit {[flags]bandit} --skip B101,B105,B106 --recursive "tests/{[tox]project_name}"
mypy {[mypy]flags} -p "{[tox]project_name}" --install-types --non-interactive mypy {[flags]mypy} --install-types --non-interactive --package "{[tox]project_name}"
[testenv:docs] [testenv:docs]
description = Generate source files for documentation description = Generate source files for documentation
allowlist_externals = allowlist_externals =
bash bash
find find
mv
changedir = src
dependency_groups = dependency_groups =
docs docs
depends = depends =
@ -55,18 +59,18 @@ deps =
uv uv
pip_pre = true pip_pre = true
setenv = setenv =
PYTHONPATH=src
SPHINX_APIDOC_OPTIONS=members,no-undoc-members,show-inheritance SPHINX_APIDOC_OPTIONS=members,no-undoc-members,show-inheritance
commands = commands =
bash -c 'shtab --shell bash --prefix ahriman --prog ahriman ahriman.application.ahriman._parser > ../package/share/bash-completion/completions/_ahriman' bash -c 'shtab {[flags]shtab} --shell bash > package/share/bash-completion/completions/_ahriman'
bash -c 'shtab --shell zsh --prefix ahriman --prog ahriman ahriman.application.ahriman._parser > ../package/share/zsh/site-functions/_ahriman' bash -c 'shtab {[flags]shtab} --shell zsh > package/share/zsh/site-functions/_ahriman'
argparse-manpage --module ahriman.application.ahriman --function _parser --author "ahriman team" --project-name ahriman --author-email "" --url https://github.com/arcan1s/ahriman --output ../package/share/man/man1/ahriman.1 argparse-manpage {[flags]manpage} --module ahriman.application.ahriman --function _parser --output ../package/share/man/man1/ahriman.1
pydeps ahriman --no-output --show-dot --dot-output architecture.dot --no-config --cluster pydeps {[flags]pydeps} --no-output --show-dot --dot-output {tox_root}{/}docs/_static/architecture.dot src/ahriman
mv architecture.dot ../docs/_static/architecture.dot
# remove autogenerated modules rst files # remove autogenerated modules rst files
find ../docs -type f -name "{[tox]project_name}*.rst" -delete find docs -type f -name "{[tox]project_name}*.rst" -delete
sphinx-apidoc -o ../docs . sphinx-apidoc --output-dir docs src
# compile list of dependencies for rtd.io # compile list of dependencies for rtd.io
uv pip compile --group ../pyproject.toml:docs --extra s3 --extra validator --extra web --output-file ../docs/requirements.txt --quiet ../pyproject.toml uv pip compile --group pyproject.toml:docs --extra s3 --extra validator --extra web --output-file docs/requirements.txt --quiet pyproject.toml
[testenv:html] [testenv:html]
description = Generate html documentation description = Generate html documentation
@ -77,7 +81,7 @@ deps =
pip_pre = true pip_pre = true
recreate = true recreate = true
commands = commands =
sphinx-build -b html -a -j auto -W docs {envtmpdir}{/}html sphinx-build --builder html --write-all --jobs auto --fail-on-warning docs {envtmpdir}{/}html
[testenv:publish] [testenv:publish]
description = Create and publish release to GitHub description = Create and publish release to GitHub