Compare commits

..

2 Commits

14 changed files with 290 additions and 124 deletions

View File

@ -177,7 +177,7 @@ class Pacman(LazyLogging):
PacmanDatabase(database, self.configuration).sync(force=force) PacmanDatabase(database, self.configuration).sync(force=force)
transaction.release() transaction.release()
def files(self, packages: Iterable[str] | None = None) -> dict[str, set[str]]: def files(self, packages: Iterable[str] | None = None) -> dict[str, set[Path]]:
""" """
extract list of known packages from the databases extract list of known packages from the databases
@ -185,11 +185,11 @@ class Pacman(LazyLogging):
packages(Iterable[str] | None, optional): filter by package names (Default value = None) packages(Iterable[str] | None, optional): filter by package names (Default value = None)
Returns: Returns:
dict[str, set[str]]: map of package name to its list of files dict[str, set[Path]]: map of package name to its list of files
""" """
packages = packages or [] packages = packages or []
def extract(tar: tarfile.TarFile) -> Generator[tuple[str, set[str]], None, None]: def extract(tar: tarfile.TarFile) -> Generator[tuple[str, set[Path]], None, None]:
for descriptor in filter(lambda info: info.path.endswith("/files"), tar.getmembers()): for descriptor in filter(lambda info: info.path.endswith("/files"), tar.getmembers()):
package, *_ = str(Path(descriptor.path).parent).rsplit("-", 2) package, *_ = str(Path(descriptor.path).parent).rsplit("-", 2)
if packages and package not in packages: if packages and package not in packages:
@ -197,11 +197,11 @@ class Pacman(LazyLogging):
content = tar.extractfile(descriptor) content = tar.extractfile(descriptor)
if content is None: if content is None:
continue continue
files = {filename.decode("utf8").rstrip() for filename in content.readlines()} files = {Path(filename.decode("utf8").rstrip()) for filename in content.readlines()}
yield package, files yield package, files
result: dict[str, set[str]] = {} result: dict[str, set[Path]] = {}
for database in self.handle.get_syncdbs(): for database in self.handle.get_syncdbs():
database_file = self.repository_paths.pacman / "sync" / f"{database.name}.files.tar.gz" database_file = self.repository_paths.pacman / "sync" / f"{database.name}.files.tar.gz"
if not database_file.is_file(): if not database_file.is_file():

View File

@ -18,6 +18,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Iterable from collections.abc import Iterable
from pathlib import Path
from ahriman.core.build_tools.sources import Sources from ahriman.core.build_tools.sources import Sources
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
@ -88,9 +89,9 @@ class UpdateHandler(PackageInfo, Cleaner):
Returns: Returns:
list[Package]: list of packages for which there is breaking linking list[Package]: list of packages for which there is breaking linking
""" """
def extract_files(lookup_packages: Iterable[str]) -> dict[str, set[str]]: def extract_files(lookup_packages: Iterable[str]) -> dict[Path, set[str]]:
database_files = self.pacman.files(lookup_packages) database_files = self.pacman.files(lookup_packages)
files: dict[str, set[str]] = {} files: dict[Path, set[str]] = {}
for package_name, package_files in database_files.items(): # invert map for package_name, package_files in database_files.items(): # invert map
for package_file in package_files: for package_file in package_files:
files.setdefault(package_file, set()).add(package_name) files.setdefault(package_file, set()).add(package_name)
@ -107,7 +108,7 @@ class UpdateHandler(PackageInfo, Cleaner):
filesystem = extract_files(required_packages) filesystem = extract_files(required_packages)
for path, packages in dependencies.paths.items(): for path, packages in dependencies.paths.items():
found = filesystem.get(path, set()) found = filesystem.get(Path(path), set())
if found.intersection(packages): if found.intersection(packages):
continue continue

View File

