Compare commits

..

6 Commits

25 changed files with 71 additions and 90 deletions

View File

@ -24,8 +24,7 @@ from ahriman.core.exceptions import OptionError
class ConfigurationMultiDict(dict[str, Any]):
"""
wrapper around :class:`collections.OrderedDict` to handle multiple configuration keys as lists
if they end with ``[]``.
wrapper around :class:`dict` to handle multiple configuration keys as lists if they end with ``[]``.
Examples:
This class is designed to be used only with :class:`configparser.RawConfigParser` class, but idea is that
@ -46,18 +45,25 @@ class ConfigurationMultiDict(dict[str, Any]):
>>> print(data) # {"single": "value", "array": ["value3"]}
"""
def _set_array(self, key: str, value: list[Any]) -> None:
def _set_array_value(self, key: str, value: Any) -> None:
"""
set array value. If the key already exists in the dictionary, it will be prepended to the value
set array value. If the key already exists in the dictionary, its value will be prepended to new value
Args:
key(str): key to insert
value(list[Any]): value of the related key
value(Any): value of the related key
Raises:
OptionError: if the key already exists in the dictionary, but not a single value list
"""
current_value = self.get(key)
if current_value is not None:
value = [f"{left} {right}" for left, right in zip(current_value, value)]
super().__setitem__(key, value)
match self.get(key):
case [current_value]:
value = f"{current_value} {value}"
case None:
pass
case other:
raise OptionError(other)
super().__setitem__(key, [value])
def __setitem__(self, key: str, value: Any) -> None:
"""
@ -72,14 +78,14 @@ class ConfigurationMultiDict(dict[str, Any]):
Raises:
OptionError: if ``key`` contains ``[]``, but not at the end of the string (e.g. ``prefix[]suffix``)
"""
real_key, is_array, remaining = key.partition("[]")
real_key, is_key_array, remaining = key.partition("[]")
if remaining:
raise OptionError(key)
if value == [""]: # em[ty value key
if real_key in self:
del self[real_key]
elif is_array and isinstance(value, list): # update array value
self._set_array(real_key, value)
else: # normal key
super().__setitem__(real_key, value)
match value:
case [""]: # empty value key
self.pop(real_key, None)
case [array_value] if is_key_array: # update array value
self._set_array_value(real_key, array_value)
case _: # normal key
super().__setitem__(real_key, value)

View File

