Compare commits

...

3 Commits

Author SHA1 Message Date
89f1b6ea0f do not update unknown status 2024-05-20 20:45:01 +03:00
ccb6aaee39 review changes 2024-05-20 17:11:27 +03:00
13c063c99d simplify watcher class 2024-05-20 14:46:13 +03:00
22 changed files with 325 additions and 530 deletions

View File

@ -218,21 +218,6 @@ class PackageOperations(Operations):
)
}
def package_base_update(self, package: Package, repository_id: RepositoryId | None = None) -> None:
"""
update package base only
Args:
package(Package): package properties
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> None:
self._package_update_insert_base(connection, package, repository_id)
return self.with_connection(run, commit=True)
def package_remove(self, package_base: str, repository_id: RepositoryId | None = None) -> None:
"""
remove package from database
@ -287,26 +272,6 @@ class PackageOperations(Operations):
return self.with_connection(lambda connection: list(run(connection)))
def remotes_get(self, repository_id: RepositoryId | None = None) -> dict[str, RemoteSource]:
"""
get packages remotes based on current settings
Args:
repository_id(RepositoryId, optional): repository unique identifier override (Default value = None)
Returns:
dict[str, RemoteSource]: map of package base to its remote sources
"""
repository_id = repository_id or self._repository_id
def run(connection: Connection) -> dict[str, Package]:
return self._packages_get_select_package_bases(connection, repository_id)
return {
package_base: package.remote
for package_base, package in self.with_connection(run).items()
}
def status_update(self, package_base: str, status: BuildStatus, repository_id: RepositoryId | None = None) -> None:
"""
insert base package status into table

View File

@ -22,6 +22,7 @@ import logging
from typing import Self
from ahriman.core.configuration import Configuration
from ahriman.core.status import Client
from ahriman.models.repository_id import RepositoryId
@ -49,8 +50,6 @@ class HttpLogHandler(logging.Handler):
# we don't really care about those parameters because they will be handled by the reporter
logging.Handler.__init__(self)
# client has to be imported here because of circular imports
from ahriman.core.status import Client
self.reporter = Client.load(repository_id, configuration, report=report)
self.suppress_errors = suppress_errors

View File

@ -43,14 +43,15 @@ class PackageInfo(RepositoryProperties):
Returns:
list[Package]: list of read packages
"""
sources = {package.base: package.remote for package, _, in self.reporter.package_get(None)}
result: dict[str, Package] = {}
# we are iterating over bases, not single packages
for full_path in packages:
try:
local = Package.from_archive(full_path, self.pacman)
remote, _ = next(iter(self.reporter.package_get(local.base)), (None, None))
if remote is not None: # update source with remote
local.remote = remote.remote
if (source := sources.get(local.base)) is not None: # update source with remote
local.remote = source
current = result.setdefault(local.base, local)
if current.version != local.version:

View File

@ -79,19 +79,6 @@ class Client:
return make_local_client()
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
"""
add new package with status
Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def package_changes_get(self, package_base: str) -> Changes:
"""
get package changes
@ -258,9 +245,9 @@ class Client:
"""
raise NotImplementedError
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
"""
update package build status. Unlike :func:`package_add()` it does not update package properties
update package build status. Unlike :func:`package_update()` it does not update package properties
Args:
package_base(str): package base to update
@ -271,6 +258,19 @@ class Client:
"""
raise NotImplementedError
def package_update(self, package: Package, status: BuildStatusEnum) -> None:
"""
add new package or update existing one with status
Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
Raises:
NotImplementedError: not implemented method
"""
raise NotImplementedError
def set_building(self, package_base: str) -> None:
"""
set package status to building
@ -278,7 +278,7 @@ class Client:
Args:
package_base(str): package base to update
"""
return self.package_update(package_base, BuildStatusEnum.Building)
self.package_status_update(package_base, BuildStatusEnum.Building)
def set_failed(self, package_base: str) -> None:
"""
@ -287,7 +287,7 @@ class Client:
Args:
package_base(str): package base to update
"""
return self.package_update(package_base, BuildStatusEnum.Failed)
self.package_status_update(package_base, BuildStatusEnum.Failed)
def set_pending(self, package_base: str) -> None:
"""
@ -296,7 +296,7 @@ class Client:
Args:
package_base(str): package base to update
"""
return self.package_update(package_base, BuildStatusEnum.Pending)
self.package_status_update(package_base, BuildStatusEnum.Pending)
def set_success(self, package: Package) -> None:
"""
@ -305,16 +305,19 @@ class Client:
Args:
package(Package): current package properties
"""
return self.package_add(package, BuildStatusEnum.Success)
self.package_update(package, BuildStatusEnum.Success)
def set_unknown(self, package: Package) -> None:
"""
set package status to unknown
set package status to unknown. Unlike other methods, this method also checks if package is known,
and - in case if it is - it silently skips updatd
Args:
package(Package): current package properties
"""
return self.package_add(package, BuildStatusEnum.Unknown)
if self.package_get(package.base):
return # skip update in case if package is already known
self.package_update(package, BuildStatusEnum.Unknown)
def status_get(self) -> InternalStatus:
"""

