mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
100% coverage
This commit is contained in:
parent
62d55eff19
commit
461883217d
@ -2,4 +2,4 @@
|
||||
test = pytest
|
||||
|
||||
[tool:pytest]
|
||||
addopts = --cov=ahriman --pspec
|
||||
addopts = --cov=ahriman --cov-report term-missing:skip-covered --pspec
|
||||
|
@ -274,11 +274,18 @@ def _set_web_parser(root: SubParserAction) -> argparse.ArgumentParser:
|
||||
return parser
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
args_parser = _parser()
|
||||
args = args_parser.parse_args()
|
||||
def run():
|
||||
"""
|
||||
run application instance
|
||||
"""
|
||||
if __name__ == "__main__":
|
||||
args_parser = _parser()
|
||||
args = args_parser.parse_args()
|
||||
|
||||
handler: handlers.Handler = args.handler
|
||||
status = handler.execute(args)
|
||||
handler: handlers.Handler = args.handler
|
||||
status = handler.execute(args)
|
||||
|
||||
sys.exit(status)
|
||||
sys.exit(status)
|
||||
|
||||
|
||||
run()
|
||||
|
@ -51,6 +51,13 @@ class Application:
|
||||
self.architecture = architecture
|
||||
self.repository = Repository(architecture, configuration)
|
||||
|
||||
def _finalize(self) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
"""
|
||||
self.report([])
|
||||
self.sync([])
|
||||
|
||||
def _known_packages(self) -> Set[str]:
|
||||
"""
|
||||
load packages from repository and pacman repositories
|
||||
@ -63,13 +70,6 @@ class Application:
|
||||
known_packages.update(self.repository.pacman.all_packages())
|
||||
return known_packages
|
||||
|
||||
def _finalize(self) -> None:
|
||||
"""
|
||||
generate report and sync to remote server
|
||||
"""
|
||||
self.report([])
|
||||
self.sync([])
|
||||
|
||||
def get_updates(self, filter_packages: List[str], no_aur: bool, no_manual: bool, no_vcs: bool,
|
||||
log_fn: Callable[[str], None]) -> List[Package]:
|
||||
"""
|
||||
@ -182,6 +182,7 @@ class Application:
|
||||
continue
|
||||
for archive in package.packages.values():
|
||||
if archive.filepath is None:
|
||||
self.logger.warning(f"filepath is empty for {package.base}")
|
||||
continue # avoid mypy warning
|
||||
src = self.repository.paths.repository / archive.filepath
|
||||
dst = self.repository.paths.packages / archive.filepath
|
||||
|
@ -19,7 +19,7 @@
|
||||
#
|
||||
import argparse
|
||||
|
||||
from typing import Type
|
||||
from typing import Callable, Type
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers.handler import Handler
|
||||
@ -39,13 +39,22 @@ class Update(Handler):
|
||||
:param architecture: repository architecture
|
||||
:param configuration: configuration instance
|
||||
"""
|
||||
# typing workaround
|
||||
def log_fn(line: str) -> None:
|
||||
return print(line) if args.dry_run else application.logger.info(line)
|
||||
|
||||
application = Application(architecture, configuration)
|
||||
packages = application.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs, log_fn)
|
||||
packages = application.get_updates(args.package, args.no_aur, args.no_manual, args.no_vcs,
|
||||
Update.log_fn(application, args.dry_run))
|
||||
if args.dry_run:
|
||||
return
|
||||
|
||||
application.update(packages)
|
||||
|
||||
@staticmethod
|
||||
def log_fn(application: Application, dry_run: bool) -> Callable[[str], None]:
|
||||
"""
|
||||
package updates log function
|
||||
:param application: application instance
|
||||
:param dry_run: do not perform update itself
|
||||
:return: in case if dry_run is set it will return print, logger otherwise
|
||||
"""
|
||||
def inner(line: str) -> None:
|
||||
return print(line) if dry_run else application.logger.info(line)
|
||||
return inner
|
||||
|
@ -45,6 +45,15 @@ class WebClient(Client):
|
||||
self.host = host
|
||||
self.port = port
|
||||
|
||||
@staticmethod
|
||||
def _exception_response_text(exception: requests.exceptions.HTTPError) -> str:
|
||||
"""
|
||||
safe response exception text generation
|
||||
:param exception: exception raised
|
||||
:return: text of the response if it is not None and empty string otherwise
|
||||
"""
|
||||
return exception.response.text if exception.response is not None else ''
|
||||
|
||||
def _ahriman_url(self) -> str:
|
||||
"""
|
||||
url generator
|
||||
@ -75,7 +84,7 @@ class WebClient(Client):
|
||||
response = requests.post(self._package_url(package.base), json=payload)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
self.logger.exception(f"could not add {package.base}: {e.response.text}")
|
||||
self.logger.exception(f"could not add {package.base}: {WebClient._exception_response_text(e)}")
|
||||
except Exception:
|
||||
self.logger.exception(f"could not add {package.base}")
|
||||
|
||||
@ -95,7 +104,7 @@ class WebClient(Client):
|
||||
for package in status_json
|
||||
]
|
||||
except requests.exceptions.HTTPError as e:
|
||||
self.logger.exception(f"could not get {base}: {e.response.text}")
|
||||
self.logger.exception(f"could not get {base}: {WebClient._exception_response_text(e)}")
|
||||
except Exception:
|
||||
self.logger.exception(f"could not get {base}")
|
||||
return []
|
||||
@ -112,7 +121,7 @@ class WebClient(Client):
|
||||
status_json = response.json()
|
||||
return BuildStatus.from_json(status_json)
|
||||
except requests.exceptions.HTTPError as e:
|
||||
self.logger.exception(f"could not get service status: {e.response.text}")
|
||||
self.logger.exception(f"could not get service status: {WebClient._exception_response_text(e)}")
|
||||
except Exception:
|
||||
self.logger.exception("could not get service status")
|
||||
return BuildStatus()
|
||||
@ -126,7 +135,7 @@ class WebClient(Client):
|
||||
response = requests.delete(self._package_url(base))
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
self.logger.exception(f"could not delete {base}: {e.response.text}")
|
||||
self.logger.exception(f"could not delete {base}: {WebClient._exception_response_text(e)}")
|
||||
except Exception:
|
||||
self.logger.exception(f"could not delete {base}")
|
||||
|
||||
@ -142,7 +151,7 @@ class WebClient(Client):
|
||||
response = requests.post(self._package_url(base), json=payload)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
self.logger.exception(f"could not update {base}: {e.response.text}")
|
||||
self.logger.exception(f"could not update {base}: {WebClient._exception_response_text(e)}")
|
||||
except Exception:
|
||||
self.logger.exception(f"could not update {base}")
|
||||
|
||||
@ -157,6 +166,6 @@ class WebClient(Client):
|
||||
response = requests.post(self._ahriman_url(), json=payload)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
self.logger.exception(f"could not update service status: {e.response.text}")
|
||||
self.logger.exception(f"could not update service status: {WebClient._exception_response_text(e)}")
|
||||
except Exception:
|
||||
self.logger.exception("could not update service status")
|
||||
|
@ -89,6 +89,6 @@ def pretty_size(size: Optional[float], level: int = 0) -> str:
|
||||
|
||||
if size is None:
|
||||
return ""
|
||||
if size < 1024 or level == 3:
|
||||
if size < 1024 or level >= 3:
|
||||
return f"{size:.1f} {str_level()}"
|
||||
return pretty_size(size / 1024, level + 1)
|
||||
|
@ -154,7 +154,7 @@ class Package:
|
||||
:return: package properties
|
||||
"""
|
||||
packages = {
|
||||
key: PackageDescription(**value)
|
||||
key: PackageDescription.from_json(value)
|
||||
for key, value in dump.get("packages", {}).items()
|
||||
}
|
||||
return Package(
|
||||
|
@ -19,10 +19,10 @@
|
||||
#
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from dataclasses import dataclass, field, fields
|
||||
from pathlib import Path
|
||||
from pyalpm import Package # type: ignore
|
||||
from typing import List, Optional, Type
|
||||
from typing import Any, Dict, List, Optional, Type
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -59,6 +59,18 @@ class PackageDescription:
|
||||
"""
|
||||
return Path(self.filename) if self.filename is not None else None
|
||||
|
||||
@classmethod
|
||||
def from_json(cls: Type[PackageDescription], dump: Dict[str, Any]) -> PackageDescription:
|
||||
"""
|
||||
construct package properties from json dump
|
||||
:param dump: json dump body
|
||||
:return: package properties
|
||||
"""
|
||||
# filter to only known fields
|
||||
known_fields = [pair.name for pair in fields(cls)]
|
||||
dump = {key: value for key, value in dump.items() if key in known_fields}
|
||||
return cls(**dump)
|
||||
|
||||
@classmethod
|
||||
def from_package(cls: Type[PackageDescription], package: Package, path: Path) -> PackageDescription:
|
||||
"""
|
||||
|
@ -31,6 +31,7 @@ class ReportSettings(Enum):
|
||||
:cvar HTML: html report generation
|
||||
"""
|
||||
|
||||
Disabled = auto() # for testing purpose
|
||||
HTML = auto()
|
||||
|
||||
@classmethod
|
||||
|
@ -32,6 +32,7 @@ class UploadSettings(Enum):
|
||||
:cvar S3: sync to Amazon S3
|
||||
"""
|
||||
|
||||
Disabled = auto() # for testing purpose
|
||||
Rsync = auto()
|
||||
S3 = auto()
|
||||
|
||||
|
@ -1,8 +1,10 @@
|
||||
import argparse
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.handlers import Handler
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
|
||||
def test_call(args: argparse.Namespace, mocker: MockerFixture) -> None:
|
||||
@ -27,3 +29,22 @@ def test_call_exception(args: argparse.Namespace, mocker: MockerFixture) -> None
|
||||
"""
|
||||
mocker.patch("ahriman.application.lock.Lock.__enter__", side_effect=Exception())
|
||||
assert not Handler._call(args, "x86_64")
|
||||
|
||||
|
||||
def test_execute(args: argparse.Namespace, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run execution in multiple processes
|
||||
"""
|
||||
args.architecture = ["i686", "x86_64"]
|
||||
starmap_mock = mocker.patch("multiprocessing.pool.Pool.starmap")
|
||||
|
||||
Handler.execute(args)
|
||||
starmap_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_packages(args: argparse.Namespace, configuration: Configuration) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
Handler.run(args, "x86_64", configuration)
|
||||
|
@ -4,6 +4,8 @@ from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.handlers import Status
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.models.build_status import BuildStatus
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
@ -12,15 +14,33 @@ def _default_args(args: argparse.Namespace) -> argparse.Namespace:
|
||||
return args
|
||||
|
||||
|
||||
def test_run(args: argparse.Namespace, configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_run(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("pathlib.Path.mkdir")
|
||||
application_mock = mocker.patch("ahriman.core.status.client.Client.get_self")
|
||||
packages_mock = mocker.patch("ahriman.core.status.client.Client.get")
|
||||
packages_mock = mocker.patch("ahriman.core.status.client.Client.get",
|
||||
return_value=[(package_ahriman, BuildStatus())])
|
||||
|
||||
Status.run(args, "x86_64", configuration)
|
||||
application_mock.assert_called_once()
|
||||
packages_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_run_with_package_filter(args: argparse.Namespace, configuration: Configuration, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run command
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.package = [package_ahriman.base]
|
||||
mocker.patch("pathlib.Path.mkdir")
|
||||
packages_mock = mocker.patch("ahriman.core.status.client.Client.get",
|
||||
return_value=[(package_ahriman, BuildStatus())])
|
||||
|
||||
Status.run(args, "x86_64", configuration)
|
||||
packages_mock.assert_called_once()
|
||||
|
||||
|
@ -2,6 +2,7 @@ import argparse
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.application import Application
|
||||
from ahriman.application.handlers import Update
|
||||
from ahriman.core.configuration import Configuration
|
||||
|
||||
@ -40,3 +41,12 @@ def test_run_dry_run(args: argparse.Namespace, configuration: Configuration, moc
|
||||
|
||||
Update.run(args, "x86_64", configuration)
|
||||
updates_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_log_fn(application: Application, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must print package updates
|
||||
"""
|
||||
logger_mock = mocker.patch("logging.Logger.info")
|
||||
Update.log_fn(application, False)("hello")
|
||||
logger_mock.assert_called_once()
|
||||
|
@ -1,5 +1,9 @@
|
||||
import argparse
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.application.handlers import Handler
|
||||
|
||||
|
||||
def test_parser(parser: argparse.ArgumentParser) -> None:
|
||||
"""
|
||||
@ -81,3 +85,19 @@ def test_subparsers_web(parser: argparse.ArgumentParser) -> None:
|
||||
args = parser.parse_args(["-a", "x86_64", "web"])
|
||||
assert args.lock is None
|
||||
assert args.no_report
|
||||
|
||||
|
||||
def test_run(args: argparse.Namespace, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
application must be run
|
||||
"""
|
||||
args.architecture = "x86_64"
|
||||
args.handler = Handler
|
||||
|
||||
from ahriman.application import ahriman
|
||||
mocker.patch.object(ahriman, "__name__", "__main__")
|
||||
mocker.patch("argparse.ArgumentParser.parse_args", return_value=args)
|
||||
exit_mock = mocker.patch("sys.exit")
|
||||
|
||||
ahriman.run()
|
||||
exit_mock.assert_called_once()
|
||||
|
@ -20,11 +20,22 @@ def test_finalize(application: Application, mocker: MockerFixture) -> None:
|
||||
sync_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_get_updates_all(application: Application, mocker: MockerFixture) -> None:
|
||||
def test_known_packages(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return not empty list of known packages
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
packages = application._known_packages()
|
||||
assert len(packages) > 1
|
||||
assert package_ahriman.base in packages
|
||||
|
||||
|
||||
def test_get_updates_all(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get updates for all
|
||||
"""
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur")
|
||||
updates_aur_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_aur",
|
||||
return_value=[package_ahriman])
|
||||
updates_manual_mock = mocker.patch("ahriman.core.repository.update_handler.UpdateHandler.updates_manual")
|
||||
|
||||
application.get_updates([], no_aur=False, no_manual=False, no_vcs=False, log_fn=print)
|
||||
@ -233,6 +244,17 @@ def test_sign(application: Application, package_ahriman: Package, package_python
|
||||
finalize_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_sign_skip(application: Application, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip sign packages with empty filename
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
mocker.patch("ahriman.core.repository.repository.Repository.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.application.application.Application.update")
|
||||
|
||||
application.sign([])
|
||||
|
||||
|
||||
def test_sign_specific(application: Application, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
|
@ -51,6 +51,15 @@ def test_fetch_new(mocker: MockerFixture) -> None:
|
||||
])
|
||||
|
||||
|
||||
def test_build(task_ahriman: Task, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must build package
|
||||
"""
|
||||
check_output_mock = mocker.patch("ahriman.core.build_tools.task.Task._check_output")
|
||||
task_ahriman.build()
|
||||
check_output_mock.assert_called()
|
||||
|
||||
|
||||
def test_init_with_cache(task_ahriman: Task, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must copy tree instead of fetch
|
||||
|
@ -18,6 +18,16 @@ def test_report_failure(configuration: Configuration, mocker: MockerFixture) ->
|
||||
Report.load("x86_64", configuration, ReportSettings.HTML.name).run(Path("path"))
|
||||
|
||||
|
||||
def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must construct dummy report class
|
||||
"""
|
||||
mocker.patch("ahriman.models.report_settings.ReportSettings.from_option", return_value=ReportSettings.Disabled)
|
||||
report_mock = mocker.patch("ahriman.core.report.report.Report.generate")
|
||||
Report.load("x86_64", configuration, ReportSettings.Disabled.name).run(Path("path"))
|
||||
report_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_report_html(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate html report
|
||||
|
@ -1,3 +1,4 @@
|
||||
import pytest
|
||||
import shutil
|
||||
|
||||
from pathlib import Path
|
||||
@ -20,6 +21,14 @@ def _mock_clear_check() -> None:
|
||||
])
|
||||
|
||||
|
||||
def test_packages_built(cleaner: Cleaner) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
cleaner.packages_built()
|
||||
|
||||
|
||||
def test_clear_build(cleaner: Cleaner, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must remove directories with sources
|
||||
|
@ -1,23 +1,34 @@
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest import mock
|
||||
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.core.repository.executor import Executor
|
||||
from ahriman.core.upload.upload import Upload
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_packages(executor: Executor) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
executor.packages()
|
||||
|
||||
|
||||
def test_process_build(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run build process
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages_built", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.build", return_value=[Path(package_ahriman.base)])
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.init")
|
||||
move_mock = mocker.patch("shutil.move")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_building")
|
||||
built_packages_mock = mocker.patch("ahriman.core.repository.executor.Executor.packages_built")
|
||||
|
||||
# must return list of built packages
|
||||
assert executor.process_build([package_ahriman]) == [package_ahriman]
|
||||
executor.process_build([package_ahriman])
|
||||
# must move files (once)
|
||||
move_mock.assert_called_once()
|
||||
# must update status
|
||||
@ -25,6 +36,8 @@ def test_process_build(executor: Executor, package_ahriman: Package, mocker: Moc
|
||||
# must clear directory
|
||||
from ahriman.core.repository.cleaner import Cleaner
|
||||
Cleaner.clear_build.assert_called_once()
|
||||
# must return build packages after all
|
||||
built_packages_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_process_build_failure(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -68,7 +81,7 @@ def test_process_remove_base_multiple(executor: Executor, package_python_schedul
|
||||
executor.process_remove([package_python_schedule.base])
|
||||
# must remove via alpm wrapper
|
||||
repo_remove_mock.assert_has_calls([
|
||||
mock.call(package, Path(props.filename))
|
||||
mock.call(package, props.filepath)
|
||||
for package, props in package_python_schedule.packages.items()
|
||||
], any_order=True)
|
||||
# must update status
|
||||
@ -91,6 +104,15 @@ def test_process_remove_base_single(executor: Executor, package_python_schedule:
|
||||
status_client_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_process_remove_failed(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress remove errors
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.alpm.repo.Repo.remove", side_effect=Exception())
|
||||
executor.process_remove([package_ahriman.base])
|
||||
|
||||
|
||||
def test_process_remove_nothing(executor: Executor, package_ahriman: Package, package_python_schedule: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
@ -103,6 +125,18 @@ def test_process_remove_nothing(executor: Executor, package_ahriman: Package, pa
|
||||
repo_remove_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_process_report(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process report
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_ahriman])
|
||||
mocker.patch("ahriman.core.report.report.Report.load", return_value=Report("x86_64", executor.configuration))
|
||||
report_mock = mocker.patch("ahriman.core.report.report.Report.run")
|
||||
|
||||
executor.process_report(["dummy"])
|
||||
report_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_process_report_auto(executor: Executor, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process report in auto mode if no targets supplied
|
||||
@ -113,7 +147,18 @@ def test_process_report_auto(executor: Executor, mocker: MockerFixture) -> None:
|
||||
configuration_getlist_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_process_sync_auto(executor: Executor, mocker: MockerFixture) -> None:
|
||||
def test_process_upload(executor: Executor, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process sync in auto mode if no targets supplied
|
||||
"""
|
||||
mocker.patch("ahriman.core.upload.upload.Upload.load", return_value=Upload("x86_64", executor.configuration))
|
||||
upload_mock = mocker.patch("ahriman.core.upload.upload.Upload.run")
|
||||
|
||||
executor.process_sync(["dummy"])
|
||||
upload_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_process_upload_auto(executor: Executor, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process sync in auto mode if no targets supplied
|
||||
"""
|
||||
@ -134,7 +179,7 @@ def test_process_update(executor: Executor, package_ahriman: Package, mocker: Mo
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
|
||||
|
||||
# must return complete
|
||||
assert executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()])
|
||||
assert executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
# must move files (once)
|
||||
move_mock.assert_called_once()
|
||||
# must sign package
|
||||
@ -158,14 +203,23 @@ def test_process_update_group(executor: Executor, package_python_schedule: Packa
|
||||
repo_add_mock = mocker.patch("ahriman.core.alpm.repo.Repo.add")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_success")
|
||||
|
||||
executor.process_update([Path(package.filename) for package in package_python_schedule.packages.values()])
|
||||
executor.process_update([package.filepath for package in package_python_schedule.packages.values()])
|
||||
repo_add_mock.assert_has_calls([
|
||||
mock.call(executor.paths.repository / package.filename)
|
||||
mock.call(executor.paths.repository / package.filepath)
|
||||
for package in package_python_schedule.packages.values()
|
||||
], any_order=True)
|
||||
status_client_mock.assert_called_with(package_python_schedule)
|
||||
|
||||
|
||||
def test_process_empty_filename(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip update for package which does not have path
|
||||
"""
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
|
||||
|
||||
def test_process_update_failed(executor: Executor, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process update for failed package
|
||||
@ -174,7 +228,7 @@ def test_process_update_failed(executor: Executor, package_ahriman: Package, moc
|
||||
mocker.patch("ahriman.models.package.Package.load", return_value=package_ahriman)
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.set_failed")
|
||||
|
||||
executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()])
|
||||
executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
status_client_mock.assert_called_once()
|
||||
|
||||
|
||||
@ -185,4 +239,4 @@ def test_process_update_failed_on_load(executor: Executor, package_ahriman: Pack
|
||||
mocker.patch("shutil.move")
|
||||
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception())
|
||||
|
||||
assert executor.process_update([Path(package.filename) for package in package_ahriman.packages.values()])
|
||||
assert executor.process_update([package.filepath for package in package_ahriman.packages.values()])
|
||||
|
@ -31,3 +31,28 @@ def test_packages(package_ahriman: Package, package_python_schedule: Package,
|
||||
expected = set(package_ahriman.packages.keys())
|
||||
expected.update(package_python_schedule.packages.keys())
|
||||
assert set(archives) == expected
|
||||
|
||||
|
||||
def test_packages_failed(repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip packages which cannot be loaded
|
||||
"""
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.pkg.tar.xz")])
|
||||
mocker.patch("ahriman.models.package.Package.load", side_effect=Exception())
|
||||
assert not repository.packages()
|
||||
|
||||
|
||||
def test_packages_not_package(repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip not packages from iteration
|
||||
"""
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz")])
|
||||
assert not repository.packages()
|
||||
|
||||
|
||||
def test_packages_built(repository: Repository, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return build packages
|
||||
"""
|
||||
mocker.patch("pathlib.Path.iterdir", return_value=[Path("a.tar.xz"), Path("b.pkg.tar.xz")])
|
||||
assert repository.packages_built() == [Path("b.pkg.tar.xz")]
|
||||
|
@ -1,9 +1,19 @@
|
||||
import pytest
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.repository.update_handler import UpdateHandler
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_packages(update_handler: UpdateHandler) -> None:
|
||||
"""
|
||||
must raise NotImplemented for missing method
|
||||
"""
|
||||
with pytest.raises(NotImplementedError):
|
||||
update_handler.packages()
|
||||
|
||||
|
||||
def test_updates_aur(update_handler: UpdateHandler, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
|
@ -53,6 +53,24 @@ def test_repository_sign_args_skip_4(gpg: GPG) -> None:
|
||||
assert not gpg.repository_sign_args
|
||||
|
||||
|
||||
def test_sign_command(gpg_with_key: GPG) -> None:
|
||||
"""
|
||||
must generate sign command
|
||||
"""
|
||||
assert gpg_with_key.sign_command(Path("a"), gpg_with_key.default_key)
|
||||
|
||||
|
||||
def test_process(gpg_with_key: GPG, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call process method correctly
|
||||
"""
|
||||
result = [Path("a"), Path("a.sig")]
|
||||
check_output_mock = mocker.patch("ahriman.core.sign.gpg.GPG._check_output")
|
||||
|
||||
assert gpg_with_key.process(Path("a"), gpg_with_key.default_key) == result
|
||||
check_output_mock.assert_called()
|
||||
|
||||
|
||||
def test_sign_package_1(gpg_with_key: GPG, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must sign package
|
||||
|
@ -51,6 +51,21 @@ def test_cache_load_no_file(watcher: Watcher, mocker: MockerFixture) -> None:
|
||||
assert not watcher.known
|
||||
|
||||
|
||||
def test_cache_load_package_load_error(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not fail on json errors
|
||||
"""
|
||||
response = {"packages": [pytest.helpers.get_package_status_extended(package_ahriman)]}
|
||||
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
mocker.patch("pathlib.Path.open")
|
||||
mocker.patch("ahriman.models.package.Package.from_json", side_effect=Exception())
|
||||
mocker.patch("json.load", return_value=response)
|
||||
|
||||
watcher._cache_load()
|
||||
assert not watcher.known
|
||||
|
||||
|
||||
def test_cache_load_unknown(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not load unknown package
|
||||
|
@ -1,5 +1,6 @@
|
||||
import json
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from requests import Response
|
||||
@ -44,6 +45,14 @@ def test_add_failed(web_client: WebClient, package_ahriman: Package, mocker: Moc
|
||||
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_add_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during addition
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_get_all(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return all packages status
|
||||
@ -69,6 +78,14 @@ def test_get_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
assert web_client.get(None) == []
|
||||
|
||||
|
||||
def test_get_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during status getting
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=requests.exceptions.HTTPError())
|
||||
assert web_client.get(None) == []
|
||||
|
||||
|
||||
def test_get_single(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return single package status
|
||||
@ -109,6 +126,14 @@ def test_get_self_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
assert web_client.get_self().status == BuildStatusEnum.Unknown
|
||||
|
||||
|
||||
def test_get_self_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during service status getting
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=requests.exceptions.HTTPError())
|
||||
assert web_client.get_self().status == BuildStatusEnum.Unknown
|
||||
|
||||
|
||||
def test_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process package removal
|
||||
@ -127,6 +152,14 @@ def test_remove_failed(web_client: WebClient, package_ahriman: Package, mocker:
|
||||
web_client.remove(package_ahriman.base)
|
||||
|
||||
|
||||
def test_remove_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during removal
|
||||
"""
|
||||
mocker.patch("requests.delete", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.remove(package_ahriman.base)
|
||||
|
||||
|
||||
def test_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process package update
|
||||
@ -145,6 +178,14 @@ def test_update_failed(web_client: WebClient, package_ahriman: Package, mocker:
|
||||
web_client.update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_update_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during update
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_update_self(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process service update
|
||||
@ -161,3 +202,11 @@ def test_update_self_failed(web_client: WebClient, mocker: MockerFixture) -> Non
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=Exception())
|
||||
web_client.update_self(BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_update_self_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during service update
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.update_self(BuildStatusEnum.Unknown)
|
||||
|
@ -105,6 +105,14 @@ def test_load_includes_missing(configuration: Configuration) -> None:
|
||||
configuration.load_includes()
|
||||
|
||||
|
||||
def test_load_includes_no_option(configuration: Configuration) -> None:
|
||||
"""
|
||||
must not fail if no option set
|
||||
"""
|
||||
configuration.remove_option("settings", "include")
|
||||
configuration.load_includes()
|
||||
|
||||
|
||||
def test_load_logging_fallback(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must fallback to stderr without errors
|
||||
|
@ -4,6 +4,7 @@ import subprocess
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.exceptions import InvalidOption
|
||||
from ahriman.core.util import check_output, package_like, pretty_datetime, pretty_size
|
||||
from ahriman.models.package import Package
|
||||
|
||||
@ -124,6 +125,14 @@ def test_pretty_size_pbytes() -> None:
|
||||
assert abbrev == "GiB"
|
||||
|
||||
|
||||
def test_pretty_size_pbytes_failure() -> None:
|
||||
"""
|
||||
must raise exception if level >= 4 supplied
|
||||
"""
|
||||
with pytest.raises(InvalidOption):
|
||||
pretty_size(42 * 1024 * 1024 * 1024 * 1024, 4).split()
|
||||
|
||||
|
||||
def test_pretty_size_empty() -> None:
|
||||
"""
|
||||
must generate empty string for None value
|
||||
|
@ -18,6 +18,16 @@ def test_upload_failure(configuration: Configuration, mocker: MockerFixture) ->
|
||||
Upload.load("x86_64", configuration, UploadSettings.Rsync.name).run(Path("path"))
|
||||
|
||||
|
||||
def test_report_dummy(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must construct dummy upload class
|
||||
"""
|
||||
mocker.patch("ahriman.models.upload_settings.UploadSettings.from_option", return_value=UploadSettings.Disabled)
|
||||
upload_mock = mocker.patch("ahriman.core.upload.upload.Upload.sync")
|
||||
Upload.load("x86_64", configuration, UploadSettings.Disabled.name).run(Path("path"))
|
||||
upload_mock.assert_called_once()
|
||||
|
||||
|
||||
def test_upload_rsync(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must upload via rsync
|
||||
|
@ -1,3 +1,5 @@
|
||||
import datetime
|
||||
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
|
||||
|
||||
@ -36,3 +38,59 @@ def test_build_status_from_json_view(build_status_failed: BuildStatus) -> None:
|
||||
must construct same object from json
|
||||
"""
|
||||
assert BuildStatus.from_json(build_status_failed.view()) == build_status_failed
|
||||
|
||||
|
||||
def test_build_status_pretty_print(build_status_failed: BuildStatus) -> None:
|
||||
"""
|
||||
must return string in pretty print function
|
||||
"""
|
||||
assert build_status_failed.pretty_print()
|
||||
assert isinstance(build_status_failed.pretty_print(), str)
|
||||
|
||||
|
||||
def test_build_status_eq(build_status_failed: BuildStatus) -> None:
|
||||
"""
|
||||
must be equal
|
||||
"""
|
||||
other = BuildStatus.from_json(build_status_failed.view())
|
||||
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)
|
||||
|
@ -124,6 +124,17 @@ def test_from_build(package_ahriman: Package, mocker: MockerFixture, resource_pa
|
||||
assert package_ahriman == package
|
||||
|
||||
|
||||
def test_from_build_failed(package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise exception if there are errors during srcinfo load
|
||||
"""
|
||||
mocker.patch("pathlib.Path.read_text", return_value="")
|
||||
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
|
||||
|
||||
with pytest.raises(InvalidPackageInfo):
|
||||
Package.from_build(Path("path"), package_ahriman.aur_url)
|
||||
|
||||
|
||||
def test_from_json_view_1(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must construct same object from json
|
||||
@ -190,6 +201,17 @@ def test_load_failure(package_ahriman: Package, pyalpm_handle: MagicMock, mocker
|
||||
Package.load(Path("path"), pyalpm_handle, package_ahriman.aur_url)
|
||||
|
||||
|
||||
def test_dependencies_failed(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise exception if there are errors during srcinfo load
|
||||
"""
|
||||
mocker.patch("pathlib.Path.read_text", return_value="")
|
||||
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
|
||||
|
||||
with pytest.raises(InvalidPackageInfo):
|
||||
Package.dependencies(Path("path"))
|
||||
|
||||
|
||||
def test_dependencies_with_version(mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must load correct list of dependencies with version
|
||||
@ -227,12 +249,25 @@ def test_actual_version_vcs(package_tpacpi_bat_git: Package, repository_paths: R
|
||||
assert package_tpacpi_bat_git.actual_version(repository_paths) == "3.1.r13.g4959b52-1"
|
||||
|
||||
|
||||
def test_actual_version_srcinfo_failed(package_tpacpi_bat_git: Package, repository_paths: RepositoryPaths,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return same version in case if exception occurred
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package._check_output", side_effect=Exception())
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
|
||||
|
||||
assert package_tpacpi_bat_git.actual_version(repository_paths) == package_tpacpi_bat_git.version
|
||||
|
||||
|
||||
def test_actual_version_vcs_failed(package_tpacpi_bat_git: Package, repository_paths: RepositoryPaths,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return same version in case if exception occurred
|
||||
"""
|
||||
mocker.patch("ahriman.models.package.Package._check_output", side_effect=Exception())
|
||||
mocker.patch("pathlib.Path.read_text", return_value="")
|
||||
mocker.patch("ahriman.models.package.parse_srcinfo", return_value=({"packages": {}}, ["an error"]))
|
||||
mocker.patch("ahriman.models.package.Package._check_output")
|
||||
mocker.patch("ahriman.core.build_tools.task.Task.fetch")
|
||||
|
||||
assert package_tpacpi_bat_git.actual_version(repository_paths) == package_tpacpi_bat_git.version
|
||||
@ -253,3 +288,11 @@ def test_is_outdated_true(package_ahriman: Package, repository_paths: Repository
|
||||
other.version = other.version.replace("-1", "-2")
|
||||
|
||||
assert package_ahriman.is_outdated(other, repository_paths)
|
||||
|
||||
|
||||
def test_build_status_pretty_print(package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return string in pretty print function
|
||||
"""
|
||||
assert package_ahriman.pretty_print()
|
||||
assert isinstance(package_ahriman.pretty_print(), str)
|
||||
|
@ -1,3 +1,4 @@
|
||||
from dataclasses import asdict
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.models.package_description import PackageDescription
|
||||
@ -19,6 +20,22 @@ def test_filepath_empty(package_description_ahriman: PackageDescription) -> None
|
||||
assert package_description_ahriman.filepath is None
|
||||
|
||||
|
||||
def test_from_json(package_description_ahriman: PackageDescription) -> None:
|
||||
"""
|
||||
must construct description from json object
|
||||
"""
|
||||
assert PackageDescription.from_json(asdict(package_description_ahriman)) == package_description_ahriman
|
||||
|
||||
|
||||
def test_from_json_with_unknown_fields(package_description_ahriman: PackageDescription) -> None:
|
||||
"""
|
||||
must construct description from json object containing unknown fields
|
||||
"""
|
||||
dump = asdict(package_description_ahriman)
|
||||
dump.update(unknown_field="value")
|
||||
assert PackageDescription.from_json(dump) == package_description_ahriman
|
||||
|
||||
|
||||
def test_from_package(package_description_ahriman: PackageDescription,
|
||||
pyalpm_package_description_ahriman: MagicMock) -> None:
|
||||
"""
|
||||
|
@ -1,4 +1,5 @@
|
||||
from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
|
||||
@ -35,3 +36,14 @@ async def test_post_exception(client: TestClient) -> None:
|
||||
"""
|
||||
post_response = await client.post("/api/v1/ahriman", json={})
|
||||
assert post_response.status == 400
|
||||
|
||||
|
||||
async def test_post_exception_inside(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
exception handler must handle 500 errors
|
||||
"""
|
||||
payload = {"status": BuildStatusEnum.Success.value}
|
||||
mocker.patch("ahriman.core.status.watcher.Watcher.update_self", side_effect=Exception())
|
||||
|
||||
post_response = await client.post("/api/v1/ahriman", json=payload)
|
||||
assert post_response.status == 500
|
||||
|
Loading…
Reference in New Issue
Block a user