@ -17,9 +17,7 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from collections.abc import Callable
from threading import Lock from threading import Lock
from typing import Any, Self
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.log import LazyLogging from ahriman.core.log import LazyLogging
@ -90,13 +88,53 @@ class Watcher(LazyLogging):
self._known[package.base] = (package, BuildStatus(status)) self._known[package.base] = (package, BuildStatus(status))
self.client.package_add(package, status) self.client.package_add(package, status)
package_changes_get: Callable[[str], Changes] def package_changes_get(self, package_base: str) -> Changes:
"""
retrieve package changes
package_changes_update: Callable[[str, Changes], None] Args:
package_base(str): package base
package_dependencies_get: Callable[[str], Dependencies] Returns:
Changes: package changes if available
"""
_ = self.package_get(package_base)
return self.client.package_changes_get(package_base)
package_dependencies_update: Callable[[str, Dependencies], None] def package_changes_update(self, package_base: str, changes: Changes) -> None:
"""
update package changes
Args:
package_base(str): package base
changes(Changes): package changes
"""
_ = self.package_get(package_base)
self.client.package_changes_update(package_base, changes)
def package_dependencies_get(self, package_base: str) -> Dependencies:
"""
retrieve package dependencies
Args:
package_base(str): package base
Returns:
Dependencies: package dependencies if available
"""
_ = self.package_get(package_base)
return self.client.package_dependencies_get(package_base)
def package_dependencies_update(self, package_base: str, dependencies: Dependencies) -> None:
"""
update package dependencies
Args:
package_base(str): package base
dependencies(Dependencies): package dependencies
"""
_ = self.package_get(package_base)
self.client.package_dependencies_update(package_base, dependencies)
def package_get(self, package_base: str) -> tuple[Package, BuildStatus]: def package_get(self, package_base: str) -> tuple[Package, BuildStatus]:
""" """
@ -117,7 +155,32 @@ class Watcher(LazyLogging):
except KeyError: except KeyError:
raise UnknownPackageError(package_base) from None raise UnknownPackageError(package_base) from None
def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None: def package_logs_get(self, package_base: str, limit: int = -1, offset: int = 0) -> list[tuple[float, str]]:
"""
extract logs for the package base
Args:
package_base(str): package base
limit(int, optional): limit records to the specified count, -1 means unlimited (Default value = -1)
offset(int, optional): records offset (Default value = 0)
Returns:
list[tuple[float, str]]: package logs
"""
_ = self.package_get(package_base)
return self.client.package_logs_get(package_base, limit, offset)
def package_logs_remove(self, package_base: str, version: str | None) -> None:
"""
remove package related logs
Args:
package_base(str): package base
version(str): package version
"""
self.client.package_logs_remove(package_base, version)
def package_logs_update(self, log_record_id: LogRecordId, created: float, message: str) -> None:
""" """
make new log record into database make new log record into database
@ -132,15 +195,40 @@ class Watcher(LazyLogging):
self._last_log_record_id = log_record_id self._last_log_record_id = log_record_id
self.client.package_logs_add(log_record_id, created, message) self.client.package_logs_add(log_record_id, created, message)
package_logs_get: Callable[[str, int, int], list[tuple[float, str]]] def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]:
"""
get patches for the package
package_logs_remove: Callable[[str, str | None], None] Args:
package_base(str): package base
variable(str | None): patch variable name if any
package_patches_get: Callable[[str, str | None], list[PkgbuildPatch]] Returns:
list[PkgbuildPatch]: list of patches which are stored for the package
"""
# patches are package base based, we don't know (and don't differentiate) to which package does them belong
# so here we skip checking if package exists or not
return self.client.package_patches_get(package_base, variable)
package_patches_remove: Callable[[str, str], None] def package_patches_remove(self, package_base: str, variable: str) -> None:
"""
remove package patch
package_patches_update: Callable[[str, PkgbuildPatch], None] Args:
package_base(str): package base
variable(str): patch variable name
"""
self.client.package_patches_remove(package_base, variable)
def package_patches_update(self, package_base: str, patch: PkgbuildPatch) -> None:
"""
update package patch
Args:
package_base(str): package base
patch(PkgbuildPatch): package patch
"""
self.client.package_patches_update(package_base, patch)
def package_remove(self, package_base: str) -> None: def package_remove(self, package_base: str) -> None:
""" """
@ -175,34 +263,3 @@ class Watcher(LazyLogging):
status(BuildStatusEnum): new service status status(BuildStatusEnum): new service status
""" """
self.status = BuildStatus(status) self.status = BuildStatus(status)
def __call__(self, package_base: str | None) -> Self:
"""
extract client for future calls
Args:
package_base(str | None): package base to validate that package exists if applicable
Returns:
Self: instance of self to pass calls to the client
"""
if package_base is not None:
_ = self.package_get(package_base)
return self
def __getattr__(self, item: str) -> Any:
"""
proxy methods for reporter client
Args:
item(str): property name:
Returns:
Any: attribute by its name
Raises:
AttributeError: in case if no such attribute found
"""
if (method := getattr(self.client, item, None)) is not None:
return method
raise AttributeError(f"'{self.__class__.__qualname__}' object has no attribute '{item}'")

View File

