mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
add remote call trigger implementation
This commit is contained in:
parent
37d3b9fa83
commit
af1803ed26
@ -85,7 +85,7 @@ Again, the most checks can be performed by `make check` command, though some add
|
||||
|
||||
Args:
|
||||
*args(Any): positional arguments
|
||||
**kwargs(Any): keyword arguments
|
||||
**kwargs(Any): keyword arguments
|
||||
"""
|
||||
self.instance_attribute = ""
|
||||
```
|
||||
|
@ -50,14 +50,14 @@ class Status(Handler):
|
||||
# we are using reporter here
|
||||
client = Application(architecture, configuration, report=True).repository.reporter
|
||||
if args.ahriman:
|
||||
service_status = client.get_internal()
|
||||
service_status = client.status_get()
|
||||
StatusPrinter(service_status.status).print(verbose=args.info)
|
||||
if args.package:
|
||||
packages: list[tuple[Package, BuildStatus]] = sum(
|
||||
(client.get(base) for base in args.package),
|
||||
(client.package_get(base) for base in args.package),
|
||||
start=[])
|
||||
else:
|
||||
packages = client.get(None)
|
||||
packages = client.package_get(None)
|
||||
|
||||
Status.check_if_empty(args.exit_code, not packages)
|
||||
|
||||
|
@ -49,10 +49,10 @@ class StatusUpdate(Handler):
|
||||
if args.action == Action.Update and args.package:
|
||||
# update packages statuses
|
||||
for package in args.package:
|
||||
client.update(package, args.status)
|
||||
client.package_update(package, args.status)
|
||||
elif args.action == Action.Update:
|
||||
# update service status
|
||||
client.update_self(args.status)
|
||||
client.status_update(args.status)
|
||||
elif args.action == Action.Remove:
|
||||
for package in args.package:
|
||||
client.remove(package)
|
||||
client.package_remove(package)
|
||||
|
@ -18,7 +18,6 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import argparse
|
||||
import time
|
||||
|
||||
from pathlib import Path
|
||||
from types import TracebackType
|
||||
@ -31,6 +30,7 @@ from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.status.client import Client
|
||||
from ahriman.core.util import check_user
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.waiter import Waiter
|
||||
|
||||
|
||||
class Lock(LazyLogging):
|
||||
@ -81,7 +81,7 @@ class Lock(LazyLogging):
|
||||
"""
|
||||
check web server version
|
||||
"""
|
||||
status = self.reporter.get_internal()
|
||||
status = self.reporter.status_get()
|
||||
if status.version is not None and status.version != __version__:
|
||||
self.logger.warning("status watcher version mismatch, our %s, their %s",
|
||||
__version__, status.version)
|
||||
@ -115,26 +115,18 @@ class Lock(LazyLogging):
|
||||
except FileExistsError:
|
||||
raise DuplicateRunError()
|
||||
|
||||
def watch(self, interval: int = 10) -> None:
|
||||
def watch(self) -> None:
|
||||
"""
|
||||
watch until lock disappear
|
||||
|
||||
Args:
|
||||
interval(int, optional): interval to check in seconds (Default value = 10)
|
||||
"""
|
||||
def is_timed_out(start: float) -> bool:
|
||||
since_start: float = time.monotonic() - start
|
||||
return self.wait_timeout != 0 and since_start > self.wait_timeout
|
||||
|
||||
# there are reasons why we are not using inotify here. First of all, if we would use it, it would bring to
|
||||
# race conditions because multiple processes will be notified in the same time. Secondly, it is good library,
|
||||
# but platform-specific, and we only need to check if file exists
|
||||
if self.path is None:
|
||||
return
|
||||
|
||||
start_time = time.monotonic()
|
||||
while not is_timed_out(start_time) and self.path.is_file():
|
||||
time.sleep(interval)
|
||||
waiter = Waiter(self.wait_timeout)
|
||||
waiter.wait(self.path.is_file)
|
||||
|
||||
def __enter__(self) -> Self:
|
||||
"""
|
||||
@ -154,7 +146,7 @@ class Lock(LazyLogging):
|
||||
self.check_version()
|
||||
self.watch()
|
||||
self.create()
|
||||
self.reporter.update_self(BuildStatusEnum.Building)
|
||||
self.reporter.status_update(BuildStatusEnum.Building)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type: type[Exception] | None, exc_val: Exception | None,
|
||||
@ -172,5 +164,5 @@ class Lock(LazyLogging):
|
||||
"""
|
||||
self.clear()
|
||||
status = BuildStatusEnum.Success if exc_val is None else BuildStatusEnum.Failed
|
||||
self.reporter.update_self(status)
|
||||
self.reporter.status_update(status)
|
||||
return False
|
||||
|
@ -31,17 +31,15 @@ class HttpLogHandler(logging.Handler):
|
||||
|
||||
Attributes:
|
||||
reporter(Client): build status reporter instance
|
||||
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
||||
"""
|
||||
|
||||
def __init__(self, configuration: Configuration, *, report: bool, suppress_errors: bool) -> None:
|
||||
def __init__(self, configuration: Configuration, *, report: bool) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
report(bool): force enable or disable reporting
|
||||
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
||||
"""
|
||||
# we don't really care about those parameters because they will be handled by the reporter
|
||||
logging.Handler.__init__(self)
|
||||
@ -49,7 +47,6 @@ class HttpLogHandler(logging.Handler):
|
||||
# client has to be imported here because of circular imports
|
||||
from ahriman.core.status.client import Client
|
||||
self.reporter = Client.load(configuration, report=report)
|
||||
self.suppress_errors = suppress_errors
|
||||
|
||||
@classmethod
|
||||
def load(cls, configuration: Configuration, *, report: bool) -> Self:
|
||||
@ -68,8 +65,7 @@ class HttpLogHandler(logging.Handler):
|
||||
if (handler := next((handler for handler in root.handlers if isinstance(handler, cls)), None)) is not None:
|
||||
return handler # there is already registered instance
|
||||
|
||||
suppress_errors = configuration.getboolean("settings", "suppress_http_log_errors", fallback=False)
|
||||
handler = cls(configuration, report=report, suppress_errors=suppress_errors)
|
||||
handler = cls(configuration, report=report)
|
||||
root.addHandler(handler)
|
||||
|
||||
return handler
|
||||
@ -85,9 +81,4 @@ class HttpLogHandler(logging.Handler):
|
||||
if package_base is None:
|
||||
return # in case if no package base supplied we need just skip log message
|
||||
|
||||
try:
|
||||
self.reporter.logs(package_base, record)
|
||||
except Exception:
|
||||
if self.suppress_errors:
|
||||
return
|
||||
self.handleError(record)
|
||||
self.reporter.package_logs(package_base, record)
|
||||
|
123
src/ahriman/core/report/remote_call.py
Normal file
123
src/ahriman/core/report/remote_call.py
Normal file
@ -0,0 +1,123 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# 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 ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.core.status.web_client import WebClient
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
from ahriman.models.waiter import Waiter
|
||||
|
||||
|
||||
class RemoteCall(Report):
|
||||
"""
|
||||
trigger implementation which call remote service with update
|
||||
|
||||
Attributes:
|
||||
client(WebClient): web client instance
|
||||
update_aur(bool): check for AUR updates
|
||||
update_local(bool): check for local packages update
|
||||
update_manual(bool): check for manually built packages
|
||||
wait_timeout(int): timeout to wait external process
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
architecture(str): repository architecture
|
||||
configuration(Configuration): configuration instance
|
||||
section(str): settings section name
|
||||
"""
|
||||
Report.__init__(self, architecture, configuration)
|
||||
|
||||
self.client = WebClient(configuration)
|
||||
|
||||
self.update_aur = configuration.getboolean(section, "aur", fallback=False)
|
||||
self.update_local = configuration.getboolean(section, "local", fallback=False)
|
||||
self.update_manual = configuration.getboolean(section, "manual", fallback=False)
|
||||
|
||||
self.wait_timeout = configuration.getint(section, "wait_timeout", fallback=-1)
|
||||
|
||||
def generate(self, packages: list[Package], result: Result) -> None:
|
||||
"""
|
||||
generate report for the specified packages
|
||||
|
||||
Args:
|
||||
packages(list[Package]): list of packages to generate report
|
||||
result(Result): build result
|
||||
"""
|
||||
process_id = self.remote_update()
|
||||
self.remote_wait(process_id)
|
||||
|
||||
def is_process_alive(self, process_id: str) -> bool:
|
||||
"""
|
||||
check if process is alive
|
||||
|
||||
Args:
|
||||
process_id(str): remote process id
|
||||
|
||||
Returns:
|
||||
bool: True in case if remote process is alive and False otherwise
|
||||
"""
|
||||
response = self.client.make_request("GET", f"{self.client.address}/api/v1/service/process/{process_id}")
|
||||
if response is None:
|
||||
return False
|
||||
|
||||
response_json = response.json()
|
||||
is_alive: bool = response_json["is_alive"]
|
||||
|
||||
return is_alive
|
||||
|
||||
def remote_update(self) -> str | None:
|
||||
"""
|
||||
call remote server for update
|
||||
|
||||
Returns:
|
||||
str | None: remote process id on success and ``None`` otherwise
|
||||
"""
|
||||
response = self.client.make_request(
|
||||
"POST",
|
||||
f"{self.client.address}/api/v1/service/update",
|
||||
json={
|
||||
"aur": self.update_aur,
|
||||
"local": self.update_local,
|
||||
"manual": self.update_manual,
|
||||
}
|
||||
)
|
||||
if response is None:
|
||||
return None # request terminated with error
|
||||
|
||||
response_json = response.json()
|
||||
process_id: str = response_json["process_id"]
|
||||
return process_id
|
||||
|
||||
def remote_wait(self, process_id: str | None) -> None:
|
||||
"""
|
||||
wait for remote process termination
|
||||
|
||||
Args:
|
||||
process_id(str | None): remote process id
|
||||
"""
|
||||
if process_id is None:
|
||||
return # nothing to track
|
||||
|
||||
waiter = Waiter(self.wait_timeout)
|
||||
waiter.wait(self.is_process_alive, process_id)
|
@ -93,6 +93,9 @@ class Report(LazyLogging):
|
||||
if provider == ReportSettings.Telegram:
|
||||
from ahriman.core.report.telegram import Telegram
|
||||
return Telegram(architecture, configuration, section)
|
||||
if provider == ReportSettings.RemoteCall:
|
||||
from ahriman.core.report.remote_call import RemoteCall
|
||||
return RemoteCall(architecture, configuration, section)
|
||||
return Report(architecture, configuration) # should never happen
|
||||
|
||||
def generate(self, packages: list[Package], result: Result) -> None:
|
||||
|
@ -191,6 +191,31 @@ class ReportTrigger(Trigger):
|
||||
},
|
||||
},
|
||||
},
|
||||
"remote-call": {
|
||||
"type": "dict",
|
||||
"schema": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"allowed": ["remote-call"],
|
||||
},
|
||||
"aur": {
|
||||
"type": "boolean",
|
||||
"coerce": "boolean",
|
||||
},
|
||||
"local": {
|
||||
"type": "boolean",
|
||||
"coerce": "boolean",
|
||||
},
|
||||
"manual": {
|
||||
"type": "boolean",
|
||||
"coerce": "boolean",
|
||||
},
|
||||
"wait_timeout": {
|
||||
"type": "integer",
|
||||
"coerce": "integer",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
|
@ -121,7 +121,7 @@ class Executor(Cleaner):
|
||||
self.database.build_queue_clear(package_base)
|
||||
self.database.patches_remove(package_base, [])
|
||||
self.database.logs_remove(package_base, None)
|
||||
self.reporter.remove(package_base) # we only update status page in case of base removal
|
||||
self.reporter.package_remove(package_base) # we only update status page in case of base removal
|
||||
except Exception:
|
||||
self.logger.exception("could not remove base %s", package_base)
|
||||
|
||||
|
@ -60,7 +60,7 @@ class Client:
|
||||
return WebClient(configuration)
|
||||
return Client()
|
||||
|
||||
def add(self, package: Package, status: BuildStatusEnum) -> None:
|
||||
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
add new package with status
|
||||
|
||||
@ -69,7 +69,7 @@ class Client:
|
||||
status(BuildStatusEnum): current package build status
|
||||
"""
|
||||
|
||||
def get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
|
||||
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
|
||||
"""
|
||||
get package status
|
||||
|
||||
@ -82,16 +82,7 @@ class Client:
|
||||
del package_base
|
||||
return []
|
||||
|
||||
def get_internal(self) -> InternalStatus:
|
||||
"""
|
||||
get internal service status
|
||||
|
||||
Returns:
|
||||
InternalStatus: current internal (web) service status
|
||||
"""
|
||||
return InternalStatus(status=BuildStatus())
|
||||
|
||||
def logs(self, package_base: str, record: logging.LogRecord) -> None:
|
||||
def package_logs(self, package_base: str, record: logging.LogRecord) -> None:
|
||||
"""
|
||||
post log record
|
||||
|
||||
@ -100,7 +91,7 @@ class Client:
|
||||
record(logging.LogRecord): log record to post to api
|
||||
"""
|
||||
|
||||
def remove(self, package_base: str) -> None:
|
||||
def package_remove(self, package_base: str) -> None:
|
||||
"""
|
||||
remove packages from watcher
|
||||
|
||||
@ -108,7 +99,7 @@ class Client:
|
||||
package_base(str): package base to remove
|
||||
"""
|
||||
|
||||
def update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
update package build status. Unlike ``add`` it does not update package properties
|
||||
|
||||
@ -117,14 +108,6 @@ class Client:
|
||||
status(BuildStatusEnum): current package build status
|
||||
"""
|
||||
|
||||
def update_self(self, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
update ahriman status itself
|
||||
|
||||
Args:
|
||||
status(BuildStatusEnum): current ahriman status
|
||||
"""
|
||||
|
||||
def set_building(self, package_base: str) -> None:
|
||||
"""
|
||||
set package status to building
|
||||
@ -132,7 +115,7 @@ class Client:
|
||||
Args:
|
||||
package_base(str): package base to update
|
||||
"""
|
||||
return self.update(package_base, BuildStatusEnum.Building)
|
||||
return self.package_update(package_base, BuildStatusEnum.Building)
|
||||
|
||||
def set_failed(self, package_base: str) -> None:
|
||||
"""
|
||||
@ -141,7 +124,7 @@ class Client:
|
||||
Args:
|
||||
package_base(str): package base to update
|
||||
"""
|
||||
return self.update(package_base, BuildStatusEnum.Failed)
|
||||
return self.package_update(package_base, BuildStatusEnum.Failed)
|
||||
|
||||
def set_pending(self, package_base: str) -> None:
|
||||
"""
|
||||
@ -150,7 +133,7 @@ class Client:
|
||||
Args:
|
||||
package_base(str): package base to update
|
||||
"""
|
||||
return self.update(package_base, BuildStatusEnum.Pending)
|
||||
return self.package_update(package_base, BuildStatusEnum.Pending)
|
||||
|
||||
def set_success(self, package: Package) -> None:
|
||||
"""
|
||||
@ -159,7 +142,7 @@ class Client:
|
||||
Args:
|
||||
package(Package): current package properties
|
||||
"""
|
||||
return self.add(package, BuildStatusEnum.Success)
|
||||
return self.package_add(package, BuildStatusEnum.Success)
|
||||
|
||||
def set_unknown(self, package: Package) -> None:
|
||||
"""
|
||||
@ -168,4 +151,21 @@ class Client:
|
||||
Args:
|
||||
package(Package): current package properties
|
||||
"""
|
||||
return self.add(package, BuildStatusEnum.Unknown)
|
||||
return self.package_add(package, BuildStatusEnum.Unknown)
|
||||
|
||||
def status_get(self) -> InternalStatus:
|
||||
"""
|
||||
get internal service status
|
||||
|
||||
Returns:
|
||||
InternalStatus: current internal (web) service status
|
||||
"""
|
||||
return InternalStatus(status=BuildStatus())
|
||||
|
||||
def status_update(self, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
update ahriman status itself
|
||||
|
||||
Args:
|
||||
status(BuildStatusEnum): current ahriman status
|
||||
"""
|
||||
|
@ -22,6 +22,7 @@ import logging
|
||||
import requests
|
||||
|
||||
from collections.abc import Generator
|
||||
from typing import Any, Literal
|
||||
from urllib.parse import quote_plus as urlencode
|
||||
|
||||
from ahriman import __version__
|
||||
@ -164,10 +165,7 @@ class WebClient(Client, LazyLogging):
|
||||
"username": self.user.username,
|
||||
"password": self.user.password
|
||||
}
|
||||
|
||||
with self.__get_session(session):
|
||||
response = session.post(self._login_url, json=payload)
|
||||
response.raise_for_status()
|
||||
self.make_request("POST", self._login_url, json=payload, session=session)
|
||||
|
||||
def _logs_url(self, package_base: str) -> str:
|
||||
"""
|
||||
@ -195,7 +193,31 @@ class WebClient(Client, LazyLogging):
|
||||
suffix = f"/{package_base}" if package_base else ""
|
||||
return f"{self.address}/api/v1/packages{suffix}"
|
||||
|
||||
def add(self, package: Package, status: BuildStatusEnum) -> None:
|
||||
def make_request(self, method: Literal["DELETE", "GET", "POST"], url: str,
|
||||
params: list[tuple[str, str]] | None = None, json: dict[str, Any] | None = None,
|
||||
session: requests.Session | None = None) -> requests.Response | None:
|
||||
"""
|
||||
perform request with specified parameters
|
||||
|
||||
Args:
|
||||
method(Literal["DELETE", "GET", "POST"]): HTTP method to call
|
||||
url(str): remote url to call
|
||||
params(list[tuple[str, str]] | None, optional): request query parameters (Default value = None)
|
||||
json(dict[str, Any] | None, optional): request json parameters (Default value = None)
|
||||
session(requests.Session | None, optional): session object if any (Default value = None)
|
||||
|
||||
Returns:
|
||||
requests.Response | None: response object or None in case of errors
|
||||
"""
|
||||
with self.__get_session(session) as _session:
|
||||
response = _session.request(method, url, params=params, json=json)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
|
||||
# noinspection PyUnreachableCode
|
||||
return None
|
||||
|
||||
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
add new package with status
|
||||
|
||||
@ -207,12 +229,9 @@ class WebClient(Client, LazyLogging):
|
||||
"status": status.value,
|
||||
"package": package.view()
|
||||
}
|
||||
self.make_request("POST", self._package_url(package.base), json=payload)
|
||||
|
||||
with self.__get_session() as session:
|
||||
response = session.post(self._package_url(package.base), json=payload)
|
||||
response.raise_for_status()
|
||||
|
||||
def get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
|
||||
def package_get(self, package_base: str | None) -> list[tuple[Package, BuildStatus]]:
|
||||
"""
|
||||
get package status
|
||||
|
||||
@ -222,37 +241,17 @@ class WebClient(Client, LazyLogging):
|
||||
Returns:
|
||||
list[tuple[Package, BuildStatus]]: list of current package description and status if it has been found
|
||||
"""
|
||||
with self.__get_session() as session:
|
||||
response = session.get(self._package_url(package_base or ""))
|
||||
response.raise_for_status()
|
||||
response = self.make_request("GET", self._package_url(package_base or ""))
|
||||
if response is None:
|
||||
return []
|
||||
|
||||
status_json = response.json()
|
||||
return [
|
||||
(Package.from_json(package["package"]), BuildStatus.from_json(package["status"]))
|
||||
for package in status_json
|
||||
]
|
||||
response_json = response.json()
|
||||
return [
|
||||
(Package.from_json(package["package"]), BuildStatus.from_json(package["status"]))
|
||||
for package in response_json
|
||||
]
|
||||
|
||||
# noinspection PyUnreachableCode
|
||||
return []
|
||||
|
||||
def get_internal(self) -> InternalStatus:
|
||||
"""
|
||||
get internal service status
|
||||
|
||||
Returns:
|
||||
InternalStatus: current internal (web) service status
|
||||
"""
|
||||
with self.__get_session() as session:
|
||||
response = session.get(self._status_url)
|
||||
response.raise_for_status()
|
||||
|
||||
status_json = response.json()
|
||||
return InternalStatus.from_json(status_json)
|
||||
|
||||
# noinspection PyUnreachableCode
|
||||
return InternalStatus(status=BuildStatus())
|
||||
|
||||
def logs(self, package_base: str, record: logging.LogRecord) -> None:
|
||||
def package_logs(self, package_base: str, record: logging.LogRecord) -> None:
|
||||
"""
|
||||
post log record
|
||||
|
||||
@ -265,23 +264,18 @@ class WebClient(Client, LazyLogging):
|
||||
"message": record.getMessage(),
|
||||
"process_id": record.process,
|
||||
}
|
||||
self.make_request("POST", self._logs_url(package_base), json=payload)
|
||||
|
||||
# in this method exception has to be handled outside in logger handler
|
||||
response = self.__session.post(self._logs_url(package_base), json=payload)
|
||||
response.raise_for_status()
|
||||
|
||||
def remove(self, package_base: str) -> None:
|
||||
def package_remove(self, package_base: str) -> None:
|
||||
"""
|
||||
remove packages from watcher
|
||||
|
||||
Args:
|
||||
package_base(str): basename to remove
|
||||
"""
|
||||
with self.__get_session() as session:
|
||||
response = session.delete(self._package_url(package_base))
|
||||
response.raise_for_status()
|
||||
self.make_request("DELETE", self._package_url(package_base))
|
||||
|
||||
def update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||
def package_update(self, package_base: str, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
update package build status. Unlike ``add`` it does not update package properties
|
||||
|
||||
@ -290,12 +284,23 @@ class WebClient(Client, LazyLogging):
|
||||
status(BuildStatusEnum): current package build status
|
||||
"""
|
||||
payload = {"status": status.value}
|
||||
self.make_request("POST", self._package_url(package_base), json=payload)
|
||||
|
||||
with self.__get_session() as session:
|
||||
response = session.post(self._package_url(package_base), json=payload)
|
||||
response.raise_for_status()
|
||||
def status_get(self) -> InternalStatus:
|
||||
"""
|
||||
get internal service status
|
||||
|
||||
def update_self(self, status: BuildStatusEnum) -> None:
|
||||
Returns:
|
||||
InternalStatus: current internal (web) service status
|
||||
"""
|
||||
response = self.make_request("GET", self._status_url)
|
||||
if response is None:
|
||||
return InternalStatus(status=BuildStatus())
|
||||
|
||||
response_json = response.json()
|
||||
return InternalStatus.from_json(response_json)
|
||||
|
||||
def status_update(self, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
update ahriman status itself
|
||||
|
||||
@ -303,7 +308,4 @@ class WebClient(Client, LazyLogging):
|
||||
status(BuildStatusEnum): current ahriman status
|
||||
"""
|
||||
payload = {"status": status.value}
|
||||
|
||||
with self.__get_session() as session:
|
||||
response = session.post(self._status_url, json=payload)
|
||||
response.raise_for_status()
|
||||
self.make_request("POST", self._status_url, json=payload)
|
||||
|
@ -32,6 +32,7 @@ class ReportSettings(str, Enum):
|
||||
Email(ReportSettings): (class attribute) email report generation
|
||||
Console(ReportSettings): (class attribute) print result to console
|
||||
Telegram(ReportSettings): (class attribute) markdown report to telegram channel
|
||||
RemoteCall(ReportSettings): (class attribute) remote server call
|
||||
"""
|
||||
|
||||
Disabled = "disabled" # for testing purpose
|
||||
@ -39,6 +40,7 @@ class ReportSettings(str, Enum):
|
||||
Email = "email"
|
||||
Console = "console"
|
||||
Telegram = "telegram"
|
||||
RemoteCall = "remote-call"
|
||||
|
||||
@staticmethod
|
||||
def from_option(value: str) -> ReportSettings:
|
||||
@ -59,4 +61,6 @@ class ReportSettings(str, Enum):
|
||||
return ReportSettings.Console
|
||||
if value.lower() in ("telegram",):
|
||||
return ReportSettings.Telegram
|
||||
if value.lower() in ("remote-call",):
|
||||
return ReportSettings.RemoteCall
|
||||
return ReportSettings.Disabled
|
||||
|
72
src/ahriman/models/waiter.py
Normal file
72
src/ahriman/models/waiter.py
Normal file
@ -0,0 +1,72 @@
|
||||
#
|
||||
# Copyright (c) 2021-2023 ahriman team.
|
||||
#
|
||||
# This file is part of ahriman
|
||||
# (see https://github.com/arcan1s/ahriman).
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import time
|
||||
|
||||
from collections.abc import Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import ParamSpec
|
||||
|
||||
|
||||
Params = ParamSpec("Params")
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Waiter:
|
||||
"""
|
||||
simple waiter implementation
|
||||
|
||||
Attributes:
|
||||
interval(int): interval in seconds between checks
|
||||
start_time(float): monotonic time of the waiter start. More likely must not be assigned explicitly
|
||||
wait_timeout(int): timeout in seconds to wait for. Negative value will result in immediate exit. Zero value
|
||||
means infinite timeout
|
||||
"""
|
||||
|
||||
wait_timeout: int
|
||||
start_time: float = field(default_factory=time.monotonic, kw_only=True)
|
||||
interval: int = field(default=10, kw_only=True)
|
||||
|
||||
def is_timed_out(self) -> bool:
|
||||
"""
|
||||
check if timer is out
|
||||
|
||||
Returns:
|
||||
bool: True in case current monotonic time is more than ``Waiter.start_time`` and
|
||||
``Waiter.wait_timeout`` doesn't equal to 0
|
||||
"""
|
||||
since_start: float = time.monotonic() - self.start_time
|
||||
return self.wait_timeout != 0 and since_start > self.wait_timeout
|
||||
|
||||
def wait(self, in_progress: Callable[Params, bool], *args: Params.args, **kwargs: Params.kwargs) -> float:
|
||||
"""
|
||||
wait until requirements are not met
|
||||
|
||||
Args:
|
||||
in_progress(Callable[Params, bool]): function to check if timer should wait for another cycle
|
||||
*args(Params.args): positional arguments for check call
|
||||
**kwargs(Params.kwargs): keyword arguments for check call
|
||||
|
||||
Returns:
|
||||
float: consumed time in seconds
|
||||
"""
|
||||
while not self.is_timed_out() and in_progress(*args, **kwargs):
|
||||
time.sleep(self.interval)
|
||||
|
||||
return time.monotonic() - self.start_time
|
@ -36,8 +36,8 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
application_mock = mocker.patch("ahriman.core.status.client.Client.get_internal")
|
||||
packages_mock = mocker.patch("ahriman.core.status.client.Client.get",
|
||||
application_mock = mocker.patch("ahriman.core.status.client.Client.status_get")
|
||||
packages_mock = mocker.patch("ahriman.core.status.client.Client.package_get",
|
||||
return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)),
|
||||
(package_python_schedule, BuildStatus(BuildStatusEnum.Failed))])
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
@ -58,8 +58,8 @@ def test_run_empty_exception(args: argparse.Namespace, configuration: Configurat
|
||||
args = _default_args(args)
|
||||
args.exit_code = True
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.core.status.client.Client.get_internal")
|
||||
mocker.patch("ahriman.core.status.client.Client.get", return_value=[])
|
||||
mocker.patch("ahriman.core.status.client.Client.status_get")
|
||||
mocker.patch("ahriman.core.status.client.Client.package_get", return_value=[])
|
||||
check_mock = mocker.patch("ahriman.application.handlers.Handler.check_if_empty")
|
||||
|
||||
Status.run(args, "x86_64", configuration, report=False)
|
||||
@ -74,7 +74,7 @@ def test_run_verbose(args: argparse.Namespace, configuration: Configuration, rep
|
||||
args = _default_args(args)
|
||||
args.info = True
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
mocker.patch("ahriman.core.status.client.Client.get",
|
||||
mocker.patch("ahriman.core.status.client.Client.package_get",
|
||||
return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success))])
|
||||
print_mock = mocker.patch("ahriman.core.formatters.Printer.print")
|
||||
|
||||
@ -90,7 +90,7 @@ def test_run_with_package_filter(args: argparse.Namespace, configuration: Config
|
||||
args = _default_args(args)
|
||||
args.package = [package_ahriman.base]
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
packages_mock = mocker.patch("ahriman.core.status.client.Client.get",
|
||||
packages_mock = mocker.patch("ahriman.core.status.client.Client.package_get",
|
||||
return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success))])
|
||||
|
||||
Status.run(args, "x86_64", configuration, report=False)
|
||||
@ -104,7 +104,7 @@ def test_run_by_status(args: argparse.Namespace, configuration: Configuration, r
|
||||
"""
|
||||
args = _default_args(args)
|
||||
args.status = BuildStatusEnum.Failed
|
||||
mocker.patch("ahriman.core.status.client.Client.get",
|
||||
mocker.patch("ahriman.core.status.client.Client.package_get",
|
||||
return_value=[(package_ahriman, BuildStatus(BuildStatusEnum.Success)),
|
||||
(package_python_schedule, BuildStatus(BuildStatusEnum.Failed))])
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
|
@ -34,7 +34,7 @@ def test_run(args: argparse.Namespace, configuration: Configuration, repository:
|
||||
"""
|
||||
args = _default_args(args)
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
update_self_mock = mocker.patch("ahriman.core.status.client.Client.update_self")
|
||||
update_self_mock = mocker.patch("ahriman.core.status.client.Client.status_update")
|
||||
|
||||
StatusUpdate.run(args, "x86_64", configuration, report=False)
|
||||
update_self_mock.assert_called_once_with(args.status)
|
||||
@ -48,7 +48,7 @@ def test_run_packages(args: argparse.Namespace, configuration: Configuration, re
|
||||
args = _default_args(args)
|
||||
args.package = [package_ahriman.base]
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
update_mock = mocker.patch("ahriman.core.status.client.Client.update")
|
||||
update_mock = mocker.patch("ahriman.core.status.client.Client.package_update")
|
||||
|
||||
StatusUpdate.run(args, "x86_64", configuration, report=False)
|
||||
update_mock.assert_called_once_with(package_ahriman.base, args.status)
|
||||
@ -63,7 +63,7 @@ def test_run_remove(args: argparse.Namespace, configuration: Configuration, repo
|
||||
args.package = [package_ahriman.base]
|
||||
args.action = Action.Remove
|
||||
mocker.patch("ahriman.core.repository.Repository.load", return_value=repository)
|
||||
update_mock = mocker.patch("ahriman.core.status.client.Client.remove")
|
||||
update_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
|
||||
|
||||
StatusUpdate.run(args, "x86_64", configuration, report=False)
|
||||
update_mock.assert_called_once_with(package_ahriman.base)
|
||||
|
@ -67,6 +67,7 @@ def test_schema(configuration: Configuration) -> None:
|
||||
assert schema.pop("keyring-generator")
|
||||
assert schema.pop("mirrorlist")
|
||||
assert schema.pop("mirrorlist-generator")
|
||||
assert schema.pop("remote-call")
|
||||
assert schema.pop("remote-pull")
|
||||
assert schema.pop("remote-push")
|
||||
assert schema.pop("report")
|
||||
|
@ -32,7 +32,7 @@ def test_check_version(lock: Lock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must check version correctly
|
||||
"""
|
||||
mocker.patch("ahriman.core.status.client.Client.get_internal",
|
||||
mocker.patch("ahriman.core.status.client.Client.status_get",
|
||||
return_value=InternalStatus(status=BuildStatus(), version=__version__))
|
||||
logging_mock = mocker.patch("logging.Logger.warning")
|
||||
|
||||
@ -44,7 +44,7 @@ def test_check_version_mismatch(lock: Lock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must check mismatched version correctly
|
||||
"""
|
||||
mocker.patch("ahriman.core.status.client.Client.get_internal",
|
||||
mocker.patch("ahriman.core.status.client.Client.status_get",
|
||||
return_value=InternalStatus(status=BuildStatus(), version="version"))
|
||||
logging_mock = mocker.patch("logging.Logger.warning")
|
||||
|
||||
@ -156,30 +156,13 @@ def test_create_unsafe(lock: Lock) -> None:
|
||||
|
||||
def test_watch(lock: Lock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must check if lock file exists in cycle
|
||||
must check if lock file exists
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=False)
|
||||
lock.watch()
|
||||
|
||||
|
||||
def test_watch_wait(lock: Lock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must wait until file will disappear
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", side_effect=[True, False])
|
||||
wait_mock = mocker.patch("ahriman.models.waiter.Waiter.wait")
|
||||
lock.path = Path(tempfile.mktemp()) # nosec
|
||||
lock.wait_timeout = 1
|
||||
|
||||
lock.watch(1)
|
||||
|
||||
|
||||
def test_watch_empty_timeout(lock: Lock, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip watch on empty timeout
|
||||
"""
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
lock.path = Path(tempfile.mktemp()) # nosec
|
||||
lock.watch()
|
||||
wait_mock.assert_called_once_with(lock.path.is_file)
|
||||
|
||||
|
||||
def test_watch_skip(lock: Lock, mocker: MockerFixture) -> None:
|
||||
@ -199,7 +182,7 @@ def test_enter(lock: Lock, mocker: MockerFixture) -> None:
|
||||
watch_mock = mocker.patch("ahriman.application.lock.Lock.watch")
|
||||
clear_mock = mocker.patch("ahriman.application.lock.Lock.clear")
|
||||
create_mock = mocker.patch("ahriman.application.lock.Lock.create")
|
||||
update_status_mock = mocker.patch("ahriman.core.status.client.Client.update_self")
|
||||
update_status_mock = mocker.patch("ahriman.core.status.client.Client.status_update")
|
||||
|
||||
with lock:
|
||||
pass
|
||||
@ -218,7 +201,7 @@ def test_exit_with_exception(lock: Lock, mocker: MockerFixture) -> None:
|
||||
mocker.patch("ahriman.application.lock.Lock.check_user")
|
||||
mocker.patch("ahriman.application.lock.Lock.clear")
|
||||
mocker.patch("ahriman.application.lock.Lock.create")
|
||||
update_status_mock = mocker.patch("ahriman.core.status.client.Client.update_self")
|
||||
update_status_mock = mocker.patch("ahriman.core.status.client.Client.status_update")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
with lock:
|
||||
|
@ -40,48 +40,20 @@ def test_emit(configuration: Configuration, log_record: logging.LogRecord, packa
|
||||
must emit log record to reporter
|
||||
"""
|
||||
log_record.package_base = package_ahriman.base
|
||||
log_mock = mocker.patch("ahriman.core.status.client.Client.logs")
|
||||
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs")
|
||||
|
||||
handler = HttpLogHandler(configuration, report=False, suppress_errors=False)
|
||||
handler = HttpLogHandler(configuration, report=False)
|
||||
|
||||
handler.emit(log_record)
|
||||
log_mock.assert_called_once_with(package_ahriman.base, log_record)
|
||||
|
||||
|
||||
def test_emit_failed(configuration: Configuration, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call handle error on exception
|
||||
"""
|
||||
log_record.package_base = package_ahriman.base
|
||||
mocker.patch("ahriman.core.status.client.Client.logs", side_effect=Exception())
|
||||
handle_error_mock = mocker.patch("logging.Handler.handleError")
|
||||
handler = HttpLogHandler(configuration, report=False, suppress_errors=False)
|
||||
|
||||
handler.emit(log_record)
|
||||
handle_error_mock.assert_called_once_with(log_record)
|
||||
|
||||
|
||||
def test_emit_suppress_failed(configuration: Configuration, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must not call handle error on exception if suppress flag is set
|
||||
"""
|
||||
log_record.package_base = package_ahriman.base
|
||||
mocker.patch("ahriman.core.status.client.Client.logs", side_effect=Exception())
|
||||
handle_error_mock = mocker.patch("logging.Handler.handleError")
|
||||
handler = HttpLogHandler(configuration, report=False, suppress_errors=True)
|
||||
|
||||
handler.emit(log_record)
|
||||
handle_error_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_emit_skip(configuration: Configuration, log_record: logging.LogRecord, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip log record posting if no package base set
|
||||
"""
|
||||
log_mock = mocker.patch("ahriman.core.status.client.Client.logs")
|
||||
handler = HttpLogHandler(configuration, report=False, suppress_errors=False)
|
||||
log_mock = mocker.patch("ahriman.core.status.client.Client.package_logs")
|
||||
handler = HttpLogHandler(configuration, report=False)
|
||||
|
||||
handler.emit(log_record)
|
||||
log_mock.assert_not_called()
|
||||
|
20
tests/ahriman/core/report/conftest.py
Normal file
20
tests/ahriman/core/report/conftest.py
Normal file
@ -0,0 +1,20 @@
|
||||
import pytest
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.report.remote_call import RemoteCall
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def remote_call(configuration: Configuration) -> RemoteCall:
|
||||
"""
|
||||
fixture for remote update trigger
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration fixture
|
||||
|
||||
Returns:
|
||||
RemoteCall: remote update trigger test instance
|
||||
"""
|
||||
configuration.set_option("web", "host", "localhost")
|
||||
configuration.set_option("web", "port", "8080")
|
||||
return RemoteCall("x86_64", configuration, "remote-call")
|
85
tests/ahriman/core/report/test_remote_call.py
Normal file
85
tests/ahriman/core/report/test_remote_call.py
Normal file
@ -0,0 +1,85 @@
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.report.remote_call import RemoteCall
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
def test_generate(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly call client
|
||||
"""
|
||||
update_mock = mocker.patch("ahriman.core.report.remote_call.RemoteCall.remote_update", return_value="id")
|
||||
wait_mock = mocker.patch("ahriman.core.report.remote_call.RemoteCall.remote_wait")
|
||||
|
||||
remote_call.generate([], Result())
|
||||
update_mock.assert_called_once_with()
|
||||
wait_mock.assert_called_once_with("id")
|
||||
|
||||
|
||||
def test_is_process_alive(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly define if process is alive
|
||||
"""
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = """{"is_alive": true}""".encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
request_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
assert remote_call.is_process_alive("id")
|
||||
request_mock.assert_called_once_with("GET", f"{remote_call.client.address}/api/v1/service/process/id")
|
||||
|
||||
|
||||
def test_is_process_alive_unknown(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly define if process is unknown
|
||||
"""
|
||||
mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=None)
|
||||
assert not remote_call.is_process_alive("id")
|
||||
|
||||
|
||||
def test_remote_update(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call remote server for update process
|
||||
"""
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = """{"process_id": "id"}""".encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
request_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
assert remote_call.remote_update() == "id"
|
||||
request_mock.assert_called_once_with("POST", f"{remote_call.client.address}/api/v1/service/update", json={
|
||||
"aur": False,
|
||||
"local": False,
|
||||
"manual": True,
|
||||
})
|
||||
|
||||
|
||||
def test_remote_update_failed(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return empty process id in case of errors
|
||||
"""
|
||||
mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=None)
|
||||
assert remote_call.generate([], Result()) is None
|
||||
|
||||
|
||||
def test_remote_wait(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must wait for remote process to success
|
||||
"""
|
||||
wait_mock = mocker.patch("ahriman.models.waiter.Waiter.wait")
|
||||
remote_call.remote_wait("id")
|
||||
wait_mock.assert_called_once_with(pytest.helpers.anyvar(int), "id")
|
||||
|
||||
|
||||
def test_remote_wait_skip(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip wait if process id is unknown
|
||||
"""
|
||||
wait_mock = mocker.patch("ahriman.models.waiter.Waiter.wait")
|
||||
remote_call.remote_wait(None)
|
||||
wait_mock.assert_not_called()
|
@ -24,6 +24,7 @@ def test_report_dummy(configuration: Configuration, result: Result, mocker: Mock
|
||||
"""
|
||||
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, "disabled").run(result, [])
|
||||
report_mock.assert_called_once_with([], result)
|
||||
|
||||
@ -55,6 +56,18 @@ def test_report_html(configuration: Configuration, result: Result, mocker: Mocke
|
||||
report_mock.assert_called_once_with([], result)
|
||||
|
||||
|
||||
def test_report_remote_call(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must instantiate remote call trigger
|
||||
"""
|
||||
configuration.set_option("web", "host", "localhost")
|
||||
configuration.set_option("web", "port", "8080")
|
||||
report_mock = mocker.patch("ahriman.core.report.remote_call.RemoteCall.generate")
|
||||
|
||||
Report.load("x86_64", configuration, "remote-call").run(result, [])
|
||||
report_mock.assert_called_once_with([], result)
|
||||
|
||||
|
||||
def test_report_telegram(configuration: Configuration, result: Result, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must generate telegram report
|
||||
|
@ -85,7 +85,7 @@ def test_process_remove_base(executor: Executor, package_ahriman: Package, mocke
|
||||
build_queue_mock = mocker.patch("ahriman.core.database.SQLite.build_queue_clear")
|
||||
patches_mock = mocker.patch("ahriman.core.database.SQLite.patches_remove")
|
||||
logs_mock = mocker.patch("ahriman.core.database.SQLite.logs_remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
|
||||
|
||||
executor.process_remove([package_ahriman.base])
|
||||
# must remove via alpm wrapper
|
||||
@ -106,7 +106,7 @@ def test_process_remove_base_multiple(executor: Executor, package_python_schedul
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
|
||||
|
||||
executor.process_remove([package_python_schedule.base])
|
||||
# must remove via alpm wrapper
|
||||
@ -125,7 +125,7 @@ def test_process_remove_base_single(executor: Executor, package_python_schedule:
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[package_python_schedule])
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
|
||||
|
||||
executor.process_remove(["python2-schedule"])
|
||||
# must remove via alpm wrapper
|
||||
@ -171,7 +171,7 @@ def test_process_remove_unknown(executor: Executor, package_ahriman: Package, mo
|
||||
"""
|
||||
mocker.patch("ahriman.core.repository.executor.Executor.packages", return_value=[])
|
||||
repo_remove_mock = mocker.patch("ahriman.core.alpm.repo.Repo.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.remove")
|
||||
status_client_mock = mocker.patch("ahriman.core.status.client.Client.package_remove")
|
||||
|
||||
executor.process_remove([package_ahriman.base])
|
||||
repo_remove_mock.assert_not_called()
|
||||
|
@ -51,64 +51,47 @@ def test_load_full_client_from_unix_socket(configuration: Configuration) -> None
|
||||
assert isinstance(Client.load(configuration, report=True), WebClient)
|
||||
|
||||
|
||||
def test_add(client: Client, package_ahriman: Package) -> None:
|
||||
def test_package_add(client: Client, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must process package addition without errors
|
||||
"""
|
||||
client.add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
client.package_add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_get(client: Client, package_ahriman: Package) -> None:
|
||||
def test_package_get(client: Client, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must return empty package list
|
||||
"""
|
||||
assert client.get(package_ahriman.base) == []
|
||||
assert client.get(None) == []
|
||||
assert client.package_get(package_ahriman.base) == []
|
||||
assert client.package_get(None) == []
|
||||
|
||||
|
||||
def test_get_internal(client: Client) -> None:
|
||||
"""
|
||||
must return dummy status for web service
|
||||
"""
|
||||
actual = client.get_internal()
|
||||
expected = InternalStatus(status=BuildStatus(timestamp=actual.status.timestamp))
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_log(client: Client, package_ahriman: Package, log_record: logging.LogRecord) -> None:
|
||||
def test_package_log(client: Client, package_ahriman: Package, log_record: logging.LogRecord) -> None:
|
||||
"""
|
||||
must process log record without errors
|
||||
"""
|
||||
client.logs(package_ahriman.base, log_record)
|
||||
client.package_logs(package_ahriman.base, log_record)
|
||||
|
||||
|
||||
def test_remove(client: Client, package_ahriman: Package) -> None:
|
||||
def test_package_remove(client: Client, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must process remove without errors
|
||||
"""
|
||||
client.remove(package_ahriman.base)
|
||||
client.package_remove(package_ahriman.base)
|
||||
|
||||
|
||||
def test_update(client: Client, package_ahriman: Package) -> None:
|
||||
def test_package_update(client: Client, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must update package status without errors
|
||||
"""
|
||||
client.update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_update_self(client: Client) -> None:
|
||||
"""
|
||||
must update self status without errors
|
||||
"""
|
||||
client.update_self(BuildStatusEnum.Unknown)
|
||||
client.package_update(package_ahriman.base, 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.Client.update")
|
||||
update_mock = mocker.patch("ahriman.core.status.client.Client.package_update")
|
||||
client.set_building(package_ahriman.base)
|
||||
|
||||
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Building)
|
||||
@ -118,7 +101,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.Client.update")
|
||||
update_mock = mocker.patch("ahriman.core.status.client.Client.package_update")
|
||||
client.set_failed(package_ahriman.base)
|
||||
|
||||
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Failed)
|
||||
@ -128,7 +111,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.Client.update")
|
||||
update_mock = mocker.patch("ahriman.core.status.client.Client.package_update")
|
||||
client.set_pending(package_ahriman.base)
|
||||
|
||||
update_mock.assert_called_once_with(package_ahriman.base, BuildStatusEnum.Pending)
|
||||
@ -138,7 +121,7 @@ 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.Client.add")
|
||||
add_mock = mocker.patch("ahriman.core.status.client.Client.package_add")
|
||||
client.set_success(package_ahriman)
|
||||
|
||||
add_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Success)
|
||||
@ -148,7 +131,24 @@ def test_set_unknown(client: Client, package_ahriman: Package, mocker: MockerFix
|
||||
"""
|
||||
must add new package with unknown status
|
||||
"""
|
||||
add_mock = mocker.patch("ahriman.core.status.client.Client.add")
|
||||
add_mock = mocker.patch("ahriman.core.status.client.Client.package_add")
|
||||
client.set_unknown(package_ahriman)
|
||||
|
||||
add_mock.assert_called_once_with(package_ahriman, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_status_get(client: Client) -> None:
|
||||
"""
|
||||
must return dummy status for web service
|
||||
"""
|
||||
actual = client.status_get()
|
||||
expected = InternalStatus(status=BuildStatus(timestamp=actual.status.timestamp))
|
||||
|
||||
assert actual == expected
|
||||
|
||||
|
||||
def test_status_update(client: Client) -> None:
|
||||
"""
|
||||
must update self status without errors
|
||||
"""
|
||||
client.status_update(BuildStatusEnum.Unknown)
|
||||
|
@ -5,7 +5,7 @@ import requests
|
||||
import requests_unixsocket
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from requests import Response
|
||||
from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.status.web_client import WebClient
|
||||
@ -74,14 +74,14 @@ def test_login(web_client: WebClient, user: User, mocker: MockerFixture) -> None
|
||||
must login user
|
||||
"""
|
||||
web_client.user = user
|
||||
requests_mock = mocker.patch("requests.Session.post")
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
payload = {
|
||||
"username": user.username,
|
||||
"password": user.password
|
||||
}
|
||||
|
||||
web_client._login(requests.Session())
|
||||
requests_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), json=payload)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json=payload)
|
||||
|
||||
|
||||
def test_login_failed(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
|
||||
@ -89,7 +89,7 @@ def test_login_failed(web_client: WebClient, user: User, mocker: MockerFixture)
|
||||
must suppress any exception happened during login
|
||||
"""
|
||||
web_client.user = user
|
||||
mocker.patch("requests.Session.post", side_effect=Exception())
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client._login(requests.Session())
|
||||
|
||||
|
||||
@ -98,7 +98,7 @@ def test_login_failed_http_error(web_client: WebClient, user: User, mocker: Mock
|
||||
must suppress HTTP exception happened during login
|
||||
"""
|
||||
web_client.user = user
|
||||
mocker.patch("requests.Session.post", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
web_client._login(requests.Session())
|
||||
|
||||
|
||||
@ -106,7 +106,7 @@ def test_login_skip(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip login if no user set
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.post")
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
web_client._login(requests.Session())
|
||||
requests_mock.assert_not_called()
|
||||
|
||||
@ -130,241 +130,292 @@ def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
assert web_client._package_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}")
|
||||
|
||||
|
||||
def test_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_make_request(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must make HTTP request
|
||||
"""
|
||||
request_mock = mocker.patch("requests.Session.request")
|
||||
|
||||
assert web_client.make_request("GET", "url") is not None
|
||||
assert web_client.make_request("GET", "url", params=[("param", "value")]) is not None
|
||||
|
||||
assert web_client.make_request("POST", "url") is not None
|
||||
assert web_client.make_request("POST", "url", json={"param": "value"}) is not None
|
||||
|
||||
assert web_client.make_request("DELETE", "url") is not None
|
||||
|
||||
request_mock.assert_has_calls([
|
||||
MockCall("GET", "url", params=None, json=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("GET", "url", params=[("param", "value")], json=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", "url", params=None, json=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", "url", params=None, json={"param": "value"}),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("DELETE", "url", params=None, json=None),
|
||||
MockCall().raise_for_status(),
|
||||
])
|
||||
|
||||
|
||||
def test_make_request_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must make HTTP request
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
assert web_client.make_request("GET", "url") is None
|
||||
|
||||
|
||||
def test_package_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process package addition
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.post")
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
payload = pytest.helpers.get_package_status(package_ahriman)
|
||||
|
||||
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), json=payload)
|
||||
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json=payload)
|
||||
|
||||
|
||||
def test_add_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
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.post", side_effect=Exception())
|
||||
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_add_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
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.post", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_add_failed_suppress(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
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.post", side_effect=Exception())
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_add_failed_http_error_suppress(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
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.post", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_get_all(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_package_get_all(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return all packages status
|
||||
"""
|
||||
response = [pytest.helpers.get_package_status_extended(package_ahriman)]
|
||||
response_obj = Response()
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = json.dumps(response).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("requests.Session.get", return_value=response_obj)
|
||||
requests_mock = mocker.patch("requests.Session.request", return_value=response_obj)
|
||||
|
||||
result = web_client.get(None)
|
||||
requests_mock.assert_called_once_with(web_client._package_url())
|
||||
result = web_client.package_get(None)
|
||||
requests_mock.assert_called_once_with("GET", web_client._package_url(), params=None, json=None)
|
||||
assert len(result) == len(response)
|
||||
assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
|
||||
|
||||
|
||||
def test_get_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
def test_package_get_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during status getting
|
||||
"""
|
||||
mocker.patch("requests.Session.get", side_effect=Exception())
|
||||
assert web_client.get(None) == []
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
assert web_client.package_get(None) == []
|
||||
|
||||
|
||||
def test_get_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
def test_package_get_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during status getting
|
||||
"""
|
||||
mocker.patch("requests.Session.get", side_effect=requests.exceptions.HTTPError())
|
||||
assert web_client.get(None) == []
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
assert web_client.package_get(None) == []
|
||||
|
||||
|
||||
def test_get_single(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_package_get_single(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return single package status
|
||||
"""
|
||||
response = [pytest.helpers.get_package_status_extended(package_ahriman)]
|
||||
response_obj = Response()
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = json.dumps(response).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("requests.Session.get", return_value=response_obj)
|
||||
requests_mock = mocker.patch("requests.Session.request", return_value=response_obj)
|
||||
|
||||
result = web_client.get(package_ahriman.base)
|
||||
requests_mock.assert_called_once_with(web_client._package_url(package_ahriman.base))
|
||||
result = web_client.package_get(package_ahriman.base)
|
||||
requests_mock.assert_called_once_with("GET", web_client._package_url(package_ahriman.base),
|
||||
params=None, json=None)
|
||||
assert len(result) == len(response)
|
||||
assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
|
||||
|
||||
|
||||
def test_get_internal(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return web service status
|
||||
"""
|
||||
status = InternalStatus(status=BuildStatus(), architecture="x86_64")
|
||||
response_obj = Response()
|
||||
response_obj._content = json.dumps(status.view()).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("requests.Session.get", return_value=response_obj)
|
||||
|
||||
result = web_client.get_internal()
|
||||
requests_mock.assert_called_once_with(web_client._status_url)
|
||||
assert result.architecture == "x86_64"
|
||||
|
||||
|
||||
def test_get_internal_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during web service status getting
|
||||
"""
|
||||
mocker.patch("requests.Session.get", side_effect=Exception())
|
||||
assert web_client.get_internal().architecture is None
|
||||
|
||||
|
||||
def test_get_internal_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during web service status getting
|
||||
"""
|
||||
mocker.patch("requests.Session.get", side_effect=requests.exceptions.HTTPError())
|
||||
assert web_client.get_internal().architecture is None
|
||||
|
||||
|
||||
def test_logs(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
def test_package_logs(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process log record
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.post")
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
payload = {
|
||||
"created": log_record.created,
|
||||
"message": log_record.getMessage(),
|
||||
"process_id": log_record.process,
|
||||
}
|
||||
|
||||
web_client.logs(package_ahriman.base, log_record)
|
||||
requests_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), json=payload)
|
||||
web_client.package_logs(package_ahriman.base, log_record)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json=payload)
|
||||
|
||||
|
||||
def test_log_failed(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
def test_package_logs_failed(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must pass exception during log post
|
||||
"""
|
||||
mocker.patch("requests.Session.post", side_effect=Exception())
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
log_record.package_base = package_ahriman.base
|
||||
with pytest.raises(Exception):
|
||||
web_client.logs(package_ahriman.base, log_record)
|
||||
web_client.package_logs(package_ahriman.base, log_record)
|
||||
|
||||
|
||||
def test_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_package_logs_failed_http_error(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must pass exception during log post
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
log_record.package_base = package_ahriman.base
|
||||
web_client.package_logs(package_ahriman.base, log_record)
|
||||
|
||||
|
||||
def test_package_remove(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process package removal
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.delete")
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
|
||||
web_client.remove(package_ahriman.base)
|
||||
requests_mock.assert_called_once_with(pytest.helpers.anyvar(str, True))
|
||||
web_client.package_remove(package_ahriman.base)
|
||||
requests_mock.assert_called_once_with("DELETE", pytest.helpers.anyvar(str, True), params=None, json=None)
|
||||
|
||||
|
||||
def test_remove_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_package_remove_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during removal
|
||||
"""
|
||||
mocker.patch("requests.Session.delete", side_effect=Exception())
|
||||
web_client.remove(package_ahriman.base)
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client.package_remove(package_ahriman.base)
|
||||
|
||||
|
||||
def test_remove_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_package_remove_failed_http_error(web_client: WebClient, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during removal
|
||||
"""
|
||||
mocker.patch("requests.Session.delete", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.remove(package_ahriman.base)
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.package_remove(package_ahriman.base)
|
||||
|
||||
|
||||
def test_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_package_update(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process package update
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.post")
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
|
||||
web_client.update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), json={
|
||||
"status": BuildStatusEnum.Unknown.value})
|
||||
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json={
|
||||
"status": BuildStatusEnum.Unknown.value
|
||||
})
|
||||
|
||||
|
||||
def test_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_package_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during update
|
||||
"""
|
||||
mocker.patch("requests.Session.post", side_effect=Exception())
|
||||
web_client.update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_update_failed_http_error(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
def test_package_update_failed_http_error(web_client: WebClient, package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during update
|
||||
"""
|
||||
mocker.patch("requests.Session.post", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_update_self(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
def test_status_get(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return web service status
|
||||
"""
|
||||
status = InternalStatus(status=BuildStatus(), architecture="x86_64")
|
||||
response_obj = requests.Response()
|
||||
response_obj._content = json.dumps(status.view()).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("requests.Session.request", return_value=response_obj)
|
||||
|
||||
result = web_client.status_get()
|
||||
requests_mock.assert_called_once_with("GET", web_client._status_url, params=None, json=None)
|
||||
assert result.architecture == "x86_64"
|
||||
|
||||
|
||||
def test_status_get_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during web service status getting
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
assert web_client.status_get().architecture is None
|
||||
|
||||
|
||||
def test_status_get_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during web service status getting
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
assert web_client.status_get().architecture is None
|
||||
|
||||
|
||||
def test_status_update(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process service update
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.post")
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
|
||||
web_client.update_self(BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with(pytest.helpers.anyvar(str, True), json={
|
||||
"status": BuildStatusEnum.Unknown.value})
|
||||
web_client.status_update(BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json={
|
||||
"status": BuildStatusEnum.Unknown.value
|
||||
})
|
||||
|
||||
|
||||
def test_update_self_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
def test_status_update_self_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress any exception happened during service update
|
||||
"""
|
||||
mocker.patch("requests.Session.post", side_effect=Exception())
|
||||
web_client.update_self(BuildStatusEnum.Unknown)
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
web_client.status_update(BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
def test_update_self_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
def test_status_update_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress HTTP exception happened during service update
|
||||
"""
|
||||
mocker.patch("requests.Session.post", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.update_self(BuildStatusEnum.Unknown)
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
web_client.status_update(BuildStatusEnum.Unknown)
|
||||
|
@ -23,3 +23,6 @@ def test_from_option_valid() -> None:
|
||||
|
||||
assert ReportSettings.from_option("telegram") == ReportSettings.Telegram
|
||||
assert ReportSettings.from_option("TElegraM") == ReportSettings.Telegram
|
||||
|
||||
assert ReportSettings.from_option("remote-call") == ReportSettings.RemoteCall
|
||||
assert ReportSettings.from_option("reMOte-cALL") == ReportSettings.RemoteCall
|
||||
|
29
tests/ahriman/models/test_waiter.py
Normal file
29
tests/ahriman/models/test_waiter.py
Normal file
@ -0,0 +1,29 @@
|
||||
import time
|
||||
|
||||
from ahriman.models.waiter import Waiter
|
||||
|
||||
|
||||
def test_is_timed_out() -> None:
|
||||
"""
|
||||
must correctly check if timer runs out
|
||||
"""
|
||||
assert Waiter(-1).is_timed_out()
|
||||
assert Waiter(1, start_time=time.monotonic() - 10.0).is_timed_out()
|
||||
assert not Waiter(1, start_time=time.monotonic() + 10.0).is_timed_out()
|
||||
|
||||
|
||||
def test_is_timed_out_infinite() -> None:
|
||||
"""
|
||||
must treat 0 wait timeout as infinite
|
||||
"""
|
||||
assert not Waiter(0).is_timed_out()
|
||||
assert not Waiter(0, start_time=time.monotonic() - 10.0).is_timed_out()
|
||||
|
||||
|
||||
def test_wait() -> None:
|
||||
"""
|
||||
must wait until file will disappear
|
||||
"""
|
||||
results = iter([True, False])
|
||||
waiter = Waiter(1, interval=1)
|
||||
assert waiter.wait(lambda: next(results)) > 0
|
@ -74,6 +74,9 @@ homepage =
|
||||
link_path =
|
||||
template_path = ../web/templates/repo-index.jinja2
|
||||
|
||||
[remote-call]
|
||||
manual = yes
|
||||
|
||||
[telegram]
|
||||
api_key = apikey
|
||||
chat_id = @ahrimantestchat
|
||||
|
Loading…
Reference in New Issue
Block a user