View File

@ -48,17 +48,6 @@ class LocalClient(Client):
self.database = database
self.repository_id = repository_id
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
"""
add new package with status
Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
"""
self.database.package_update(package, self.repository_id)
self.database.status_update(package.base, BuildStatus(status), self.repository_id)
def package_changes_get(self, package_base: str) -> Changes:
"""
get package changes
@ -197,12 +186,29 @@ class LocalClient(Client):
"""
self.database.package_clear(package_base)
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
"""
update package build status. Unlike :func:`package_add()` it does not update package properties
update package build status. Unlike :func:`package_update()` it does not update package properties
Args:
package_base(str): package base to update
status(BuildStatusEnum): current package build status
Raises:
NotImplementedError: not implemented method
"""
self.database.status_update(package_base, BuildStatus(status), self.repository_id)
def package_update(self, package: Package, status: BuildStatusEnum) -> None:
"""
add new package or update existing one with status
Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
Raises:
NotImplementedError: not implemented method
"""
self.database.package_update(package, self.repository_id)
self.database.status_update(package.base, BuildStatus(status), self.repository_id)

View File

@ -17,7 +17,9 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
from collections.abc import Callable
from threading import Lock
from typing import Any, Self
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.log import LazyLogging
@ -76,65 +78,13 @@ class Watcher(LazyLogging):
for package, status in self.client.package_get(None)
}
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
"""
update package
package_changes_get: Callable[[str], Changes]
Args:
package(Package): package description
status(BuildStatusEnum): new build status
"""
with self._lock:
self._known[package.base] = (package, BuildStatus(status))
self.client.package_add(package, status)
package_changes_update: Callable[[str, Changes], None]
def package_changes_get(self, package_base: str) -> Changes:
"""
retrieve package changes
package_dependencies_get: Callable[[str], Dependencies]
Args:
package_base(str): package base
Returns:
Changes: package changes if available
"""
_ = self.package_get(package_base)
return self.client.package_changes_get(package_base)
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)
package_dependencies_update: Callable[[str, Dependencies], None]
def package_get(self, package_base: str) -> tuple[Package, BuildStatus]:
"""
@ -155,32 +105,7 @@ class Watcher(LazyLogging):
except KeyError:
raise UnknownPackageError(package_base) from 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:
def package_logs_add(self, log_record_id: LogRecordId, created: float, message: str) -> None:
"""
make new log record into database
@ -195,40 +120,15 @@ class Watcher(LazyLogging):
self._last_log_record_id = log_record_id
self.client.package_logs_add(log_record_id, created, message)
def package_patches_get(self, package_base: str, variable: str | None) -> list[PkgbuildPatch]:
"""
get patches for the package
package_logs_get: Callable[[str, int, int], list[tuple[float, str]]]
Args:
package_base(str): package base
variable(str | None): patch variable name if any
package_logs_remove: Callable[[str, str | None], None]
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_get: Callable[[str, str | None], list[PkgbuildPatch]]
def package_patches_remove(self, package_base: str, variable: str) -> None:
"""
remove package patch
package_patches_remove: Callable[[str, str], 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)
package_patches_update: Callable[[str, PkgbuildPatch], None]
def package_remove(self, package_base: str) -> None:
"""
@ -242,7 +142,7 @@ class Watcher(LazyLogging):
self.client.package_remove(package_base)
self.package_logs_remove(package_base, None)
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
"""
update package status
@ -253,7 +153,19 @@ class Watcher(LazyLogging):
package, _ = self.package_get(package_base)
with self._lock:
self._known[package_base] = (package, BuildStatus(status))
self.client.package_update(package_base, status)
self.client.package_status_update(package_base, status)
def package_update(self, package: Package, status: BuildStatusEnum) -> None:
"""
update package
Args:
package(Package): package description
status(BuildStatusEnum): new build status
"""
with self._lock:
self._known[package.base] = (package, BuildStatus(status))
self.client.package_update(package, status)
def status_update(self, status: BuildStatusEnum) -> None:
"""
@ -263,3 +175,34 @@ class Watcher(LazyLogging):
status(BuildStatusEnum): new service 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

@ -157,22 +157,6 @@ class WebClient(Client, SyncAhrimanClient):
"""
return f"{self.address}/api/v1/status"
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
"""
add new package with status
Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
"""
payload = {
"status": status.value,
"package": package.view()
}
with contextlib.suppress(Exception):
self.make_request("POST", self._package_url(package.base),
params=self.repository_id.query(), json=payload)
def package_changes_get(self, package_base: str) -> Changes:
"""
get package changes
@ -365,19 +349,41 @@ class WebClient(Client, SyncAhrimanClient):
with contextlib.suppress(Exception):
self.make_request("DELETE", self._package_url(package_base), params=self.repository_id.query())
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
def package_status_update(self, package_base: str, status: BuildStatusEnum) -> None:
"""
update package build status. Unlike :func:`package_add()` it does not update package properties
update package build status. Unlike :func:`package_update()` it does not update package properties
Args:
package_base(str): package base to update
status(BuildStatusEnum): current package build status
Raises:
NotImplementedError: not implemented method
"""
payload = {"status": status.value}
with contextlib.suppress(Exception):
self.make_request("POST", self._package_url(package_base),
params=self.repository_id.query(), json=payload)
def package_update(self, package: Package, status: BuildStatusEnum) -> None:
"""
add new package or update existing one with status
Args:
package(Package): package properties
status(BuildStatusEnum): current package build status
Raises:
NotImplementedError: not implemented method
"""
payload = {
"status": status.value,
"package": package.view(),
}
with contextlib.suppress(Exception):
self.make_request("POST", self._package_url(package.base),
params=self.repository_id.query(), json=payload)
def status_get(self) -> InternalStatus:
"""
get internal service status