@ -218,13 +218,12 @@ class BaseView(View, CorsViewMixin):
return RepositoryId(architecture, name) return RepositoryId(architecture, name)
return next(iter(sorted(self.services.keys()))) return next(iter(sorted(self.services.keys())))
def service(self, repository_id: RepositoryId | None = None, package_base: str | None = None) -> Watcher: def service(self, repository_id: RepositoryId | None = None) -> Watcher:
""" """
get status watcher instance get status watcher instance
Args: Args:
repository_id(RepositoryId | None, optional): repository unique identifier (Default value = None) repository_id(RepositoryId | None, optional): repository unique identifier (Default value = None)
package_base(str | None, optional): package base to validate if exists (Default value = None)
Returns: Returns:
Watcher: build status watcher instance. If no repository provided, it will return the first one Watcher: build status watcher instance. If no repository provided, it will return the first one
@ -235,7 +234,7 @@ class BaseView(View, CorsViewMixin):
if repository_id is None: if repository_id is None:
repository_id = self.repository_id() repository_id = self.repository_id()
try: try:
return self.services[repository_id](package_base) return self.services[repository_id]
except KeyError: except KeyError:
raise HTTPNotFound(reason=f"Repository {repository_id.id} is unknown") raise HTTPNotFound(reason=f"Repository {repository_id.id} is unknown")

View File

@ -71,7 +71,7 @@ class ChangesView(StatusViewGuard, BaseView):
package_base = self.request.match_info["package"] package_base = self.request.match_info["package"]
try: try:
changes = self.service(package_base=package_base).package_changes_get(package_base) changes = self.service().package_changes_get(package_base)
except UnknownPackageError: except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown") raise HTTPNotFound(reason=f"Package {package_base} is unknown")
@ -86,7 +86,7 @@ class ChangesView(StatusViewGuard, BaseView):
400: {"description": "Bad data is supplied", "schema": ErrorSchema}, 400: {"description": "Bad data is supplied", "schema": ErrorSchema},
401: {"description": "Authorization required", "schema": ErrorSchema}, 401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema}, 403: {"description": "Access is forbidden", "schema": ErrorSchema},
404: {"description": "Repository is unknown", "schema": ErrorSchema}, 404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema}, 500: {"description": "Internal server error", "schema": ErrorSchema},
}, },
security=[{"token": [POST_PERMISSION]}], security=[{"token": [POST_PERMISSION]}],
@ -113,6 +113,9 @@ class ChangesView(StatusViewGuard, BaseView):
raise HTTPBadRequest(reason=str(ex)) raise HTTPBadRequest(reason=str(ex))
changes = Changes(last_commit_sha, change) changes = Changes(last_commit_sha, change)
self.service().package_changes_update(package_base, changes) try:
self.service().package_changes_update(package_base, changes)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
raise HTTPNoContent raise HTTPNoContent

View File

@ -71,7 +71,7 @@ class DependenciesView(StatusViewGuard, BaseView):
package_base = self.request.match_info["package"] package_base = self.request.match_info["package"]
try: try:
dependencies = self.service(package_base=package_base).package_dependencies_get(package_base) dependencies = self.service().package_dependencies_get(package_base)
except UnknownPackageError: except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown") raise HTTPNotFound(reason=f"Package {package_base} is unknown")
@ -113,7 +113,7 @@ class DependenciesView(StatusViewGuard, BaseView):
raise HTTPBadRequest(reason=str(ex)) raise HTTPBadRequest(reason=str(ex))
try: try:
self.service(package_base=package_base).package_dependencies_update(package_base, dependencies) self.service().package_dependencies_update(package_base, dependencies)
except UnknownPackageError: except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown") raise HTTPNotFound(reason=f"Package {package_base} is unknown")

View File

@ -104,7 +104,7 @@ class LogsView(StatusViewGuard, BaseView):
try: try:
_, status = self.service().package_get(package_base) _, status = self.service().package_get(package_base)
logs = self.service(package_base=package_base).package_logs_get(package_base, -1, 0) logs = self.service().package_logs_get(package_base)
except UnknownPackageError: except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown") raise HTTPNotFound(reason=f"Package {package_base} is unknown")
@ -150,6 +150,6 @@ class LogsView(StatusViewGuard, BaseView):
except Exception as ex: except Exception as ex:
raise HTTPBadRequest(reason=str(ex)) raise HTTPBadRequest(reason=str(ex))
self.service().package_logs_add(LogRecordId(package_base, version), created, record) self.service().package_logs_update(LogRecordId(package_base, version), created, record)
raise HTTPNoContent raise HTTPNoContent