@ -139,7 +139,7 @@ def test_add_remote(application_packages: ApplicationPackages, package_descripti
def test_add_remote_missing(application_packages: ApplicationPackages, mocker: MockerFixture) -> None:
"""
must add package from remote source
must raise UnknownPackageError if remote package wasn't found
"""
mocker.patch("requests.get", side_effect=Exception())
with pytest.raises(UnknownPackageError):

View File

@ -79,7 +79,7 @@ def test_clean_packages(application_repository: ApplicationRepository, mocker: M
def test_clean_pacman(application_repository: ApplicationRepository, mocker: MockerFixture) -> None:
"""
must clean packages directory
must clean pacman directory
"""
clear_mock = mocker.patch("ahriman.core.repository.Repository.clear_pacman")
application_repository.clean(cache=False, chroot=False, manual=False, packages=False, pacman=True)

View File

@ -171,7 +171,7 @@ def test_repositories_extract_repository(args: argparse.Namespace, configuration
def test_repositories_extract_repository_legacy(args: argparse.Namespace, configuration: Configuration,
mocker: MockerFixture) -> None:
"""
must generate list of available repositories based on flags and tree
must generate list of available repositories based on flags and tree (legacy mode)
"""
args.architecture = "arch"
args.configuration = configuration.path

View File

@ -191,7 +191,7 @@ def test_extract_packages_by_status(application: Application, mocker: MockerFixt
def test_extract_packages_from_database(application: Application, mocker: MockerFixture) -> None:
"""
must extract packages from database
must extract packages from database from database
"""
packages_mock = mocker.patch("ahriman.core.database.SQLite.packages_get")
Rebuild.extract_packages(application, None, from_database=True)

View File

@ -40,7 +40,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
def test_run_eval(args: argparse.Namespace, configuration: Configuration, repository: Repository,
mocker: MockerFixture) -> None:
"""
must run command
must run command via eval
"""
args = _default_args(args)
args.code = """print("hello world")"""

View File

@ -60,7 +60,7 @@ def test_remote_git_url(aur_package_ahriman: AURPackage) -> None:
def test_remote_web_url(aur_package_ahriman: AURPackage) -> None:
"""
must generate package git url
must generate package web url
"""
web_url = AUR.remote_web_url(aur_package_ahriman.package_base)
assert web_url.startswith(AUR.DEFAULT_AUR_URL)

View File

@ -87,7 +87,7 @@ def test_database_copy_skip(pacman: Pacman, mocker: MockerFixture) -> None:
def test_database_copy_no_directory(pacman: Pacman, mocker: MockerFixture) -> None:
"""
must do not copy database if local cache already exists
must do not copy database if directory does not exist
"""
database = next(db for db in pacman.handle.get_syncdbs() if db.name == "core")
path = Path("randomname")

View File

@ -35,6 +35,16 @@ def test_setitem_array() -> None:
assert instance["key"] == ["value1 value2"]
def test_setitem_array_exception() -> None:
"""
must raise exception if the current value is not a single value array
"""
instance = ConfigurationMultiDict()
instance["key[]"] = "value1"
with pytest.raises(OptionError):
instance["key[]"] = ["value2"]
def test_setitem_exception() -> None:
"""
must raise exception on invalid key

View File

@ -44,7 +44,7 @@ def test_patches_list_filter(database: SQLite, package_ahriman: Package, package
def test_patches_list_filter_by_variable(database: SQLite, package_ahriman: Package,
package_python_schedule: Package) -> None:
"""
must list all patches filtered by package name (same as get)
must list all patches filtered by variable (same as get)
"""
database.patches_insert(package_ahriman.base, [PkgbuildPatch(None, "patch1")])
database.patches_insert(package_ahriman.base, [PkgbuildPatch("key", "patch2")])

View File

@ -63,7 +63,7 @@ def test_in_package_context(database: SQLite, package_ahriman: Package, mocker:
def test_in_package_context_empty_version(database: SQLite, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set package log context
must set package log context with empty version
"""
set_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_set")
reset_mock = mocker.patch("ahriman.core.log.LazyLogging._package_logger_reset")

View File

@ -24,7 +24,7 @@ def test_template(configuration: Configuration) -> None:
def test_template_full(configuration: Configuration) -> None:
"""
must correctly parse template name and path
must correctly parse full template name and path
"""
template = "template"
root, repository_id = configuration.check_loaded()

View File

@ -107,7 +107,7 @@ def test_generate_very_big_text(telegram: Telegram, package_ahriman: Package, re
def test_generate_no_empty(telegram: Telegram, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must generate report
must skip report generation if result is empty
"""
send_mock = mocker.patch("ahriman.core.report.telegram.Telegram._send")
telegram.generate([package_ahriman], Result())

View File

@ -62,7 +62,7 @@ def test_clear_packages(cleaner: Cleaner, mocker: MockerFixture) -> None:
def test_clear_pacman(cleaner: Cleaner, mocker: MockerFixture) -> None:
"""
must delete built packages
must clear pacman root
"""
_mock_clear(mocker)
cleaner.clear_pacman()

View File

@ -142,7 +142,7 @@ def test_updates_aur_load_by_package(update_handler: UpdateHandler, package_pyth
def test_updates_aur_load_by_package_failed(update_handler: UpdateHandler, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must update status via client for failed load
must update status via client for failed load if no remote package found
"""
mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.packages", return_value=[package_ahriman])
mocker.patch("ahriman.models.package.Package.from_aur", side_effect=UnknownPackageError(package_ahriman.base))

View File

@ -9,50 +9,35 @@ from ahriman.core.sign.gpg import GPG
from ahriman.models.sign_settings import SignSettings
def test_repository_sign_args_1(gpg_with_key: GPG) -> None:
def test_repository_sign_args(gpg_with_key: GPG) -> None:
"""
must generate correct sign args
"""
gpg_with_key.targets = {SignSettings.Repository}
assert gpg_with_key.repository_sign_args
def test_repository_sign_args_2(gpg_with_key: GPG) -> None:
"""
must generate correct sign args
"""
gpg_with_key.targets = {SignSettings.Packages, SignSettings.Repository}
assert gpg_with_key.repository_sign_args
def test_repository_sign_args_skip_1(gpg_with_key: GPG) -> None:
def test_repository_sign_args_skip(gpg_with_key: GPG) -> None:
"""
must return empty args if it is not set
"""
gpg_with_key.targets = {}
assert not gpg_with_key.repository_sign_args
def test_repository_sign_args_skip_2(gpg_with_key: GPG) -> None:
"""
must return empty args if it is not set
"""
gpg_with_key.targets = {SignSettings.Packages}
assert not gpg_with_key.repository_sign_args
def test_repository_sign_args_skip_3(gpg: GPG) -> None:
def test_repository_sign_args_skip_no_key(gpg: GPG) -> None:
"""
must return empty args if it is not set
must return empty args if it is not set if no key set
"""
gpg.targets = {SignSettings.Repository}
assert not gpg.repository_sign_args
def test_repository_sign_args_skip_4(gpg: GPG) -> None:
"""
must return empty args if it is not set
"""
gpg.targets = {SignSettings.Packages, SignSettings.Repository}
assert not gpg.repository_sign_args
@ -188,7 +173,7 @@ def test_process_sign_package_3(gpg_with_key: GPG, mocker: MockerFixture) -> Non
def test_process_sign_package_skip_1(gpg_with_key: GPG, mocker: MockerFixture) -> None:
"""
must not sign package if it is not set
must not sign package on empty target list
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
gpg_with_key.targets = {}
@ -198,7 +183,7 @@ def test_process_sign_package_skip_1(gpg_with_key: GPG, mocker: MockerFixture) -
def test_process_sign_package_skip_2(gpg_with_key: GPG, mocker: MockerFixture) -> None:
"""
must not sign package if it is not set
must not sign package if repository only is enabled
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
gpg_with_key.targets = {SignSettings.Repository}
@ -208,7 +193,7 @@ def test_process_sign_package_skip_2(gpg_with_key: GPG, mocker: MockerFixture) -
def test_process_sign_package_skip_3(gpg: GPG, mocker: MockerFixture) -> None:
"""
must not sign package if it is not set
must not sign package if key is not set
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
gpg.targets = {SignSettings.Packages}
@ -216,16 +201,6 @@ def test_process_sign_package_skip_3(gpg: GPG, mocker: MockerFixture) -> None:
process_mock.assert_not_called()
def test_process_sign_package_skip_4(gpg: GPG, mocker: MockerFixture) -> None:
"""
must not sign package if it is not set
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
gpg.targets = {SignSettings.Packages, SignSettings.Repository}
gpg.process_sign_package(Path("a"), None)
process_mock.assert_not_called()
def test_process_sign_package_skip_already_signed(gpg_with_key: GPG, mocker: MockerFixture) -> None:
"""
must not sign package if it was already signed
@ -264,7 +239,7 @@ def test_process_sign_repository_2(gpg_with_key: GPG, mocker: MockerFixture) ->
def test_process_sign_repository_skip_1(gpg_with_key: GPG, mocker: MockerFixture) -> None:
"""
must not sign repository if it is not set
must not sign repository if no targets set
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
gpg_with_key.targets = {}
@ -274,7 +249,7 @@ def test_process_sign_repository_skip_1(gpg_with_key: GPG, mocker: MockerFixture
def test_process_sign_repository_skip_2(gpg_with_key: GPG, mocker: MockerFixture) -> None:
"""
must not sign repository if it is not set
must not sign repository if repository target is not set
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
gpg_with_key.targets = {SignSettings.Packages}
@ -284,19 +259,9 @@ def test_process_sign_repository_skip_2(gpg_with_key: GPG, mocker: MockerFixture
def test_process_sign_repository_skip_3(gpg: GPG, mocker: MockerFixture) -> None:
"""
must not sign repository if it is not set
must not sign repository if key is not set
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
gpg.targets = {SignSettings.Repository}
gpg.process_sign_repository(Path("a"))
process_mock.assert_not_called()
def test_process_sign_repository_skip_4(gpg: GPG, mocker: MockerFixture) -> None:
"""
must not sign repository if it is not set
"""
process_mock = mocker.patch("ahriman.core.sign.gpg.GPG.process")
gpg.targets = {SignSettings.Packages, SignSettings.Repository}
gpg.process_sign_repository(Path("a"))
process_mock.assert_not_called()

View File

@ -245,7 +245,7 @@ def test_set_failed(client: Client, package_ahriman: Package, mocker: MockerFixt
def test_set_pending(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set building status to the package
must set pending status to the package
"""
update_mock = mocker.patch("ahriman.core.status.Client.package_status_update")
client.set_pending(package_ahriman.base)

View File

@ -46,7 +46,7 @@ def test_changes_url(web_client: WebClient, package_ahriman: Package) -> None:
def test_dependencies_url(web_client: WebClient, package_ahriman: Package) -> None:
"""
must generate changes url correctly
must generate dependencies url correctly
"""
assert web_client._dependencies_url(package_ahriman.base).startswith(web_client.address)
assert web_client._dependencies_url(package_ahriman.base).endswith(
@ -74,7 +74,7 @@ def test_logs_url(web_client: WebClient, package_ahriman: Package) -> None:
def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
"""
must generate package status url correctly
must generate package url correctly
"""
assert web_client._package_url("").startswith(web_client.address)
assert web_client._package_url("").endswith("/api/v1/packages")
@ -86,7 +86,7 @@ def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
def test_patches_url(web_client: WebClient, package_ahriman: Package) -> None:
"""
must generate changes url correctly
must generate patches url correctly
"""
assert web_client._patches_url(package_ahriman.base).startswith(web_client.address)
assert web_client._patches_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/patches")
@ -575,7 +575,7 @@ def test_package_logs_add_failed(web_client: WebClient, log_record: logging.LogR
def test_package_logs_add_failed_http_error(web_client: WebClient, log_record: logging.LogRecord,
package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must pass exception during log post
must pass HTTP exception during log post
"""
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
log_record.package_base = package_ahriman.base
@ -725,7 +725,7 @@ def test_package_patches_get_failed(web_client: WebClient, package_ahriman: Pack
def test_package_patches_get_failed_http_error(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during dependencies fetch
must suppress HTTP exception happened during patches fetch
"""
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
web_client.package_patches_get(package_ahriman.base, None)

View File

@ -13,7 +13,7 @@ from ahriman.models.result import Result
def test_known_triggers(configuration: Configuration) -> None:
"""
must return used triggers
must return known triggers
"""
configuration.set_option("build", "triggers_known", "a b c")
assert TriggerLoader.known_triggers(configuration) == ["a", "b", "c"]

View File

@ -255,14 +255,14 @@ def test_from_json_view_1(package_ahriman: Package) -> None:
def test_from_json_view_2(package_python_schedule: Package) -> None:
"""
must construct same object from json
must construct same object from json (double package)
"""
assert Package.from_json(package_python_schedule.view()) == package_python_schedule
def test_from_json_view_3(package_tpacpi_bat_git: Package) -> None:
"""
must construct same object from json
must construct same object from json (git package)
"""
assert Package.from_json(package_tpacpi_bat_git.view()) == package_tpacpi_bat_git

View File

@ -86,7 +86,7 @@ def test_resolve_local(repository_paths: RepositoryPaths, mocker: MockerFixture)
def test_resolve_local_cache(repository_paths: RepositoryPaths, mocker: MockerFixture) -> None:
"""
must resolve auto type into the local sources
must resolve auto type into the local sources with cache
"""
cache_mock = mocker.patch("ahriman.models.repository_paths.RepositoryPaths.cache_for", return_value=Path("cache"))
mocker.patch("pathlib.Path.is_dir", autospec=True, side_effect=lambda p: p == Path("cache"))

View File

@ -27,7 +27,7 @@ def test_is_function() -> None:
def test_is_plain_diff() -> None:
"""
must correctly define key as function
must correctly define key as a plain diff
"""
assert not PkgbuildPatch("key", "value").is_plain_diff
assert PkgbuildPatch(None, "value").is_plain_diff
@ -112,7 +112,7 @@ def test_from_env_serialize() -> None:
def test_serialize_plain_diff() -> None:
"""
must correctly serialize function values
must correctly serialize plain diff values
"""
assert PkgbuildPatch(None, "{ value }").serialize() == "{ value }"

View File

@ -98,7 +98,7 @@ async def test_exception_handler_unauthorized(mocker: MockerFixture) -> None:
async def test_exception_handler_unauthorized_templated(mocker: MockerFixture) -> None:
"""
must handle unauthorized exception as json response
must handle unauthorized exception as json response in html context
"""
request = pytest.helpers.request("", "", "")
request_handler = AsyncMock(side_effect=HTTPUnauthorized())

View File

@ -124,7 +124,7 @@ def test_run_with_auth(application_with_auth: Application, mocker: MockerFixture
def test_run_with_socket(application: Application, mocker: MockerFixture) -> None:
"""
must run application
must run application with socket
"""
port = 8080
application[ConfigurationKey].set_option("web", "port", str(port))

View File

@ -158,7 +158,7 @@ async def test_post_unauthorized(client_with_auth: TestClient, user: User, mocke
async def test_post_invalid_json(client_with_auth: TestClient, mocker: MockerFixture) -> None:
"""
must return unauthorized on invalid auth
must return unauthorized on invalid payload
"""
response_schema = pytest.helpers.schema_response(LoginView.post, code=400)
remember_mock = mocker.patch("aiohttp_security.remember")