View File

@ -25,6 +25,7 @@ from typing import TypeVar
from ahriman.core.auth import Auth
from ahriman.core.configuration import Configuration
from ahriman.core.distributed import WorkersCache
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.sign.gpg import GPG
from ahriman.core.spawn import Spawn
from ahriman.core.status.watcher import Watcher
@ -218,12 +219,13 @@ class BaseView(View, CorsViewMixin):
return RepositoryId(architecture, name)
return next(iter(sorted(self.services.keys())))
def service(self, repository_id: RepositoryId | None = None) -> Watcher:
def service(self, repository_id: RepositoryId | None = None, package_base: str | None = None) -> Watcher:
"""
get status watcher instance
Args:
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:
Watcher: build status watcher instance. If no repository provided, it will return the first one
@ -234,9 +236,11 @@ class BaseView(View, CorsViewMixin):
if repository_id is None:
repository_id = self.repository_id()
try:
return self.services[repository_id]
return self.services[repository_id](package_base)
except KeyError:
raise HTTPNotFound(reason=f"Repository {repository_id.id} is unknown")
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
async def username(self) -> str | None:
"""

View File

@ -19,9 +19,8 @@
#
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.changes import Changes
from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ChangesSchema, ErrorSchema, PackageNameSchema, RepositoryIdSchema
@ -70,10 +69,7 @@ class ChangesView(StatusViewGuard, BaseView):
"""
package_base = self.request.match_info["package"]
try:
changes = self.service().package_changes_get(package_base)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
changes = self.service(package_base=package_base).package_changes_get(package_base)
return json_response(changes.view())
@ -86,7 +82,7 @@ class ChangesView(StatusViewGuard, BaseView):
400: {"description": "Bad data is supplied", "schema": ErrorSchema},
401: {"description": "Authorization required", "schema": ErrorSchema},
403: {"description": "Access is forbidden", "schema": ErrorSchema},
404: {"description": "Package base and/or repository are unknown", "schema": ErrorSchema},
404: {"description": "Repository is unknown", "schema": ErrorSchema},
500: {"description": "Internal server error", "schema": ErrorSchema},
},
security=[{"token": [POST_PERMISSION]}],
@ -113,9 +109,6 @@ class ChangesView(StatusViewGuard, BaseView):
raise HTTPBadRequest(reason=str(ex))
changes = Changes(last_commit_sha, change)
try:
self.service().package_changes_update(package_base, changes)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
self.service().package_changes_update(package_base, changes)
raise HTTPNoContent

View File

@ -19,9 +19,8 @@
#
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPBadRequest, HTTPNoContent, HTTPNotFound, Response, json_response
from aiohttp.web import HTTPBadRequest, HTTPNoContent, Response, json_response
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.dependencies import Dependencies
from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, DependenciesSchema, ErrorSchema, PackageNameSchema, RepositoryIdSchema
@ -70,10 +69,7 @@ class DependenciesView(StatusViewGuard, BaseView):
"""
package_base = self.request.match_info["package"]
try:
dependencies = self.service().package_dependencies_get(package_base)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
dependencies = self.service(package_base=package_base).package_dependencies_get(package_base)
return json_response(dependencies.view())
@ -112,9 +108,6 @@ class DependenciesView(StatusViewGuard, BaseView):
except Exception as ex:
raise HTTPBadRequest(reason=str(ex))
try:
self.service().package_dependencies_update(package_base, dependencies)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
self.service(package_base=package_base).package_dependencies_update(package_base, dependencies)
raise HTTPNoContent