View File

@ -69,7 +69,7 @@ class LogsView(StatusViewGuard, BaseView):
package_base = self.request.match_info["package"] package_base = self.request.match_info["package"]
limit, offset = self.page() limit, offset = self.page()
try: try:
logs = self.service(package_base=package_base).package_logs_get(package_base, limit, offset) logs = self.service().package_logs_get(package_base, limit, offset)
except UnknownPackageError: except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown") raise HTTPNotFound(reason=f"Package {package_base} is unknown")

View File

@ -190,7 +190,7 @@ def test_files(pacman: Pacman, package_ahriman: Package, mocker: MockerFixture,
files = pacman.files() files = pacman.files()
assert len(files) == 2 assert len(files) == 2
assert package_ahriman.base in files assert package_ahriman.base in files
assert "usr/bin/ahriman" in files[package_ahriman.base] assert Path("usr/bin/ahriman") in files[package_ahriman.base]
open_mock.assert_called_once_with(pytest.helpers.anyvar(int), "r:gz") open_mock.assert_called_once_with(pytest.helpers.anyvar(int), "r:gz")

View File

@ -160,7 +160,7 @@ def test_updates_dependencies(update_handler: UpdateHandler, package_ahriman: Pa
mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get", mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get",
side_effect=lambda base: dependencies[base]) side_effect=lambda base: dependencies[base])
mocker.patch("ahriman.core.alpm.pacman.Pacman.files", mocker.patch("ahriman.core.alpm.pacman.Pacman.files",
return_value={"python": {"usr/lib/python3.12/site-packages"}}) return_value={"python": {Path("usr/lib/python3.12/site-packages")}})
assert update_handler.updates_dependencies(["filter"]) == [package_ahriman] assert update_handler.updates_dependencies(["filter"]) == [package_ahriman]
packages_mock.assert_called_once_with(["filter"]) packages_mock.assert_called_once_with(["filter"])
@ -175,7 +175,7 @@ def test_updates_dependencies_skip_unknown(update_handler: UpdateHandler, packag
mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get", mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get",
return_value=Dependencies()) return_value=Dependencies())
mocker.patch("ahriman.core.alpm.pacman.Pacman.files", mocker.patch("ahriman.core.alpm.pacman.Pacman.files",
return_value={"python": {"usr/lib/python3.12/site-packages"}}) return_value={"python": {Path("usr/lib/python3.12/site-packages")}})
assert update_handler.updates_dependencies(["filter"]) == [] assert update_handler.updates_dependencies(["filter"]) == []
@ -189,8 +189,8 @@ def test_updates_dependencies_partial(update_handler: UpdateHandler, package_ahr
dependencies = Dependencies({"usr": ["filesystem", "python"]}) dependencies = Dependencies({"usr": ["filesystem", "python"]})
mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get", return_value=dependencies) mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get", return_value=dependencies)
mocker.patch("ahriman.core.alpm.pacman.Pacman.files", return_value={ mocker.patch("ahriman.core.alpm.pacman.Pacman.files", return_value={
"filesystem": {"usr"}, "filesystem": {Path("usr")},
"python": {"usr"}, "python": {Path("usr")},
}) })
assert update_handler.updates_dependencies(["filter"]) == [] assert update_handler.updates_dependencies(["filter"]) == []

View File

@ -1,12 +1,16 @@
import pytest import pytest
from pytest_mock import MockerFixture from pytest_mock import MockerFixture
from unittest.mock import call as MockCall
from ahriman.core.exceptions import UnknownPackageError from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.status.watcher import Watcher from ahriman.core.status.watcher import Watcher
from ahriman.models.build_status import BuildStatus, BuildStatusEnum from ahriman.models.build_status import BuildStatus, BuildStatusEnum
from ahriman.models.changes import Changes
from ahriman.models.dependencies import Dependencies
from ahriman.models.log_record_id import LogRecordId from ahriman.models.log_record_id import LogRecordId
from ahriman.models.package import Package from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_packages(watcher: Watcher, package_ahriman: Package) -> None: def test_packages(watcher: Watcher, package_ahriman: Package) -> None:
@ -57,6 +61,82 @@ def test_package_add(watcher: Watcher, package_ahriman: Package, mocker: MockerF
cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int)) cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int))
def test_package_changes_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package changes
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_get")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_changes_get(package_ahriman.base)
cache_mock.assert_called_once_with(package_ahriman.base)
def test_package_changes_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
"""
must fail if package is unknown during fetching changes
"""
with pytest.raises(UnknownPackageError):
watcher.package_changes_get(package_ahriman.base)
def test_package_changes_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package changes
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_changes_update")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_changes_update(package_ahriman.base, Changes())
cache_mock.assert_called_once_with(package_ahriman.base, Changes())
def test_package_changes_update_failed(watcher: Watcher, package_ahriman: Package) -> None:
"""
must fail if package is unknown during updating changes
"""
with pytest.raises(UnknownPackageError):
watcher.package_changes_update(package_ahriman.base, Changes())
def test_package_dependencies_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package dependencies
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_get")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_dependencies_get(package_ahriman.base)
cache_mock.assert_called_once_with(package_ahriman.base)
def test_package_dependencies_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
"""
must fail if package is unknown during fetching dependencies
"""
with pytest.raises(UnknownPackageError):
watcher.package_dependencies_get(package_ahriman.base)
def test_package_dependencies_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package dependencies
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_dependencies_update")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_dependencies_update(package_ahriman.base, Dependencies())
cache_mock.assert_called_once_with(package_ahriman.base, Dependencies())
def test_package_dependencies_update_failed(watcher: Watcher, package_ahriman: Package) -> None:
"""
must fail if package is unknown during updating dependencies
"""
with pytest.raises(UnknownPackageError):
watcher.package_dependencies_update(package_ahriman.base, Dependencies())
def test_package_get(watcher: Watcher, package_ahriman: Package) -> None: def test_package_get(watcher: Watcher, package_ahriman: Package) -> None:
""" """
must return package status must return package status
@ -75,44 +155,107 @@ def test_package_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
watcher.package_get(package_ahriman.base) watcher.package_get(package_ahriman.base)
def test_package_logs_add_new(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: def test_package_logs_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return package logs
"""
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
logs_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_logs_get")
watcher.package_logs_get(package_ahriman.base, 1, 2)
logs_mock.assert_called_once_with(package_ahriman.base, 1, 2)
def test_package_logs_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
"""
must raise UnknownPackageError on logs in case of unknown package
"""
with pytest.raises(UnknownPackageError):
watcher.package_logs_get(package_ahriman.base)
def test_package_logs_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must remove package logs
"""
logs_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_logs_remove")
watcher.package_logs_remove(package_ahriman.base, "42")
logs_mock.assert_called_once_with(package_ahriman.base, "42")
def test_package_logs_update_new(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must create package logs record for new package must create package logs record for new package
""" """
delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True) delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove")
insert_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_logs_add") insert_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_logs_add")
log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version) log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version)
assert watcher._last_log_record_id != log_record_id assert watcher._last_log_record_id != log_record_id
watcher.package_logs_add(log_record_id, 42.01, "log record") watcher.package_logs_update(log_record_id, 42.01, "log record")
delete_mock.assert_called_once_with(package_ahriman.base, log_record_id.version) delete_mock.assert_called_once_with(package_ahriman.base, log_record_id.version)
insert_mock.assert_called_once_with(log_record_id, 42.01, "log record") insert_mock.assert_called_once_with(log_record_id, 42.01, "log record")
assert watcher._last_log_record_id == log_record_id assert watcher._last_log_record_id == log_record_id
def test_package_logs_add_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: def test_package_logs_update_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must create package logs record for current package must create package logs record for current package
""" """
delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True) delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove")
insert_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_logs_add") insert_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_logs_add")
log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version) log_record_id = LogRecordId(package_ahriman.base, watcher._last_log_record_id.version)
watcher._last_log_record_id = log_record_id watcher._last_log_record_id = log_record_id
watcher.package_logs_add(log_record_id, 42.01, "log record") watcher.package_logs_update(log_record_id, 42.01, "log record")
delete_mock.assert_not_called() delete_mock.assert_not_called()
insert_mock.assert_called_once_with(log_record_id, 42.01, "log record") insert_mock.assert_called_once_with(log_record_id, 42.01, "log record")
def test_package_patches_get(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must return patches for the package
"""
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
patches_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_get")
watcher.package_patches_get(package_ahriman.base, None)
watcher.package_patches_get(package_ahriman.base, "var")
patches_mock.assert_has_calls([
MockCall(package_ahriman.base, None),
MockCall(package_ahriman.base, "var"),
])
def test_package_patches_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must remove patches for the package
"""
patches_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_remove")
watcher.package_patches_remove(package_ahriman.base, "var")
patches_mock.assert_called_once_with(package_ahriman.base, "var")
def test_package_patches_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update patches for the package
"""
patch = PkgbuildPatch("key", "value")
patches_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_patches_update")
watcher.package_patches_update(package_ahriman.base, patch)
patches_mock.assert_called_once_with(package_ahriman.base, patch)
def test_package_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None: def test_package_remove(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
""" """
must remove package base must remove package base
""" """
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove") cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True) logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())} watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_remove(package_ahriman.base) watcher.package_remove(package_ahriman.base)
@ -158,41 +301,3 @@ def test_status_update(watcher: Watcher) -> None:
""" """
watcher.status_update(BuildStatusEnum.Success) watcher.status_update(BuildStatusEnum.Success)
assert watcher.status.status == BuildStatusEnum.Success assert watcher.status.status == BuildStatusEnum.Success
def test_call(watcher: Watcher, package_ahriman: Package) -> None:
"""
must return self instance if package exists
"""
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
assert watcher(package_ahriman.base)
def test_call_skip(watcher: Watcher) -> None:
"""
must return self instance if no package base set
"""
assert watcher(None)
def test_call_failed(watcher: Watcher, package_ahriman: Package) -> None:
"""
must raise UnknownPackage
"""
with pytest.raises(UnknownPackageError):
assert watcher(package_ahriman.base)
def test_getattr(watcher: Watcher) -> None:
"""
must return client method call
"""
assert watcher.package_logs_remove
def test_getattr_unknown_method(watcher: Watcher) -> None:
"""
must raise AttributeError in case if no reporter attribute found
"""
with pytest.raises(AttributeError):
assert watcher.random_method

View File

@ -7,7 +7,6 @@ from pytest_mock import MockerFixture
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from ahriman.core.configuration import Configuration from ahriman.core.configuration import Configuration
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.repository_id import RepositoryId from ahriman.models.repository_id import RepositoryId
from ahriman.models.user_access import UserAccess from ahriman.models.user_access import UserAccess
from ahriman.web.keys import WatcherKey from ahriman.web.keys import WatcherKey
@ -205,15 +204,6 @@ def test_service_not_found(base: BaseView) -> None:
base.service(RepositoryId("", "")) base.service(RepositoryId("", ""))
def test_service_package(base: BaseView, repository_id: RepositoryId, mocker: MockerFixture) -> None:
"""
must validate that package exists
"""
mocker.patch("ahriman.web.views.base.BaseView.repository_id", return_value=repository_id)
with pytest.raises(UnknownPackageError):
base.service(package_base="base")
async def test_username(base: BaseView, mocker: MockerFixture) -> None: async def test_username(base: BaseView, mocker: MockerFixture) -> None:
""" """
must return identity of logged-in user must return identity of logged-in user

View File

@ -82,3 +82,14 @@ async def test_post_exception(client: TestClient, package_ahriman: Package) -> N
response = await client.post(f"/api/v1/packages/{package_ahriman.base}/changes", json=[]) response = await client.post(f"/api/v1/packages/{package_ahriman.base}/changes", json=[])
assert response.status == 400 assert response.status == 400
assert not response_schema.validate(await response.json()) assert not response_schema.validate(await response.json())
async def test_post_not_found(client: TestClient, package_ahriman: Package) -> None:
"""
must raise exception on unknown package
"""
response_schema = pytest.helpers.schema_response(ChangesView.post, code=404)
response = await client.post(f"/api/v1/packages/{package_ahriman.base}/changes", json={})
assert response.status == 404
assert not response_schema.validate(await response.json())

View File

@ -37,9 +37,9 @@ async def test_delete(client: TestClient, package_ahriman: Package, package_pyth
json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()}) json={"status": BuildStatusEnum.Success.value, "package": package_python_schedule.view()})
await client.post(f"/api/v1/packages/{package_ahriman.base}/logs", await client.post(f"/api/v1/packages/{package_ahriman.base}/logs",
json={"created": 42.0, "message": "message 1", "version": "42"}) json={"created": 42.0, "message": "message", "version": "42"})
await client.post(f"/api/v1/packages/{package_python_schedule.base}/logs", await client.post(f"/api/v1/packages/{package_python_schedule.base}/logs",
json={"created": 42.0, "message": "message 2", "version": "42"}) json={"created": 42.0, "message": "message", "version": "42"})
request_schema = pytest.helpers.schema_request(LogsView.delete, location="querystring") request_schema = pytest.helpers.schema_request(LogsView.delete, location="querystring")
payload = {} payload = {}