View File

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

View File

@ -153,9 +153,9 @@ class PackageView(StatusViewGuard, BaseView):
try:
if package is None:
self.service().package_update(package_base, status)
self.service().package_status_update(package_base, status)
else:
self.service().package_add(package, status)
self.service().package_update(package, status)
except UnknownPackageError:
raise HTTPBadRequest(reason=f"Package {package_base} is unknown, but no package body set")

View File

@ -63,6 +63,7 @@ class PatchView(StatusViewGuard, BaseView):
"""
package_base = self.request.match_info["package"]
variable = self.request.match_info["patch"]
self.service().package_patches_remove(package_base, variable)
raise HTTPNoContent

View File

@ -19,9 +19,8 @@
#
import aiohttp_apispec # type: ignore[import-untyped]
from aiohttp.web import HTTPNotFound, Response, json_response
from aiohttp.web import Response, json_response
from ahriman.core.exceptions import UnknownPackageError
from ahriman.models.user_access import UserAccess
from ahriman.web.schemas import AuthSchema, ErrorSchema, LogSchema, PackageNameSchema, PaginationSchema
from ahriman.web.views.base import BaseView
@ -68,10 +67,8 @@ class LogsView(StatusViewGuard, BaseView):
"""
package_base = self.request.match_info["package"]
limit, offset = self.page()
try:
logs = self.service().package_logs_get(package_base, limit, offset)
except UnknownPackageError:
raise HTTPNotFound(reason=f"Package {package_base} is unknown")
logs = self.service(package_base=package_base).package_logs_get(package_base, limit, offset)
response = [
{

View File

@ -7,8 +7,6 @@ from unittest.mock import call as MockCall
from ahriman.core.database import SQLite
from ahriman.models.build_status import BuildStatus
from ahriman.models.package import Package
from ahriman.models.package_source import PackageSource
from ahriman.models.remote_source import RemoteSource
def test_package_remove_package_base(database: SQLite, connection: Connection) -> None:
@ -185,26 +183,6 @@ def test_package_update_update(database: SQLite, package_ahriman: Package) -> No
if db_package.base == package_ahriman.base) == package_ahriman.version
def test_remote_update_get(database: SQLite, package_ahriman: Package) -> None:
"""
must insert and retrieve package remote
"""
database.package_base_update(package_ahriman)
assert database.remotes_get()[package_ahriman.base] == package_ahriman.remote
def test_remote_update_update(database: SQLite, package_ahriman: Package) -> None:
"""
must perform package remote update for existing package
"""
database.package_base_update(package_ahriman)
remote_source = RemoteSource(source=PackageSource.Repository)
package_ahriman.remote = remote_source
database.package_base_update(package_ahriman)
assert database.remotes_get()[package_ahriman.base] == remote_source
def test_status_update(database: SQLite, package_ahriman: Package) -> None:
"""
must insert single package status

View File

@ -94,14 +94,6 @@ def test_load_web_client_from_legacy_unix_socket(configuration: Configuration, d
assert isinstance(Client.load(repository_id, configuration, database, report=True), WebClient)
def test_package_add(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on package addition
"""
with pytest.raises(NotImplementedError):
client.package_add(package_ahriman, BuildStatusEnum.Unknown)
def test_package_changes_get(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on package changes request
@ -198,19 +190,27 @@ def test_package_remove(client: Client, package_ahriman: Package) -> None:
client.package_remove(package_ahriman.base)
def test_package_update(client: Client, package_ahriman: Package) -> None:
def test_package_status_update(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on package update
"""
with pytest.raises(NotImplementedError):
client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
client.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown)
def test_package_update(client: Client, package_ahriman: Package) -> None:
"""
must raise not implemented on package addition
"""
with pytest.raises(NotImplementedError):
client.package_update(package_ahriman, BuildStatusEnum.Unknown)
def test_set_building(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must set building status to the package
"""
update_mock = mocker.patch("ahriman.core.status.Client.package_update")
update_mock = mocker.patch("ahriman.core.status.Client.package_status_update")
client.set_building(package_ahriman.base)
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Building)
@ -220,7 +220,7 @@ def test_set_failed(client: Client, package_ahriman: Package, mocker: MockerFixt
"""
must set failed status to the package
"""
update_mock = mocker.patch("ahriman.core.status.Client.package_update")
update_mock = mocker.patch("ahriman.core.status.Client.package_status_update")
client.set_failed(package_ahriman.base)
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Failed)
@ -230,7 +230,7 @@ def test_set_pending(client: Client, package_ahriman: Package, mocker: MockerFix
"""
must set building status to the package
"""
update_mock = mocker.patch("ahriman.core.status.Client.package_update")
update_mock = mocker.patch("ahriman.core.status.Client.package_status_update")
client.set_pending(package_ahriman.base)
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Pending)
@ -240,20 +240,32 @@ def test_set_success(client: Client, package_ahriman: Package, mocker: MockerFix
"""
must set success status to the package
"""
add_mock = mocker.patch("ahriman.core.status.Client.package_add")
update_mock = mocker.patch("ahriman.core.status.Client.package_update")
client.set_success(package_ahriman)
add_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Success)
update_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Success)
def test_set_unknown(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add new package with unknown status
"""
add_mock = mocker.patch("ahriman.core.status.Client.package_add")
mocker.patch("ahriman.core.status.Client.package_get", return_value=[])
update_mock = mocker.patch("ahriman.core.status.Client.package_update")
client.set_unknown(package_ahriman)
add_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Unknown)
update_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Unknown)
def test_set_unknown_skip(client: Client, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must skip unknown status update in case if pacakge is already known
"""
mocker.patch("ahriman.core.status.Client.package_get", return_value=[(package_ahriman, None)])
update_mock = mocker.patch("ahriman.core.status.Client.package_update")
client.set_unknown(package_ahriman)
update_mock.assert_not_called()
def test_status_get(client: Client) -> None:

View File

@ -12,18 +12,6 @@ from ahriman.models.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_package_add(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package addition
"""
package_mock = mocker.patch("ahriman.core.database.SQLite.package_update")
status_mock = mocker.patch("ahriman.core.database.SQLite.status_update")
local_client.package_add(package_ahriman, BuildStatusEnum.Success)
package_mock.assert_called_once_with(package_ahriman, local_client.repository_id)
status_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int), local_client.repository_id)
def test_package_changes_get(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must retrieve package changes
@ -173,10 +161,22 @@ def test_package_remove(local_client: LocalClient, package_ahriman: Package, moc
package_mock.assert_called_once_with(package_ahriman.base)
def test_package_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
def test_package_status_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package status
"""
status_mock = mocker.patch("ahriman.core.database.SQLite.status_update")
local_client.package_update(package_ahriman.base, BuildStatusEnum.Success)
local_client.package_status_update(package_ahriman.base, BuildStatusEnum.Success)
status_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int), local_client.repository_id)
def test_package_update(local_client: LocalClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package addition
"""
package_mock = mocker.patch("ahriman.core.database.SQLite.package_update")
status_mock = mocker.patch("ahriman.core.database.SQLite.status_update")
local_client.package_update(package_ahriman, BuildStatusEnum.Success)
package_mock.assert_called_once_with(package_ahriman, local_client.repository_id)
status_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int), local_client.repository_id)

View File

@ -1,16 +1,12 @@
import pytest
from pytest_mock import MockerFixture
from unittest.mock import call as MockCall
from ahriman.core.exceptions import UnknownPackageError
from ahriman.core.status.watcher import Watcher
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.package import Package
from ahriman.models.pkgbuild_patch import PkgbuildPatch
def test_packages(watcher: Watcher, package_ahriman: Package) -> None:
@ -50,93 +46,6 @@ def test_load_known(watcher: Watcher, package_ahriman: Package, mocker: MockerFi
assert status.status == BuildStatusEnum.Success
def test_package_add(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add package to cache
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_add")
watcher.package_add(package_ahriman, BuildStatusEnum.Unknown)
assert watcher.packages
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:
"""
must return package status
@ -155,107 +64,44 @@ def test_package_get_failed(watcher: Watcher, package_ahriman: Package) -> None:
watcher.package_get(package_ahriman.base)
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:
def test_package_logs_add_new(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must create package logs record for new package
"""
delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove")
delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True)
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)
assert watcher._last_log_record_id != log_record_id
watcher.package_logs_update(log_record_id, 42.01, "log record")
watcher.package_logs_add(log_record_id, 42.01, "log record")
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")
assert watcher._last_log_record_id == log_record_id
def test_package_logs_update_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
def test_package_logs_add_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must create package logs record for current package
"""
delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove")
delete_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True)
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)
watcher._last_log_record_id = log_record_id
watcher.package_logs_update(log_record_id, 42.01, "log record")
watcher.package_logs_add(log_record_id, 42.01, "log record")
delete_mock.assert_not_called()
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:
"""
must remove package base
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_remove")
logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove")
logs_mock = mocker.patch("ahriman.core.status.watcher.Watcher.package_logs_remove", create=True)
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_remove(package_ahriman.base)
@ -273,26 +119,37 @@ def test_package_remove_unknown(watcher: Watcher, package_ahriman: Package, mock
cache_mock.assert_called_once_with(package_ahriman.base)
def test_package_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
def test_package_status_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must update package status only for known package
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_update")
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_status_update")
watcher._known = {package_ahriman.base: (package_ahriman, BuildStatus())}
watcher.package_update(package_ahriman.base, BuildStatusEnum.Success)
watcher.package_status_update(package_ahriman.base, BuildStatusEnum.Success)
cache_mock.assert_called_once_with(package_ahriman.base, pytest.helpers.anyvar(int))
package, status = watcher._known[package_ahriman.base]
assert package == package_ahriman
assert status.status == BuildStatusEnum.Success
def test_package_update_unknown(watcher: Watcher, package_ahriman: Package) -> None:
def test_package_status_update_unknown(watcher: Watcher, package_ahriman: Package) -> None:
"""
must fail on unknown package status update only
"""
with pytest.raises(UnknownPackageError):
watcher.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
watcher.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown)
def test_package_update(watcher: Watcher, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must add package to cache
"""
cache_mock = mocker.patch("ahriman.core.status.local_client.LocalClient.package_update")
watcher.package_update(package_ahriman, BuildStatusEnum.Unknown)
assert watcher.packages
cache_mock.assert_called_once_with(package_ahriman, pytest.helpers.anyvar(int))
def test_status_update(watcher: Watcher) -> None:
@ -301,3 +158,41 @@ def test_status_update(watcher: Watcher) -> None:
"""
watcher.status_update(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

@ -97,59 +97,6 @@ def test_status_url(web_client: WebClient) -> None:
assert web_client._status_url().endswith("/api/v1/status")
def test_package_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package addition
"""
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
payload = pytest.helpers.get_package_status(package_ahriman)
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
params=web_client.repository_id.query(), json=payload)
def test_package_add_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during addition
"""
mocker.patch("requests.Session.request", side_effect=Exception())
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
def test_package_add_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during addition
"""
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
def test_package_add_failed_suppress(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during addition and don't log
"""
web_client.suppress_errors = True
mocker.patch("requests.Session.request", side_effect=Exception())
logging_mock = mocker.patch("logging.exception")
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
logging_mock.assert_not_called()
def test_package_add_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during addition and don't log
"""
web_client.suppress_errors = True
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
logging_mock = mocker.patch("logging.exception")
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
logging_mock.assert_not_called()
def test_package_changes_get(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must get changes
@ -785,13 +732,13 @@ def test_package_remove_failed_http_error(web_client: WebClient, package_ahriman
web_client.package_remove(package_ahriman.base)
def test_package_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
def test_package_status_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package update
"""
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
web_client.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown)
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
params=web_client.repository_id.query(),
json={
@ -799,21 +746,75 @@ def test_package_update(web_client: WebClient, package_ahriman: Package, mocker:
})
def test_package_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
def test_package_status_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during update
"""
mocker.patch("requests.Session.request", side_effect=Exception())
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
web_client.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown)
def test_package_status_update_failed_http_error(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during update
"""
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
web_client.package_status_update(package_ahriman.base, BuildStatusEnum.Unknown)
def test_package_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must process package addition
"""
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
payload = pytest.helpers.get_package_status(package_ahriman)
web_client.package_update(package_ahriman, BuildStatusEnum.Unknown)
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
params=web_client.repository_id.query(), json=payload)
def test_package_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during addition
"""
mocker.patch("requests.Session.request", side_effect=Exception())
web_client.package_update(package_ahriman, BuildStatusEnum.Unknown)
def test_package_update_failed_http_error(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during update
must suppress HTTP exception happened during addition
"""
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
web_client.package_update(package_ahriman, BuildStatusEnum.Unknown)
def test_package_update_failed_suppress(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
"""
must suppress any exception happened during addition and don't log
"""
web_client.suppress_errors = True
mocker.patch("requests.Session.request", side_effect=Exception())
logging_mock = mocker.patch("logging.exception")
web_client.package_update(package_ahriman, BuildStatusEnum.Unknown)
logging_mock.assert_not_called()
def test_package_update_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package,
mocker: MockerFixture) -> None:
"""
must suppress HTTP exception happened during addition and don't log
"""
web_client.suppress_errors = True
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
logging_mock = mocker.patch("logging.exception")
web_client.package_update(package_ahriman, BuildStatusEnum.Unknown)
logging_mock.assert_not_called()
def test_status_get(web_client: WebClient, mocker: MockerFixture) -> None:

View File

@ -204,6 +204,15 @@ def test_service_not_found(base: BaseView) -> None:
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(HTTPNotFound):
base.service(package_base="base")
async def test_username(base: BaseView, mocker: MockerFixture) -> None:
"""
must return identity of logged-in user

View File

@ -82,14 +82,3 @@ async def test_post_exception(client: TestClient, package_ahriman: Package) -> N
response = await client.post(f"/api/v1/packages/{package_ahriman.base}/changes", json=[])
assert response.status == 400
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()})
await client.post(f"/api/v1/packages/{package_ahriman.base}/logs",
json={"created": 42.0, "message": "message", "version": "42"})
json={"created": 42.0, "message": "message 1", "version": "42"})
await client.post(f"/api/v1/packages/{package_python_schedule.base}/logs",
json={"created": 42.0, "message": "message", "version": "42"})
json={"created": 42.0, "message": "message 2", "version": "42"})
request_schema = pytest.helpers.schema_request(LogsView.delete, location="querystring")
payload = {}