mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
use http client class for all http requests
This commit is contained in:
parent
6dfe1b92f2
commit
33e68a59e2
@ -114,6 +114,7 @@ Web server settings. If any of ``host``/``port`` is not set, web integration wil
|
||||
* ``port`` - port to bind, int, optional.
|
||||
* ``static_path`` - path to directory with static files, string, required.
|
||||
* ``templates`` - path to templates directory, string, required.
|
||||
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
||||
* ``unix_socket`` - path to the listening unix socket, string, optional. If set, server will create the socket on the specified address which can (and will) be used by application. Note, that unlike usual host/port configuration, unix socket allows to perform requests without authorization.
|
||||
* ``unix_socket_unsafe`` - set unsafe (o+w) permissions to unix socket, boolean, optional, default ``yes``. This option is enabled by default, because it is supposed that unix socket is created in safe environment (only web service is supposed to be used in unsafe), but it can be disabled by configuration.
|
||||
* ``username`` - username to authorize in web service in order to update service status, string, required in case if authorization enabled.
|
||||
@ -311,6 +312,7 @@ This feature requires Github key creation (see below). Section name must be eith
|
||||
Section name must be either ``remote-service`` (plus optional architecture name, e.g. ``remote-service:x86_64``) or random name with ``type`` set.
|
||||
|
||||
* ``type`` - type of the report, string, optional, must be set to ``remote-service`` if exists.
|
||||
* ``timeout`` - HTTP request timeout in seconds, int, optional, default is ``30``.
|
||||
|
||||
``rsync`` type
|
||||
^^^^^^^^^^^^^^
|
||||
|
@ -17,14 +17,11 @@
|
||||
# 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 requests
|
||||
|
||||
from typing import Any
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.alpm.remote import Remote
|
||||
from ahriman.core.exceptions import PackageInfoError, UnknownPackageError
|
||||
from ahriman.core.util import exception_response_text
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
@ -36,13 +33,11 @@ class AUR(Remote):
|
||||
DEFAULT_AUR_URL(str): (class attribute) default AUR url
|
||||
DEFAULT_RPC_URL(str): (class attribute) default AUR RPC url
|
||||
DEFAULT_RPC_VERSION(str): (class attribute) default AUR RPC version
|
||||
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
|
||||
"""
|
||||
|
||||
DEFAULT_AUR_URL = "https://aur.archlinux.org"
|
||||
DEFAULT_RPC_URL = f"{DEFAULT_AUR_URL}/rpc"
|
||||
DEFAULT_RPC_VERSION = "5"
|
||||
DEFAULT_TIMEOUT = 30
|
||||
|
||||
@classmethod
|
||||
def remote_git_url(cls, package_base: str, repository: str) -> str:
|
||||
@ -91,7 +86,7 @@ class AUR(Remote):
|
||||
raise PackageInfoError(error_details)
|
||||
return [AURPackage.from_json(package) for package in response["results"]]
|
||||
|
||||
def make_request(self, request_type: str, *args: str, **kwargs: str) -> list[AURPackage]:
|
||||
def aur_request(self, request_type: str, *args: str, **kwargs: str) -> list[AURPackage]:
|
||||
"""
|
||||
perform request to AUR RPC
|
||||
|
||||
@ -103,34 +98,20 @@ class AUR(Remote):
|
||||
Returns:
|
||||
list[AURPackage]: response parsed to package list
|
||||
"""
|
||||
query: dict[str, Any] = {
|
||||
"type": request_type,
|
||||
"v": self.DEFAULT_RPC_VERSION
|
||||
}
|
||||
query: list[tuple[str, str]] = [
|
||||
("type", request_type),
|
||||
("v", self.DEFAULT_RPC_VERSION),
|
||||
]
|
||||
|
||||
arg_query = "arg[]" if len(args) > 1 else "arg"
|
||||
query[arg_query] = list(args)
|
||||
for arg in args:
|
||||
query.append((arg_query, arg))
|
||||
|
||||
for key, value in kwargs.items():
|
||||
query[key] = value
|
||||
query.append((key, value))
|
||||
|
||||
try:
|
||||
response = requests.get(
|
||||
self.DEFAULT_RPC_URL,
|
||||
params=query,
|
||||
headers={"User-Agent": self.DEFAULT_USER_AGENT},
|
||||
timeout=self.DEFAULT_TIMEOUT)
|
||||
response.raise_for_status()
|
||||
return self.parse_response(response.json())
|
||||
except requests.HTTPError as e:
|
||||
self.logger.exception(
|
||||
"could not perform request by using type %s: %s",
|
||||
request_type,
|
||||
exception_response_text(e))
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.exception("could not perform request by using type %s", request_type)
|
||||
raise
|
||||
response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query)
|
||||
return self.parse_response(response.json())
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
"""
|
||||
@ -146,7 +127,7 @@ class AUR(Remote):
|
||||
Raises:
|
||||
UnknownPackageError: package doesn't exist
|
||||
"""
|
||||
packages = self.make_request("info", package_name)
|
||||
packages = self.aur_request("info", package_name)
|
||||
try:
|
||||
return next(package for package in packages if package.name == package_name)
|
||||
except StopIteration:
|
||||
@ -163,4 +144,4 @@ class AUR(Remote):
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
"""
|
||||
return self.make_request("search", *keywords, by="name-desc")
|
||||
return self.aur_request("search", *keywords, by="name-desc")
|
||||
|
@ -17,14 +17,11 @@
|
||||
# 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 requests
|
||||
|
||||
from typing import Any
|
||||
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.alpm.remote import Remote
|
||||
from ahriman.core.exceptions import PackageInfoError, UnknownPackageError
|
||||
from ahriman.core.util import exception_response_text
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
@ -37,14 +34,12 @@ class Official(Remote):
|
||||
DEFAULT_ARCHLINUX_GIT_URL(str): (class attribute) default url for git packages
|
||||
DEFAULT_SEARCH_REPOSITORIES(list[str]): (class attribute) default list of repositories to search
|
||||
DEFAULT_RPC_URL(str): (class attribute) default archlinux repositories RPC url
|
||||
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
|
||||
"""
|
||||
|
||||
DEFAULT_ARCHLINUX_GIT_URL = "https://gitlab.archlinux.org"
|
||||
DEFAULT_ARCHLINUX_URL = "https://archlinux.org"
|
||||
DEFAULT_SEARCH_REPOSITORIES = ["Core", "Extra", "Multilib"]
|
||||
DEFAULT_RPC_URL = "https://archlinux.org/packages/search/json"
|
||||
DEFAULT_TIMEOUT = 30
|
||||
|
||||
@classmethod
|
||||
def remote_git_url(cls, package_base: str, repository: str) -> str:
|
||||
@ -91,7 +86,7 @@ class Official(Remote):
|
||||
raise PackageInfoError("API validation error")
|
||||
return [AURPackage.from_repo(package) for package in response["results"]]
|
||||
|
||||
def make_request(self, *args: str, by: str) -> list[AURPackage]:
|
||||
def arch_request(self, *args: str, by: str) -> list[AURPackage]:
|
||||
"""
|
||||
perform request to official repositories RPC
|
||||
|
||||
@ -102,20 +97,15 @@ class Official(Remote):
|
||||
Returns:
|
||||
list[AURPackage]: response parsed to package list
|
||||
"""
|
||||
try:
|
||||
response = requests.get(
|
||||
self.DEFAULT_RPC_URL,
|
||||
params={by: args, "repo": self.DEFAULT_SEARCH_REPOSITORIES},
|
||||
headers={"User-Agent": self.DEFAULT_USER_AGENT},
|
||||
timeout=self.DEFAULT_TIMEOUT)
|
||||
response.raise_for_status()
|
||||
return self.parse_response(response.json())
|
||||
except requests.HTTPError as e:
|
||||
self.logger.exception("could not perform request: %s", exception_response_text(e))
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.exception("could not perform request")
|
||||
raise
|
||||
query: list[tuple[str, str]] = [
|
||||
("repo", repository)
|
||||
for repository in self.DEFAULT_SEARCH_REPOSITORIES
|
||||
]
|
||||
for arg in args:
|
||||
query.append((by, arg))
|
||||
|
||||
response = self.make_request("GET", self.DEFAULT_RPC_URL, params=query)
|
||||
return self.parse_response(response.json())
|
||||
|
||||
def package_info(self, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
"""
|
||||
@ -131,7 +121,7 @@ class Official(Remote):
|
||||
Raises:
|
||||
UnknownPackageError: package doesn't exist
|
||||
"""
|
||||
packages = self.make_request(package_name, by="name")
|
||||
packages = self.arch_request(package_name, by="name")
|
||||
try:
|
||||
return next(package for package in packages if package.name == package_name)
|
||||
except StopIteration:
|
||||
@ -148,4 +138,4 @@ class Official(Remote):
|
||||
Returns:
|
||||
list[AURPackage]: list of packages which match the criteria
|
||||
"""
|
||||
return self.make_request(*keywords, by="q")
|
||||
return self.arch_request(*keywords, by="q")
|
||||
|
@ -17,19 +17,15 @@
|
||||
# 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 import __version__
|
||||
from ahriman.core.alpm.pacman import Pacman
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.http import SyncHttpClient
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
|
||||
|
||||
class Remote(LazyLogging):
|
||||
class Remote(SyncHttpClient):
|
||||
"""
|
||||
base class for remote package search
|
||||
|
||||
Attributes:
|
||||
DEFAULT_USER_AGENT(str): (class attribute) default user agent
|
||||
|
||||
Examples:
|
||||
These classes are designed to be used without instancing. In order to achieve it several class methods are
|
||||
provided: ``info``, ``multisearch`` and ``search``. Thus, the basic flow is the following::
|
||||
@ -43,8 +39,6 @@ class Remote(LazyLogging):
|
||||
directly, whereas ``multisearch`` splits search one by one and finds intersection between search results.
|
||||
"""
|
||||
|
||||
DEFAULT_USER_AGENT = f"ahriman/{__version__}"
|
||||
|
||||
@classmethod
|
||||
def info(cls, package_name: str, *, pacman: Pacman) -> AURPackage:
|
||||
"""
|
||||
|
20
src/ahriman/core/http/__init__.py
Normal file
20
src/ahriman/core/http/__init__.py
Normal file
@ -0,0 +1,20 @@
|
||||
#
|
||||
# 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.http.sync_http_client import MultipartType, SyncHttpClient
|
137
src/ahriman/core/http/sync_http_client.py
Normal file
137
src/ahriman/core/http/sync_http_client.py
Normal file
@ -0,0 +1,137 @@
|
||||
#
|
||||
# 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 requests
|
||||
|
||||
from functools import cached_property
|
||||
from typing import Any, IO, Literal
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log import LazyLogging
|
||||
|
||||
|
||||
# filename, file, content-type, headers
|
||||
MultipartType = tuple[str, IO[bytes], str, dict[str, str]]
|
||||
|
||||
|
||||
class SyncHttpClient(LazyLogging):
|
||||
"""
|
||||
wrapper around requests library to reduce boilerplate
|
||||
|
||||
Attributes:
|
||||
auth(tuple[str, str] | None): HTTP basic auth object if set
|
||||
suppress_errors(bool): suppress logging of request errors
|
||||
timeout(int): HTTP request timeout in seconds
|
||||
"""
|
||||
|
||||
def __init__(self, section: str | None = None, configuration: Configuration | None = None, *,
|
||||
suppress_errors: bool = False) -> None:
|
||||
"""
|
||||
default constructor
|
||||
|
||||
Args:
|
||||
section(str, optional): settings section name (Default value = None)
|
||||
configuration(Configuration | None): configuration instance (Default value = None)
|
||||
suppress_errors(bool, optional): suppress logging of request errors (Default value = False)
|
||||
"""
|
||||
if configuration is None:
|
||||
configuration = Configuration() # dummy configuration
|
||||
if section is None:
|
||||
section = configuration.default_section
|
||||
|
||||
username = configuration.get(section, "username", fallback=None)
|
||||
password = configuration.get(section, "password", fallback=None)
|
||||
self.auth = (username, password) if username and password else None
|
||||
|
||||
self.timeout = configuration.getint(section, "timeout", fallback=30)
|
||||
self.suppress_errors = suppress_errors
|
||||
|
||||
@cached_property
|
||||
def session(self) -> requests.Session:
|
||||
"""
|
||||
get or create session
|
||||
|
||||
Returns:
|
||||
request.Session: created session object
|
||||
"""
|
||||
session = requests.Session()
|
||||
session.headers["User-Agent"] = f"ahriman/{__version__}"
|
||||
|
||||
return session
|
||||
|
||||
@staticmethod
|
||||
def exception_response_text(exception: requests.exceptions.RequestException) -> str:
|
||||
"""
|
||||
safe response exception text generation
|
||||
|
||||
Args:
|
||||
exception(requests.exceptions.RequestException): exception raised
|
||||
|
||||
Returns:
|
||||
str: text of the response if it is not None and empty string otherwise
|
||||
"""
|
||||
result: str = exception.response.text if exception.response is not None else ""
|
||||
return result
|
||||
|
||||
def make_request(self, method: Literal["DELETE", "GET", "POST", "PUT"], url: str, *,
|
||||
headers: dict[str, str] | None = None,
|
||||
params: list[tuple[str, str]] | None = None,
|
||||
data: Any | None = None,
|
||||
json: dict[str, Any] | None = None,
|
||||
files: dict[str, MultipartType] | None = None,
|
||||
session: requests.Session | None = None,
|
||||
suppress_errors: bool | None = None) -> requests.Response:
|
||||
"""
|
||||
perform request with specified parameters
|
||||
|
||||
Args:
|
||||
method(Literal["DELETE", "GET", "POST", "PUT"]): HTTP method to call
|
||||
url(str): remote url to call
|
||||
headers(dict[str, str] | None, optional): request headers (Default value = None)
|
||||
params(list[tuple[str, str]] | None, optional): request query parameters (Default value = None)
|
||||
data(Any | None, optional): request raw data parameters (Default value = None)
|
||||
json(dict[str, Any] | None, optional): request json parameters (Default value = None)
|
||||
files(dict[str, MultipartType] | None, optional): multipart upload (Default value = None)
|
||||
session(requests.Session | None, optional): session object if any (Default value = None)
|
||||
suppress_errors(bool | None, optional): suppress logging errors (e.g. if no web server available). If none
|
||||
set, the instance-wide value will be used (Default value = None)
|
||||
|
||||
Returns:
|
||||
requests.Response: response object
|
||||
"""
|
||||
# defaults
|
||||
if suppress_errors is None:
|
||||
suppress_errors = self.suppress_errors
|
||||
if session is None:
|
||||
session = self.session
|
||||
|
||||
try:
|
||||
response = session.request(method, url, params=params, data=data, headers=headers, files=files, json=json,
|
||||
auth=self.auth, timeout=self.timeout)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except requests.HTTPError as ex:
|
||||
if not suppress_errors:
|
||||
self.logger.exception("could not perform http request: %s", self.exception_response_text(ex))
|
||||
raise
|
||||
except Exception:
|
||||
if not suppress_errors:
|
||||
self.logger.exception("could not perform http request")
|
||||
raise
|
@ -81,8 +81,9 @@ class RemoteCall(Report):
|
||||
"""
|
||||
try:
|
||||
response = self.client.make_request("GET", f"/api/v1/service/process/{process_id}")
|
||||
except requests.RequestException as e:
|
||||
if e.response is not None and e.response.status_code == 404:
|
||||
except requests.HTTPError as ex:
|
||||
status_code = ex.response.status_code if ex.response is not None else None
|
||||
if status_code == 404:
|
||||
return False
|
||||
raise
|
||||
|
||||
|
@ -17,17 +17,15 @@
|
||||
# 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 requests # technically we could use python-telegram-bot, but it is just a single request, c'mon
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.http import SyncHttpClient
|
||||
from ahriman.core.report.jinja_template import JinjaTemplate
|
||||
from ahriman.core.report.report import Report
|
||||
from ahriman.core.util import exception_response_text
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.result import Result
|
||||
|
||||
|
||||
class Telegram(Report, JinjaTemplate):
|
||||
class Telegram(Report, JinjaTemplate, SyncHttpClient):
|
||||
"""
|
||||
telegram report generator
|
||||
|
||||
@ -38,7 +36,6 @@ class Telegram(Report, JinjaTemplate):
|
||||
chat_id(str): chat id to post message, either string with @ or integer
|
||||
template_path(Path): path to template for built packages
|
||||
template_type(str): template message type to be used in parse mode, one of MarkdownV2, HTML, Markdown
|
||||
timeout(int): HTTP request timeout in seconds
|
||||
"""
|
||||
|
||||
TELEGRAM_API_URL = "https://api.telegram.org"
|
||||
@ -55,12 +52,12 @@ class Telegram(Report, JinjaTemplate):
|
||||
"""
|
||||
Report.__init__(self, architecture, configuration)
|
||||
JinjaTemplate.__init__(self, section, configuration)
|
||||
SyncHttpClient.__init__(self, section, configuration)
|
||||
|
||||
self.api_key = configuration.get(section, "api_key")
|
||||
self.chat_id = configuration.get(section, "chat_id")
|
||||
self.template_path = configuration.getpath(section, "template_path")
|
||||
self.template_type = configuration.get(section, "template_type", fallback="HTML")
|
||||
self.timeout = configuration.getint(section, "timeout", fallback=30)
|
||||
|
||||
def _send(self, text: str) -> None:
|
||||
"""
|
||||
@ -69,18 +66,8 @@ class Telegram(Report, JinjaTemplate):
|
||||
Args:
|
||||
text(str): message body text
|
||||
"""
|
||||
try:
|
||||
response = requests.post(
|
||||
f"{self.TELEGRAM_API_URL}/bot{self.api_key}/sendMessage",
|
||||
data={"chat_id": self.chat_id, "text": text, "parse_mode": self.template_type},
|
||||
timeout=self.timeout)
|
||||
response.raise_for_status()
|
||||
except requests.HTTPError as e:
|
||||
self.logger.exception("could not perform request: %s", exception_response_text(e))
|
||||
raise
|
||||
except Exception:
|
||||
self.logger.exception("could not perform request")
|
||||
raise
|
||||
self.make_request("POST", f"{self.TELEGRAM_API_URL}/bot{self.api_key}/sendMessage",
|
||||
data={"chat_id": self.chat_id, "text": text, "parse_mode": self.template_type})
|
||||
|
||||
def generate(self, packages: list[Package], result: Result) -> None:
|
||||
"""
|
||||
|
@ -17,30 +17,26 @@
|
||||
# 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 requests
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.exceptions import BuildError
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.util import check_output, exception_response_text
|
||||
from ahriman.core.http import SyncHttpClient
|
||||
from ahriman.core.util import check_output
|
||||
from ahriman.models.sign_settings import SignSettings
|
||||
|
||||
|
||||
class GPG(LazyLogging):
|
||||
class GPG(SyncHttpClient):
|
||||
"""
|
||||
gnupg wrapper
|
||||
|
||||
Attributes:
|
||||
DEFAULT_TIMEOUT(int): (class attribute) HTTP request timeout in seconds
|
||||
configuration(Configuration): configuration instance
|
||||
default_key(str | None): default PGP key ID to use
|
||||
targets(set[SignSettings]): list of targets to sign (repository, package etc)
|
||||
"""
|
||||
|
||||
_check_output = check_output
|
||||
DEFAULT_TIMEOUT = 30
|
||||
|
||||
def __init__(self, configuration: Configuration) -> None:
|
||||
"""
|
||||
@ -49,6 +45,7 @@ class GPG(LazyLogging):
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
SyncHttpClient.__init__(self)
|
||||
self.configuration = configuration
|
||||
self.targets, self.default_key = self.sign_options(configuration)
|
||||
|
||||
@ -126,16 +123,11 @@ class GPG(LazyLogging):
|
||||
str: key as plain text
|
||||
"""
|
||||
key = key if key.startswith("0x") else f"0x{key}"
|
||||
try:
|
||||
response = requests.get(f"https://{server}/pks/lookup", params={
|
||||
"op": "get",
|
||||
"options": "mr",
|
||||
"search": key
|
||||
}, timeout=self.DEFAULT_TIMEOUT)
|
||||
response.raise_for_status()
|
||||
except requests.exceptions.HTTPError as e:
|
||||
self.logger.exception("could not download key %s from %s: %s", key, server, exception_response_text(e))
|
||||
raise
|
||||
response = self.make_request("GET", f"https://{server}/pks/lookup", params=[
|
||||
("op", "get"),
|
||||
("options", "mr"),
|
||||
("search", key),
|
||||
])
|
||||
return response.text
|
||||
|
||||
def key_export(self, key: str) -> str:
|
||||
|
@ -22,39 +22,27 @@ import logging
|
||||
import requests
|
||||
|
||||
from functools import cached_property
|
||||
from typing import Any, IO, Literal
|
||||
from urllib.parse import quote_plus as urlencode
|
||||
|
||||
from ahriman import __version__
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.log import LazyLogging
|
||||
from ahriman.core.http import SyncHttpClient
|
||||
from ahriman.core.status.client import Client
|
||||
from ahriman.core.util import exception_response_text
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.log_record_id import LogRecordId
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user import User
|
||||
|
||||
|
||||
# filename, file, content-type, headers
|
||||
MultipartType = tuple[str, IO[bytes], str, dict[str, str]]
|
||||
|
||||
|
||||
class WebClient(Client, LazyLogging):
|
||||
class WebClient(Client, SyncHttpClient):
|
||||
"""
|
||||
build status reporter web client
|
||||
|
||||
Attributes:
|
||||
address(str): address of the web service
|
||||
suppress_errors(bool): suppress logging errors (e.g. if no web server available)
|
||||
user(User | None): web service user descriptor
|
||||
use_unix_socket(bool): use websocket or not
|
||||
"""
|
||||
|
||||
_login_url = "/api/v1/login"
|
||||
_status_url = "/api/v1/status"
|
||||
|
||||
def __init__(self, configuration: Configuration) -> None:
|
||||
"""
|
||||
default constructor
|
||||
@ -62,11 +50,10 @@ class WebClient(Client, LazyLogging):
|
||||
Args:
|
||||
configuration(Configuration): configuration instance
|
||||
"""
|
||||
suppress_errors = configuration.getboolean("settings", "suppress_http_log_errors", fallback=False)
|
||||
SyncHttpClient.__init__(self, "web", configuration, suppress_errors=suppress_errors)
|
||||
|
||||
self.address, self.use_unix_socket = self.parse_address(configuration)
|
||||
self.user = User.from_option(
|
||||
configuration.get("web", "username", fallback=None),
|
||||
configuration.get("web", "password", fallback=None))
|
||||
self.suppress_errors = configuration.getboolean("settings", "suppress_http_log_errors", fallback=False)
|
||||
|
||||
@cached_property
|
||||
def session(self) -> requests.Session:
|
||||
@ -78,34 +65,6 @@ class WebClient(Client, LazyLogging):
|
||||
"""
|
||||
return self._create_session(use_unix_socket=self.use_unix_socket)
|
||||
|
||||
@staticmethod
|
||||
def _logs_url(package_base: str) -> str:
|
||||
"""
|
||||
get url for the logs api
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
|
||||
Returns:
|
||||
str: full url for web service for logs
|
||||
"""
|
||||
return f"/api/v1/packages/{package_base}/logs"
|
||||
|
||||
@staticmethod
|
||||
def _package_url(package_base: str = "") -> str:
|
||||
"""
|
||||
url generator
|
||||
|
||||
Args:
|
||||
package_base(str, optional): package base to generate url (Default value = "")
|
||||
|
||||
Returns:
|
||||
str: full url of web service for specific package base
|
||||
"""
|
||||
# in case if unix socket is used we need to normalize url
|
||||
suffix = f"/{package_base}" if package_base else ""
|
||||
return f"/api/v1/packages{suffix}"
|
||||
|
||||
@staticmethod
|
||||
def parse_address(configuration: Configuration) -> tuple[str, bool]:
|
||||
"""
|
||||
@ -157,56 +116,60 @@ class WebClient(Client, LazyLogging):
|
||||
Args:
|
||||
session(requests.Session): request session to login
|
||||
"""
|
||||
if self.user is None:
|
||||
if self.auth is None:
|
||||
return # no auth configured
|
||||
|
||||
username, password = self.auth
|
||||
payload = {
|
||||
"username": self.user.username,
|
||||
"password": self.user.password
|
||||
"username": username,
|
||||
"password": password,
|
||||
}
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._login_url, json=payload, session=session)
|
||||
self.make_request("POST", self._login_url(), json=payload, session=session)
|
||||
|
||||
def make_request(self, method: Literal["DELETE", "GET", "POST"], url: str, *,
|
||||
params: list[tuple[str, str]] | None = None,
|
||||
json: dict[str, Any] | None = None,
|
||||
files: dict[str, MultipartType] | None = None,
|
||||
session: requests.Session | None = None,
|
||||
suppress_errors: bool | None = None) -> requests.Response:
|
||||
def _login_url(self) -> str:
|
||||
"""
|
||||
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)
|
||||
files(dict[str, MultipartType] | None, optional): multipart upload (Default value = None)
|
||||
session(requests.Session | None, optional): session object if any (Default value = None)
|
||||
suppress_errors(bool | None, optional): suppress logging errors (e.g. if no web server available). If none
|
||||
set, the instance-wide value will be used (Default value = None)
|
||||
get url for the login api
|
||||
|
||||
Returns:
|
||||
requests.Response: response object
|
||||
str: full url for web service to log in
|
||||
"""
|
||||
# defaults
|
||||
if suppress_errors is None:
|
||||
suppress_errors = self.suppress_errors
|
||||
if session is None:
|
||||
session = self.session
|
||||
return f"{self.address}/api/v1/login"
|
||||
|
||||
try:
|
||||
response = session.request(method, f"{self.address}{url}", params=params, json=json, files=files)
|
||||
response.raise_for_status()
|
||||
return response
|
||||
except requests.RequestException as e:
|
||||
if not suppress_errors:
|
||||
self.logger.exception("could not perform http request: %s", exception_response_text(e))
|
||||
raise
|
||||
except Exception:
|
||||
if not suppress_errors:
|
||||
self.logger.exception("could not perform http request")
|
||||
raise
|
||||
def _logs_url(self, package_base: str) -> str:
|
||||
"""
|
||||
get url for the logs api
|
||||
|
||||
Args:
|
||||
package_base(str): package base
|
||||
|
||||
Returns:
|
||||
str: full url for web service for logs
|
||||
"""
|
||||
return f"{self.address}/api/v1/packages/{package_base}/logs"
|
||||
|
||||
def _package_url(self, package_base: str = "") -> str:
|
||||
"""
|
||||
url generator
|
||||
|
||||
Args:
|
||||
package_base(str, optional): package base to generate url (Default value = "")
|
||||
|
||||
Returns:
|
||||
str: full url of web service for specific package base
|
||||
"""
|
||||
# in case if unix socket is used we need to normalize url
|
||||
suffix = f"/{package_base}" if package_base else ""
|
||||
return f"{self.address}/api/v1/packages{suffix}"
|
||||
|
||||
def _status_url(self) -> str:
|
||||
"""
|
||||
get url for the status api
|
||||
|
||||
Returns:
|
||||
str: full url for web service for status
|
||||
"""
|
||||
return f"{self.address}/api/v1/status"
|
||||
|
||||
def package_add(self, package: Package, status: BuildStatusEnum) -> None:
|
||||
"""
|
||||
@ -293,7 +256,7 @@ class WebClient(Client, LazyLogging):
|
||||
InternalStatus: current internal (web) service status
|
||||
"""
|
||||
with contextlib.suppress(Exception):
|
||||
response = self.make_request("GET", self._status_url)
|
||||
response = self.make_request("GET", self._status_url())
|
||||
response_json = response.json()
|
||||
|
||||
return InternalStatus.from_json(response_json)
|
||||
@ -309,4 +272,4 @@ class WebClient(Client, LazyLogging):
|
||||
"""
|
||||
payload = {"status": status.value}
|
||||
with contextlib.suppress(Exception):
|
||||
self.make_request("POST", self._status_url, json=payload)
|
||||
self.make_request("POST", self._status_url(), json=payload)
|
||||
|
@ -61,7 +61,7 @@ class Github(HttpUpload):
|
||||
"""
|
||||
try:
|
||||
asset = next(asset for asset in release["assets"] if asset["name"] == name)
|
||||
self._request("DELETE", asset["url"])
|
||||
self.make_request("DELETE", asset["url"])
|
||||
except StopIteration:
|
||||
self.logger.info("no asset %s found in release %s", name, release["name"])
|
||||
|
||||
@ -81,7 +81,7 @@ class Github(HttpUpload):
|
||||
headers = {"Content-Type": mime} if mime is not None else {"Content-Type": "application/octet-stream"}
|
||||
|
||||
with path.open("rb") as archive:
|
||||
self._request("POST", url, params={"name": path.name}, data=archive, headers=headers)
|
||||
self.make_request("POST", url, params=[("name", path.name)], data=archive, headers=headers)
|
||||
|
||||
def get_local_files(self, path: Path) -> dict[Path, str]:
|
||||
"""
|
||||
@ -136,7 +136,7 @@ class Github(HttpUpload):
|
||||
dict[str, Any]: github API release object for the new release
|
||||
"""
|
||||
url = f"https://api.github.com/repos/{self.github_owner}/{self.github_repository}/releases"
|
||||
response = self._request("POST", url, json={"tag_name": self.architecture, "name": self.architecture})
|
||||
response = self.make_request("POST", url, json={"tag_name": self.architecture, "name": self.architecture})
|
||||
release: dict[str, Any] = response.json()
|
||||
return release
|
||||
|
||||
@ -149,11 +149,11 @@ class Github(HttpUpload):
|
||||
"""
|
||||
url = f"https://api.github.com/repos/{self.github_owner}/{self.github_repository}/releases/tags/{self.architecture}"
|
||||
try:
|
||||
response = self._request("GET", url)
|
||||
response = self.make_request("GET", url)
|
||||
release: dict[str, Any] = response.json()
|
||||
return release
|
||||
except requests.HTTPError as e:
|
||||
status_code = e.response.status_code if e.response is not None else None
|
||||
except requests.HTTPError as ex:
|
||||
status_code = ex.response.status_code if ex.response is not None else None
|
||||
if status_code == 404:
|
||||
return None
|
||||
raise
|
||||
@ -166,7 +166,7 @@ class Github(HttpUpload):
|
||||
release(dict[str, Any]): release object
|
||||
body(str): new release body
|
||||
"""
|
||||
self._request("POST", release["url"], json={"body": body})
|
||||
self.make_request("POST", release["url"], json={"body": body})
|
||||
|
||||
def sync(self, path: Path, built_packages: list[Package]) -> None:
|
||||
"""
|
||||
|
@ -18,24 +18,17 @@
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
import hashlib
|
||||
import requests
|
||||
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.http import SyncHttpClient
|
||||
from ahriman.core.upload.upload import Upload
|
||||
from ahriman.core.util import exception_response_text
|
||||
|
||||
|
||||
class HttpUpload(Upload):
|
||||
class HttpUpload(Upload, SyncHttpClient):
|
||||
"""
|
||||
helper for the http based uploads
|
||||
|
||||
Attributes:
|
||||
auth(tuple[str, str] | None): HTTP auth object if set
|
||||
timeout(int): HTTP request timeout in seconds
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration, section: str) -> None:
|
||||
@ -48,20 +41,7 @@ class HttpUpload(Upload):
|
||||
section(str): configuration section name
|
||||
"""
|
||||
Upload.__init__(self, architecture, configuration)
|
||||
password = configuration.get(section, "password", fallback=None)
|
||||
username = configuration.get(section, "username", fallback=None)
|
||||
self.auth = (password, username) if password and username else None
|
||||
self.timeout = configuration.getint(section, "timeout", fallback=30)
|
||||
|
||||
@cached_property
|
||||
def session(self) -> requests.Session:
|
||||
"""
|
||||
get or create session
|
||||
|
||||
Returns:
|
||||
request.Session: created session object
|
||||
"""
|
||||
return requests.Session()
|
||||
SyncHttpClient.__init__(self, section, configuration)
|
||||
|
||||
@staticmethod
|
||||
def calculate_hash(path: Path) -> str:
|
||||
@ -107,23 +87,3 @@ class HttpUpload(Upload):
|
||||
file, md5 = line.split()
|
||||
files[file] = md5
|
||||
return files
|
||||
|
||||
def _request(self, method: str, url: str, **kwargs: Any) -> requests.Response:
|
||||
"""
|
||||
request wrapper
|
||||
|
||||
Args:
|
||||
method(str): request method
|
||||
url(str): request url
|
||||
**kwargs(Any): request parameters to be passed as is
|
||||
|
||||
Returns:
|
||||
requests.Response: request response object
|
||||
"""
|
||||
try:
|
||||
response = self.session.request(method, url, auth=self.auth, timeout=self.timeout, **kwargs)
|
||||
response.raise_for_status()
|
||||
except requests.HTTPError as e:
|
||||
self.logger.exception("could not perform %s request to %s: %s", method, url, exception_response_text(e))
|
||||
raise
|
||||
return response
|
||||
|
@ -23,8 +23,9 @@ from functools import cached_property
|
||||
from pathlib import Path
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.http import MultipartType
|
||||
from ahriman.core.sign.gpg import GPG
|
||||
from ahriman.core.status.web_client import MultipartType, WebClient
|
||||
from ahriman.core.status.web_client import WebClient
|
||||
from ahriman.core.upload.http_upload import HttpUpload
|
||||
from ahriman.models.package import Package
|
||||
|
||||
@ -77,7 +78,7 @@ class RemoteService(HttpUpload):
|
||||
if signature_path is not None:
|
||||
files["signature"] = signature_path.name, signature_path.open("rb"), "application/octet-stream", {}
|
||||
|
||||
self._request("POST", f"{self.client.address}/api/v1/service/upload", files=files)
|
||||
self.make_request("POST", f"{self.client.address}/api/v1/service/upload", files=files)
|
||||
finally:
|
||||
for _, fd, _, _ in files.values():
|
||||
fd.close()
|
||||
|
@ -51,8 +51,8 @@ class Upload(LazyLogging):
|
||||
|
||||
>>> try:
|
||||
>>> upload.sync(configuration.repository_paths.repository, [])
|
||||
>>> except Exception as exception:
|
||||
>>> handle_exceptions(exception)
|
||||
>>> except Exception as ex:
|
||||
>>> handle_exceptions(ex)
|
||||
"""
|
||||
|
||||
def __init__(self, architecture: str, configuration: Configuration) -> None:
|
||||
|
@ -24,7 +24,6 @@ import itertools
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import requests
|
||||
import selectors
|
||||
import subprocess
|
||||
|
||||
@ -44,7 +43,6 @@ __all__ = [
|
||||
"check_user",
|
||||
"dataclass_view",
|
||||
"enum_values",
|
||||
"exception_response_text",
|
||||
"extract_user",
|
||||
"filter_json",
|
||||
"full_version",
|
||||
@ -214,20 +212,6 @@ def enum_values(enum: type[Enum]) -> list[str]:
|
||||
return [str(key.value) for key in enum] # explicit str conversion for typing
|
||||
|
||||
|
||||
def exception_response_text(exception: requests.exceptions.RequestException) -> str:
|
||||
"""
|
||||
safe response exception text generation
|
||||
|
||||
Args:
|
||||
exception(requests.exceptions.RequestException): exception raised
|
||||
|
||||
Returns:
|
||||
str: text of the response if it is not None and empty string otherwise
|
||||
"""
|
||||
result: str = exception.response.text if exception.response is not None else ""
|
||||
return result
|
||||
|
||||
|
||||
def extract_user() -> str | None:
|
||||
"""
|
||||
extract user from system environment
|
||||
|
@ -75,24 +75,6 @@ class User:
|
||||
object.__setattr__(self, "packager_id", self.packager_id or None)
|
||||
object.__setattr__(self, "key", self.key or None)
|
||||
|
||||
@classmethod
|
||||
def from_option(cls, username: str | None, password: str | None,
|
||||
access: UserAccess = UserAccess.Read) -> Self | None:
|
||||
"""
|
||||
build user descriptor from configuration options
|
||||
|
||||
Args:
|
||||
username(str | None): username
|
||||
password(str | None): password as string
|
||||
access(UserAccess, optional): optional user access (Default value = UserAccess.Read)
|
||||
|
||||
Returns:
|
||||
Self | None: generated user descriptor if all options are supplied and None otherwise
|
||||
"""
|
||||
if username is None or password is None:
|
||||
return None
|
||||
return cls(username=username, password=password, access=access, packager_id=None, key=None)
|
||||
|
||||
@staticmethod
|
||||
def generate_password(length: int) -> str:
|
||||
"""
|
||||
|
@ -60,31 +60,31 @@ def exception_handler(logger: logging.Logger) -> MiddlewareType:
|
||||
async def handle(request: Request, handler: HandlerType) -> StreamResponse:
|
||||
try:
|
||||
return await handler(request)
|
||||
except HTTPUnauthorized as e:
|
||||
except HTTPUnauthorized as ex:
|
||||
if _is_templated_unauthorized(request):
|
||||
context = {"code": e.status_code, "reason": e.reason}
|
||||
return aiohttp_jinja2.render_template("error.jinja2", request, context, status=e.status_code)
|
||||
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||
except HTTPMethodNotAllowed as e:
|
||||
if e.method == "OPTIONS":
|
||||
context = {"code": ex.status_code, "reason": ex.reason}
|
||||
return aiohttp_jinja2.render_template("error.jinja2", request, context, status=ex.status_code)
|
||||
return json_response(data={"error": ex.reason}, status=ex.status_code)
|
||||
except HTTPMethodNotAllowed as ex:
|
||||
if ex.method == "OPTIONS":
|
||||
# automatically handle OPTIONS method, idea comes from
|
||||
# https://github.com/arcan1s/ffxivbis/blob/master/src/main/scala/me/arcanis/ffxivbis/http/api/v1/HttpHandler.scala#L32
|
||||
raise HTTPNoContent(headers={"Allow": ",".join(sorted(e.allowed_methods))})
|
||||
if e.method == "HEAD":
|
||||
raise HTTPNoContent(headers={"Allow": ",".join(sorted(ex.allowed_methods))})
|
||||
if ex.method == "HEAD":
|
||||
# since we have special autogenerated HEAD method, we need to remove it from list of available
|
||||
e.allowed_methods = {method for method in e.allowed_methods if method != "HEAD"}
|
||||
e.headers["Allow"] = ",".join(sorted(e.allowed_methods))
|
||||
raise e
|
||||
ex.allowed_methods = {method for method in ex.allowed_methods if method != "HEAD"}
|
||||
ex.headers["Allow"] = ",".join(sorted(ex.allowed_methods))
|
||||
raise ex
|
||||
raise
|
||||
except HTTPClientError as e:
|
||||
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||
except HTTPServerError as e:
|
||||
except HTTPClientError as ex:
|
||||
return json_response(data={"error": ex.reason}, status=ex.status_code)
|
||||
except HTTPServerError as ex:
|
||||
logger.exception("server exception during performing request to %s", request.path)
|
||||
return json_response(data={"error": e.reason}, status=e.status_code)
|
||||
return json_response(data={"error": ex.reason}, status=ex.status_code)
|
||||
except HTTPException: # just raise 2xx and 3xx codes
|
||||
raise
|
||||
except Exception as e:
|
||||
except Exception as ex:
|
||||
logger.exception("unknown exception during performing request to %s", request.path)
|
||||
return json_response(data={"error": str(e)}, status=500)
|
||||
return json_response(data={"error": str(ex)}, status=500)
|
||||
|
||||
return handle
|
||||
|
@ -64,8 +64,8 @@ class AddView(BaseView):
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = self.get_non_empty(lambda key: [package for package in data[key] if package], "packages")
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_add(packages, username, now=True)
|
||||
|
@ -68,8 +68,8 @@ class PGPView(BaseView):
|
||||
try:
|
||||
key = self.get_non_empty(self.request.query.getone, "key")
|
||||
server = self.get_non_empty(self.request.query.getone, "server")
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
try:
|
||||
key = self.service.repository.sign.key_download(server, key)
|
||||
@ -107,8 +107,8 @@ class PGPView(BaseView):
|
||||
|
||||
try:
|
||||
key = self.get_non_empty(data.get, "key")
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
process_id = self.spawner.key_import(key, data.get("server"))
|
||||
|
||||
|
@ -65,8 +65,8 @@ class RebuildView(BaseView):
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = self.get_non_empty(lambda key: [package for package in data[key] if package], "packages")
|
||||
depends_on = next(iter(packages))
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_rebuild(depends_on, username)
|
||||
|
@ -64,8 +64,8 @@ class RemoveView(BaseView):
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = self.get_non_empty(lambda key: [package for package in data[key] if package], "packages")
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
process_id = self.spawner.packages_remove(packages)
|
||||
|
||||
|
@ -64,8 +64,8 @@ class RequestView(BaseView):
|
||||
try:
|
||||
data = await self.extract_data(["packages"])
|
||||
packages = self.get_non_empty(lambda key: [package for package in data[key] if package], "packages")
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_add(packages, username, now=False)
|
||||
|
@ -69,8 +69,8 @@ class SearchView(BaseView):
|
||||
try:
|
||||
search: list[str] = self.get_non_empty(lambda key: self.request.query.getall(key, default=[]), "for")
|
||||
packages = AUR.multisearch(*search, pacman=self.service.repository.pacman)
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
if not packages:
|
||||
raise HTTPNotFound(reason=f"No packages found for terms: {search}")
|
||||
|
@ -63,8 +63,8 @@ class UpdateView(BaseView):
|
||||
"""
|
||||
try:
|
||||
data = await self.extract_data()
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
username = await self.username()
|
||||
process_id = self.spawner.packages_update(
|
||||
|
@ -120,8 +120,8 @@ class UploadView(BaseView):
|
||||
|
||||
try:
|
||||
reader = await self.request.multipart()
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
max_body_size = self.configuration.getint("web", "max_body_size", fallback=None)
|
||||
target = self.configuration.repository_paths.packages
|
||||
|
@ -138,8 +138,8 @@ class LogsView(BaseView):
|
||||
created = data["created"]
|
||||
record = data["message"]
|
||||
version = data["version"]
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
self.service.logs_update(LogRecordId(package_base, version), created, record)
|
||||
|
||||
|
@ -138,8 +138,8 @@ class PackageView(BaseView):
|
||||
try:
|
||||
package = Package.from_json(data["package"]) if "package" in data else None
|
||||
status = BuildStatusEnum(data["status"])
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
try:
|
||||
self.service.package_update(package_base, status, package)
|
||||
|
@ -99,8 +99,8 @@ class StatusView(BaseView):
|
||||
try:
|
||||
data = await self.extract_data()
|
||||
status = BuildStatusEnum(data["status"])
|
||||
except Exception as e:
|
||||
raise HTTPBadRequest(reason=str(e))
|
||||
except Exception as ex:
|
||||
raise HTTPBadRequest(reason=str(ex))
|
||||
|
||||
self.service.status_update(status)
|
||||
|
||||
|
@ -67,80 +67,73 @@ def test_remote_web_url(aur_package_ahriman: AURPackage) -> None:
|
||||
assert web_url.startswith(AUR.DEFAULT_AUR_URL)
|
||||
|
||||
|
||||
def test_make_request(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
def test_aur_request(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must perform request to AUR
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.json.return_value = json.loads(_get_response(resource_path_root))
|
||||
request_mock = mocker.patch("requests.get", return_value=response_mock)
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=response_mock)
|
||||
|
||||
assert aur.make_request("info", "ahriman") == [aur_package_ahriman]
|
||||
assert aur.aur_request("info", "ahriman") == [aur_package_ahriman]
|
||||
request_mock.assert_called_once_with(
|
||||
"https://aur.archlinux.org/rpc",
|
||||
params={"v": "5", "type": "info", "arg": ["ahriman"]},
|
||||
headers={"User-Agent": AUR.DEFAULT_USER_AGENT},
|
||||
timeout=aur.DEFAULT_TIMEOUT)
|
||||
"GET", "https://aur.archlinux.org/rpc",
|
||||
params=[("type", "info"), ("v", "5"), ("arg", "ahriman")])
|
||||
|
||||
|
||||
def test_make_request_multi_arg(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
def test_aur_request_multi_arg(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must perform request to AUR with multiple args
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.json.return_value = json.loads(_get_response(resource_path_root))
|
||||
request_mock = mocker.patch("requests.get", return_value=response_mock)
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=response_mock)
|
||||
|
||||
assert aur.make_request("search", "ahriman", "is", "cool") == [aur_package_ahriman]
|
||||
assert aur.aur_request("search", "ahriman", "is", "cool") == [aur_package_ahriman]
|
||||
request_mock.assert_called_once_with(
|
||||
"https://aur.archlinux.org/rpc",
|
||||
params={"v": "5", "type": "search", "arg[]": ["ahriman", "is", "cool"]},
|
||||
headers={"User-Agent": AUR.DEFAULT_USER_AGENT},
|
||||
timeout=aur.DEFAULT_TIMEOUT)
|
||||
"GET", "https://aur.archlinux.org/rpc",
|
||||
params=[("type", "search"), ("v", "5"), ("arg[]", "ahriman"), ("arg[]", "is"), ("arg[]", "cool")])
|
||||
|
||||
|
||||
def test_make_request_with_kwargs(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
def test_aur_request_with_kwargs(aur: AUR, aur_package_ahriman: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must perform request to AUR with named parameters
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.json.return_value = json.loads(_get_response(resource_path_root))
|
||||
request_mock = mocker.patch("requests.get", return_value=response_mock)
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=response_mock)
|
||||
|
||||
assert aur.make_request("search", "ahriman", by="name") == [aur_package_ahriman]
|
||||
assert aur.aur_request("search", "ahriman", by="name") == [aur_package_ahriman]
|
||||
request_mock.assert_called_once_with(
|
||||
"https://aur.archlinux.org/rpc",
|
||||
params={"v": "5", "type": "search", "arg": ["ahriman"], "by": "name"},
|
||||
headers={"User-Agent": AUR.DEFAULT_USER_AGENT},
|
||||
timeout=aur.DEFAULT_TIMEOUT)
|
||||
"GET", "https://aur.archlinux.org/rpc",
|
||||
params=[("type", "search"), ("v", "5"), ("arg", "ahriman"), ("by", "name")])
|
||||
|
||||
|
||||
def test_make_request_failed(aur: AUR, mocker: MockerFixture) -> None:
|
||||
def test_aur_request_failed(aur: AUR, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise generic exception
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=Exception())
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
with pytest.raises(Exception):
|
||||
aur.make_request("info", "ahriman")
|
||||
aur.aur_request("info", "ahriman")
|
||||
|
||||
|
||||
def test_make_request_failed_http_error(aur: AUR, mocker: MockerFixture) -> None:
|
||||
def test_aur_request_failed_http_error(aur: AUR, mocker: MockerFixture) -> None:
|
||||
""" must reraise http exception
|
||||
"""
|
||||
must reraise http exception
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=requests.exceptions.HTTPError())
|
||||
with pytest.raises(requests.exceptions.HTTPError):
|
||||
aur.make_request("info", "ahriman")
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
with pytest.raises(requests.HTTPError):
|
||||
aur.aur_request("info", "ahriman")
|
||||
|
||||
|
||||
def test_package_info(aur: AUR, aur_package_ahriman: AURPackage, pacman: Pacman, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must make request for info
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=[aur_package_ahriman])
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.aur_request", return_value=[aur_package_ahriman])
|
||||
assert aur.package_info(aur_package_ahriman.name, pacman=pacman) == aur_package_ahriman
|
||||
request_mock.assert_called_once_with("info", aur_package_ahriman.name)
|
||||
|
||||
@ -150,7 +143,7 @@ def test_package_info_not_found(aur: AUR, aur_package_ahriman: AURPackage, pacma
|
||||
"""
|
||||
must raise UnknownPackage exception in case if no package was found
|
||||
"""
|
||||
mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=[])
|
||||
mocker.patch("ahriman.core.alpm.remote.AUR.aur_request", return_value=[])
|
||||
with pytest.raises(UnknownPackageError, match=aur_package_ahriman.name):
|
||||
assert aur.package_info(aur_package_ahriman.name, pacman=pacman)
|
||||
|
||||
@ -159,6 +152,6 @@ def test_package_search(aur: AUR, aur_package_ahriman: AURPackage, pacman: Pacma
|
||||
"""
|
||||
must make request for search
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.make_request", return_value=[aur_package_ahriman])
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.AUR.aur_request", return_value=[aur_package_ahriman])
|
||||
assert aur.package_search(aur_package_ahriman.name, pacman=pacman) == [aur_package_ahriman]
|
||||
request_mock.assert_called_once_with("search", aur_package_ahriman.name, by="name-desc")
|
||||
|
@ -62,39 +62,37 @@ def test_remote_web_url(aur_package_akonadi: AURPackage) -> None:
|
||||
assert web_url.startswith(Official.DEFAULT_ARCHLINUX_URL)
|
||||
|
||||
|
||||
def test_make_request(official: Official, aur_package_akonadi: AURPackage,
|
||||
def test_arch_request(official: Official, aur_package_akonadi: AURPackage,
|
||||
mocker: MockerFixture, resource_path_root: Path) -> None:
|
||||
"""
|
||||
must perform request to official repositories
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.json.return_value = json.loads(_get_response(resource_path_root))
|
||||
request_mock = mocker.patch("requests.get", return_value=response_mock)
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.Official.make_request", return_value=response_mock)
|
||||
|
||||
assert official.make_request("akonadi", by="q") == [aur_package_akonadi]
|
||||
assert official.arch_request("akonadi", by="q") == [aur_package_akonadi]
|
||||
request_mock.assert_called_once_with(
|
||||
"https://archlinux.org/packages/search/json",
|
||||
params={"q": ("akonadi",), "repo": Official.DEFAULT_SEARCH_REPOSITORIES},
|
||||
headers={"User-Agent": Official.DEFAULT_USER_AGENT},
|
||||
timeout=official.DEFAULT_TIMEOUT)
|
||||
"GET", "https://archlinux.org/packages/search/json",
|
||||
params=[("repo", repository) for repository in Official.DEFAULT_SEARCH_REPOSITORIES] + [("q", "akonadi")])
|
||||
|
||||
|
||||
def test_make_request_failed(official: Official, mocker: MockerFixture) -> None:
|
||||
def test_arch_request_failed(official: Official, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise generic exception
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=Exception())
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
with pytest.raises(Exception):
|
||||
official.make_request("akonadi", by="q")
|
||||
official.arch_request("akonadi", by="q")
|
||||
|
||||
|
||||
def test_make_request_failed_http_error(official: Official, mocker: MockerFixture) -> None:
|
||||
def test_arch_request_failed_http_error(official: Official, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise http exception
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=requests.exceptions.HTTPError())
|
||||
with pytest.raises(requests.exceptions.HTTPError):
|
||||
official.make_request("akonadi", by="q")
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
with pytest.raises(requests.HTTPError):
|
||||
official.arch_request("akonadi", by="q")
|
||||
|
||||
|
||||
def test_package_info(official: Official, aur_package_akonadi: AURPackage, pacman: Pacman,
|
||||
@ -102,7 +100,7 @@ def test_package_info(official: Official, aur_package_akonadi: AURPackage, pacma
|
||||
"""
|
||||
must make request for info
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.Official.make_request",
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.Official.arch_request",
|
||||
return_value=[aur_package_akonadi])
|
||||
assert official.package_info(aur_package_akonadi.name, pacman=pacman) == aur_package_akonadi
|
||||
request_mock.assert_called_once_with(aur_package_akonadi.name, by="name")
|
||||
@ -113,7 +111,7 @@ def test_package_info_not_found(official: Official, aur_package_ahriman: AURPack
|
||||
"""
|
||||
must raise UnknownPackage exception in case if no package was found
|
||||
"""
|
||||
mocker.patch("ahriman.core.alpm.remote.Official.make_request", return_value=[])
|
||||
mocker.patch("ahriman.core.alpm.remote.Official.arch_request", return_value=[])
|
||||
with pytest.raises(UnknownPackageError, match=aur_package_ahriman.name):
|
||||
assert official.package_info(aur_package_ahriman.name, pacman=pacman)
|
||||
|
||||
@ -123,7 +121,7 @@ def test_package_search(official: Official, aur_package_akonadi: AURPackage, pac
|
||||
"""
|
||||
must make request for search
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.Official.make_request",
|
||||
request_mock = mocker.patch("ahriman.core.alpm.remote.Official.arch_request",
|
||||
return_value=[aur_package_akonadi])
|
||||
assert official.package_search(aur_package_akonadi.name, pacman=pacman) == [aur_package_akonadi]
|
||||
request_mock.assert_called_once_with(aur_package_akonadi.name, by="q")
|
||||
|
154
tests/ahriman/core/http/test_sync_http_client.py
Normal file
154
tests/ahriman/core/http/test_sync_http_client.py
Normal file
@ -0,0 +1,154 @@
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock, call as MockCall
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.http import SyncHttpClient
|
||||
|
||||
|
||||
def test_init() -> None:
|
||||
"""
|
||||
must init from empty parameters
|
||||
"""
|
||||
assert SyncHttpClient()
|
||||
|
||||
|
||||
def test_init_auth(configuration: Configuration) -> None:
|
||||
"""
|
||||
must init with auth
|
||||
"""
|
||||
configuration.set_option("web", "username", "username")
|
||||
configuration.set_option("web", "password", "password")
|
||||
|
||||
assert SyncHttpClient("web", configuration).auth == ("username", "password")
|
||||
assert SyncHttpClient(configuration=configuration).auth is None
|
||||
|
||||
|
||||
def test_init_auth_empty() -> None:
|
||||
"""
|
||||
must init with empty auth
|
||||
"""
|
||||
assert SyncHttpClient().auth is None
|
||||
|
||||
|
||||
def test_session() -> None:
|
||||
"""
|
||||
must generate valid session
|
||||
"""
|
||||
session = SyncHttpClient().session
|
||||
assert "User-Agent" in session.headers
|
||||
|
||||
|
||||
def test_exception_response_text() -> None:
|
||||
"""
|
||||
must parse HTTP response to string
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.text = "hello"
|
||||
exception = requests.exceptions.HTTPError(response=response_mock)
|
||||
|
||||
assert SyncHttpClient.exception_response_text(exception) == "hello"
|
||||
|
||||
|
||||
def test_exception_response_text_empty() -> None:
|
||||
"""
|
||||
must parse HTTP exception with empty response to empty string
|
||||
"""
|
||||
exception = requests.exceptions.HTTPError(response=None)
|
||||
assert SyncHttpClient.exception_response_text(exception) == ""
|
||||
|
||||
|
||||
def test_make_request(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must make HTTP request
|
||||
"""
|
||||
request_mock = mocker.patch("requests.Session.request")
|
||||
client = SyncHttpClient()
|
||||
|
||||
assert client.make_request("GET", "url1") is not None
|
||||
assert client.make_request("GET", "url2", params=[("param", "value")]) is not None
|
||||
|
||||
assert client.make_request("POST", "url3") is not None
|
||||
assert client.make_request("POST", "url4", json={"param": "value"}) is not None
|
||||
assert client.make_request("POST", "url5", data={"param": "value"}) is not None
|
||||
# we don't want to put full descriptor here
|
||||
assert client.make_request("POST", "url6", files={"file": "tuple"}) is not None
|
||||
|
||||
assert client.make_request("DELETE", "url7") is not None
|
||||
|
||||
assert client.make_request("GET", "url8", headers={"user-agent": "ua"}) is not None
|
||||
|
||||
auth = client.auth = ("username", "password")
|
||||
assert client.make_request("GET", "url9") is not None
|
||||
|
||||
request_mock.assert_has_calls([
|
||||
MockCall("GET", "url1", params=None, data=None, headers=None, files=None, json=None,
|
||||
auth=None, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("GET", "url2", params=[("param", "value")], data=None, headers=None, files=None, json=None,
|
||||
auth=None, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", "url3", params=None, data=None, headers=None, files=None, json=None,
|
||||
auth=None, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", "url4", params=None, data=None, headers=None, files=None, json={"param": "value"},
|
||||
auth=None, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", "url5", params=None, data={"param": "value"}, headers=None, files=None, json=None,
|
||||
auth=None, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", "url6", params=None, data=None, headers=None, files={"file": "tuple"}, json=None,
|
||||
auth=None, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("DELETE", "url7", params=None, data=None, headers=None, files=None, json=None,
|
||||
auth=None, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("GET", "url8", params=None, data=None, headers={"user-agent": "ua"}, files=None, json=None,
|
||||
auth=None, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("GET", "url9", params=None, data=None, headers=None, files=None, json=None,
|
||||
auth=auth, timeout=client.timeout),
|
||||
MockCall().raise_for_status(),
|
||||
])
|
||||
|
||||
|
||||
def test_make_request_failed(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process request errors
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
logging_mock = mocker.patch("logging.Logger.exception")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
SyncHttpClient().make_request("GET", "url")
|
||||
logging_mock.assert_called_once() # we do not check logging arguments
|
||||
|
||||
|
||||
def test_make_request_suppress_errors(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must suppress request errors correctly
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
logging_mock = mocker.patch("logging.Logger.exception")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
SyncHttpClient().make_request("GET", "url", suppress_errors=True)
|
||||
with pytest.raises(Exception):
|
||||
SyncHttpClient(suppress_errors=True).make_request("GET", "url")
|
||||
|
||||
logging_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_make_request_session() -> None:
|
||||
"""
|
||||
must use session from arguments
|
||||
"""
|
||||
session_mock = MagicMock()
|
||||
client = SyncHttpClient()
|
||||
|
||||
client.make_request("GET", "url", session=session_mock)
|
||||
session_mock.request.assert_called_once_with(
|
||||
"GET", "url", params=None, data=None, headers=None, files=None, json=None,
|
||||
auth=None, timeout=client.timeout)
|
@ -40,7 +40,7 @@ def test_is_process_alive_unknown(remote_call: RemoteCall, mocker: MockerFixture
|
||||
response = requests.Response()
|
||||
response.status_code = 404
|
||||
mocker.patch("ahriman.core.status.web_client.WebClient.make_request",
|
||||
side_effect=requests.RequestException(response=response))
|
||||
side_effect=requests.HTTPError(response=response))
|
||||
|
||||
assert not remote_call.is_process_alive("id")
|
||||
|
||||
@ -62,9 +62,9 @@ def test_is_process_alive_http_error(remote_call: RemoteCall, mocker: MockerFixt
|
||||
response = requests.Response()
|
||||
response.status_code = 500
|
||||
mocker.patch("ahriman.core.status.web_client.WebClient.make_request",
|
||||
side_effect=requests.RequestException(response=response))
|
||||
side_effect=requests.HTTPError(response=response))
|
||||
|
||||
with pytest.raises(requests.RequestException):
|
||||
with pytest.raises(requests.HTTPError):
|
||||
remote_call.is_process_alive("id")
|
||||
|
||||
|
||||
|
@ -14,35 +14,35 @@ def test_send(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must send a message
|
||||
"""
|
||||
request_mock = mocker.patch("requests.post")
|
||||
request_mock = mocker.patch("ahriman.core.report.telegram.Telegram.make_request")
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
report._send("a text")
|
||||
request_mock.assert_called_once_with(
|
||||
"POST",
|
||||
pytest.helpers.anyvar(str, strict=True),
|
||||
data={"chat_id": pytest.helpers.anyvar(str, strict=True), "text": "a text", "parse_mode": "HTML"},
|
||||
timeout=report.timeout)
|
||||
data={"chat_id": pytest.helpers.anyvar(str, strict=True), "text": "a text", "parse_mode": "HTML"})
|
||||
|
||||
|
||||
def test_send_failed(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise generic exception
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=Exception())
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
with pytest.raises(Exception):
|
||||
report._send("a text")
|
||||
|
||||
|
||||
def test_make_request_failed_http_error(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
def test_send_failed_http_error(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must reraise http exception
|
||||
"""
|
||||
mocker.patch("requests.post", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
report = Telegram("x86_64", configuration, "telegram")
|
||||
|
||||
with pytest.raises(requests.exceptions.HTTPError):
|
||||
with pytest.raises(requests.HTTPError):
|
||||
report._send("a text")
|
||||
|
||||
|
||||
|
@ -87,20 +87,19 @@ def test_key_download(gpg: GPG, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must download the key from public server
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.get")
|
||||
requests_mock = mocker.patch("ahriman.core.sign.gpg.GPG.make_request")
|
||||
gpg.key_download("keyserver.ubuntu.com", "0xE989490C")
|
||||
requests_mock.assert_called_once_with(
|
||||
"https://keyserver.ubuntu.com/pks/lookup",
|
||||
params={"op": "get", "options": "mr", "search": "0xE989490C"},
|
||||
timeout=gpg.DEFAULT_TIMEOUT)
|
||||
"GET", "https://keyserver.ubuntu.com/pks/lookup",
|
||||
params=[("op", "get"), ("options", "mr"), ("search", "0xE989490C")])
|
||||
|
||||
|
||||
def test_key_download_failure(gpg: GPG, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must download the key from public server and log error if any (and raise it again)
|
||||
"""
|
||||
mocker.patch("requests.get", side_effect=requests.exceptions.HTTPError())
|
||||
with pytest.raises(requests.exceptions.HTTPError):
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
with pytest.raises(requests.HTTPError):
|
||||
gpg.key_download("keyserver.ubuntu.com", "0xE989490C")
|
||||
|
||||
|
||||
|
@ -5,7 +5,6 @@ import requests
|
||||
import requests_unixsocket
|
||||
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import call as MockCall
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.status.web_client import WebClient
|
||||
@ -16,35 +15,6 @@ from ahriman.models.package import Package
|
||||
from ahriman.models.user import User
|
||||
|
||||
|
||||
def test_login_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must generate login url correctly
|
||||
"""
|
||||
assert web_client._login_url.endswith("/api/v1/login")
|
||||
|
||||
|
||||
def test_status_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must generate package status url correctly
|
||||
"""
|
||||
assert web_client._status_url.endswith("/api/v1/status")
|
||||
|
||||
|
||||
def test_logs_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate logs url correctly
|
||||
"""
|
||||
assert web_client._logs_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/logs")
|
||||
|
||||
|
||||
def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate package status url correctly
|
||||
"""
|
||||
assert web_client._package_url("").endswith("/api/v1/packages")
|
||||
assert web_client._package_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}")
|
||||
|
||||
|
||||
def test_parse_address(configuration: Configuration) -> None:
|
||||
"""
|
||||
must extract address correctly
|
||||
@ -87,16 +57,16 @@ def test_login(web_client: WebClient, user: User, mocker: MockerFixture) -> None
|
||||
"""
|
||||
must login user
|
||||
"""
|
||||
web_client.user = user
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
web_client.auth = (user.username, user.password)
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
payload = {
|
||||
"username": user.username,
|
||||
"password": user.password
|
||||
}
|
||||
session = requests.Session()
|
||||
|
||||
web_client._login(requests.Session())
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
|
||||
params=None, json=payload, files=None)
|
||||
web_client._login(session)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), json=payload, session=session)
|
||||
|
||||
|
||||
def test_login_failed(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
|
||||
@ -113,7 +83,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.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client._login(requests.Session())
|
||||
|
||||
|
||||
@ -126,57 +96,50 @@ def test_login_skip(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
requests_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_make_request(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
def test_login_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must make HTTP request
|
||||
must generate login url correctly
|
||||
"""
|
||||
request_mock = mocker.patch("requests.Session.request")
|
||||
|
||||
assert web_client.make_request("GET", "/url1") is not None
|
||||
assert web_client.make_request("GET", "/url2", params=[("param", "value")]) is not None
|
||||
|
||||
assert web_client.make_request("POST", "/url3") is not None
|
||||
assert web_client.make_request("POST", "/url4", json={"param": "value"}) is not None
|
||||
# we don't want to put full descriptor here
|
||||
assert web_client.make_request("POST", "/url5", files={"file": "tuple"}) is not None
|
||||
|
||||
assert web_client.make_request("DELETE", "/url6") is not None
|
||||
|
||||
request_mock.assert_has_calls([
|
||||
MockCall("GET", f"{web_client.address}/url1", params=None, json=None, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("GET", f"{web_client.address}/url2", params=[("param", "value")], json=None, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", f"{web_client.address}/url3", params=None, json=None, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", f"{web_client.address}/url4", params=None, json={"param": "value"}, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", f"{web_client.address}/url5", params=None, json=None, files={"file": "tuple"}),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("DELETE", f"{web_client.address}/url6", params=None, json=None, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
])
|
||||
assert web_client._login_url().startswith(web_client.address)
|
||||
assert web_client._login_url().endswith("/api/v1/login")
|
||||
|
||||
|
||||
def test_make_request_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
def test_status_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must make HTTP request
|
||||
must generate package status url correctly
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=Exception())
|
||||
with pytest.raises(Exception):
|
||||
web_client.make_request("GET", "url")
|
||||
assert web_client._status_url().startswith(web_client.address)
|
||||
assert web_client._status_url().endswith("/api/v1/status")
|
||||
|
||||
|
||||
def test_logs_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate logs url correctly
|
||||
"""
|
||||
assert web_client._logs_url(package_ahriman.base).startswith(web_client.address)
|
||||
assert web_client._logs_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}/logs")
|
||||
|
||||
|
||||
def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate package status url correctly
|
||||
"""
|
||||
assert web_client._package_url("").startswith(web_client.address)
|
||||
assert web_client._package_url("").endswith("/api/v1/packages")
|
||||
|
||||
assert web_client._package_url(package_ahriman.base).startswith(web_client.address)
|
||||
assert web_client._package_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}")
|
||||
|
||||
|
||||
def test_package_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process package addition
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
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=None, json=payload, files=None)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), json=payload)
|
||||
|
||||
|
||||
def test_package_add_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -191,7 +154,7 @@ def test_package_add_failed_http_error(web_client: WebClient, package_ahriman: P
|
||||
"""
|
||||
must suppress HTTP exception happened during addition
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
@ -213,7 +176,7 @@ def test_package_add_failed_http_error_suppress(web_client: WebClient, package_a
|
||||
must suppress HTTP exception happened during addition and don't log
|
||||
"""
|
||||
web_client.suppress_errors = True
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
logging_mock = mocker.patch("logging.exception")
|
||||
|
||||
web_client.package_add(package_ahriman, BuildStatusEnum.Unknown)
|
||||
@ -229,11 +192,11 @@ def test_package_get_all(web_client: WebClient, package_ahriman: Package, mocker
|
||||
response_obj._content = json.dumps(response).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("requests.Session.request", return_value=response_obj)
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request",
|
||||
return_value=response_obj)
|
||||
|
||||
result = web_client.package_get(None)
|
||||
requests_mock.assert_called_once_with("GET", f"{web_client.address}{web_client._package_url()}",
|
||||
params=None, json=None, files=None)
|
||||
requests_mock.assert_called_once_with("GET", web_client._package_url())
|
||||
assert len(result) == len(response)
|
||||
assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
|
||||
|
||||
@ -250,7 +213,7 @@ def test_package_get_failed_http_error(web_client: WebClient, mocker: MockerFixt
|
||||
"""
|
||||
must suppress HTTP exception happened during status getting
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
assert web_client.package_get(None) == []
|
||||
|
||||
|
||||
@ -263,12 +226,11 @@ def test_package_get_single(web_client: WebClient, package_ahriman: Package, moc
|
||||
response_obj._content = json.dumps(response).encode("utf8")
|
||||
response_obj.status_code = 200
|
||||
|
||||
requests_mock = mocker.patch("requests.Session.request", return_value=response_obj)
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request",
|
||||
return_value=response_obj)
|
||||
|
||||
result = web_client.package_get(package_ahriman.base)
|
||||
requests_mock.assert_called_once_with("GET",
|
||||
f"{web_client.address}{web_client._package_url(package_ahriman.base)}",
|
||||
params=None, json=None, files=None)
|
||||
requests_mock.assert_called_once_with("GET", web_client._package_url(package_ahriman.base))
|
||||
assert len(result) == len(response)
|
||||
assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
|
||||
|
||||
@ -278,7 +240,7 @@ def test_package_logs(web_client: WebClient, log_record: logging.LogRecord, pack
|
||||
"""
|
||||
must process log record
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
payload = {
|
||||
"created": log_record.created,
|
||||
"message": log_record.getMessage(),
|
||||
@ -286,8 +248,8 @@ def test_package_logs(web_client: WebClient, log_record: logging.LogRecord, pack
|
||||
}
|
||||
|
||||
web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
|
||||
params=None, json=payload, files=None)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), json=payload,
|
||||
suppress_errors=True)
|
||||
|
||||
|
||||
def test_package_logs_failed(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
@ -306,7 +268,7 @@ def test_package_logs_failed_http_error(web_client: WebClient, log_record: loggi
|
||||
"""
|
||||
must pass exception during log post
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
log_record.package_base = package_ahriman.base
|
||||
with pytest.raises(Exception):
|
||||
web_client.package_logs(LogRecordId(package_ahriman.base, package_ahriman.version), log_record)
|
||||
@ -316,11 +278,10 @@ def test_package_remove(web_client: WebClient, package_ahriman: Package, mocker:
|
||||
"""
|
||||
must process package removal
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
|
||||
web_client.package_remove(package_ahriman.base)
|
||||
requests_mock.assert_called_once_with("DELETE", pytest.helpers.anyvar(str, True),
|
||||
params=None, json=None, files=None)
|
||||
requests_mock.assert_called_once_with("DELETE", pytest.helpers.anyvar(str, True))
|
||||
|
||||
|
||||
def test_package_remove_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -336,7 +297,7 @@ def test_package_remove_failed_http_error(web_client: WebClient, package_ahriman
|
||||
"""
|
||||
must suppress HTTP exception happened during removal
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client.package_remove(package_ahriman.base)
|
||||
|
||||
|
||||
@ -344,12 +305,12 @@ def test_package_update(web_client: WebClient, package_ahriman: Package, mocker:
|
||||
"""
|
||||
must process package update
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
|
||||
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json={
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), json={
|
||||
"status": BuildStatusEnum.Unknown.value
|
||||
}, files=None)
|
||||
})
|
||||
|
||||
|
||||
def test_package_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -365,7 +326,7 @@ def test_package_update_failed_http_error(web_client: WebClient, package_ahriman
|
||||
"""
|
||||
must suppress HTTP exception happened during update
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
|
||||
|
||||
@ -378,11 +339,11 @@ def test_status_get(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
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)
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request",
|
||||
return_value=response_obj)
|
||||
|
||||
result = web_client.status_get()
|
||||
requests_mock.assert_called_once_with("GET", f"{web_client.address}{web_client._status_url}",
|
||||
params=None, json=None, files=None)
|
||||
requests_mock.assert_called_once_with("GET", web_client._status_url())
|
||||
assert result.architecture == "x86_64"
|
||||
|
||||
|
||||
@ -398,7 +359,7 @@ def test_status_get_failed_http_error(web_client: WebClient, mocker: MockerFixtu
|
||||
"""
|
||||
must suppress HTTP exception happened during web service status getting
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
assert web_client.status_get().architecture is None
|
||||
|
||||
|
||||
@ -406,12 +367,12 @@ def test_status_update(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must process service update
|
||||
"""
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
requests_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
|
||||
web_client.status_update(BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json={
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), json={
|
||||
"status": BuildStatusEnum.Unknown.value
|
||||
}, files=None)
|
||||
})
|
||||
|
||||
|
||||
def test_status_update_self_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
@ -426,5 +387,5 @@ def test_status_update_failed_http_error(web_client: WebClient, mocker: MockerFi
|
||||
"""
|
||||
must suppress HTTP exception happened during service update
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.exceptions.HTTPError())
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
web_client.status_update(BuildStatusEnum.Unknown)
|
||||
|
@ -10,9 +10,9 @@ from typing import Any
|
||||
from unittest.mock import MagicMock, call as MockCall
|
||||
|
||||
from ahriman.core.exceptions import BuildError, CalledProcessError, OptionError, UnsafeRunError
|
||||
from ahriman.core.util import check_output, check_user, dataclass_view, enum_values, exception_response_text, \
|
||||
extract_user, filter_json, full_version, package_like, parse_version, partition, pretty_datetime, pretty_size, \
|
||||
safe_filename, srcinfo_property, srcinfo_property_list, trim_package, utcnow, walk
|
||||
from ahriman.core.util import check_output, check_user, dataclass_view, enum_values, extract_user, filter_json, \
|
||||
full_version, package_like, parse_version, partition, pretty_datetime, pretty_size, safe_filename, \
|
||||
srcinfo_property, srcinfo_property_list, trim_package, utcnow, walk
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.package_source import PackageSource
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
@ -204,25 +204,6 @@ def test_dataclass_view_without_none(package_ahriman: Package) -> None:
|
||||
assert Package.from_json(result) == package_ahriman
|
||||
|
||||
|
||||
def test_exception_response_text() -> None:
|
||||
"""
|
||||
must parse HTTP response to string
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
response_mock.text = "hello"
|
||||
exception = requests.exceptions.HTTPError(response=response_mock)
|
||||
|
||||
assert exception_response_text(exception) == "hello"
|
||||
|
||||
|
||||
def test_exception_response_text_empty() -> None:
|
||||
"""
|
||||
must parse HTTP exception with empty response to empty string
|
||||
"""
|
||||
exception = requests.exceptions.HTTPError(response=None)
|
||||
assert exception_response_text(exception) == ""
|
||||
|
||||
|
||||
def test_extract_user() -> None:
|
||||
"""
|
||||
must extract user from system environment
|
||||
|
@ -13,7 +13,7 @@ def test_asset_remove(github: Github, github_release: dict[str, Any], mocker: Mo
|
||||
"""
|
||||
must remove asset from the release
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github.make_request")
|
||||
github.asset_remove(github_release, "asset_name")
|
||||
request_mock.assert_called_once_with("DELETE", "asset_url")
|
||||
|
||||
@ -22,7 +22,7 @@ def test_asset_remove_unknown(github: Github, github_release: dict[str, Any], mo
|
||||
"""
|
||||
must not fail if no asset found
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github.make_request")
|
||||
github.asset_remove(github_release, "unknown_asset_name")
|
||||
request_mock.assert_not_called()
|
||||
|
||||
@ -32,11 +32,11 @@ def test_asset_upload(github: Github, github_release: dict[str, Any], mocker: Mo
|
||||
must upload asset to the repository
|
||||
"""
|
||||
mocker.patch("pathlib.Path.open")
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github.make_request")
|
||||
remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
|
||||
|
||||
github.asset_upload(github_release, Path("/root/new.tar.xz"))
|
||||
request_mock.assert_called_once_with("POST", "upload_url", params={"name": "new.tar.xz"},
|
||||
request_mock.assert_called_once_with("POST", "upload_url", params=[("name", "new.tar.xz")],
|
||||
data=pytest.helpers.anyvar(int),
|
||||
headers={"Content-Type": "application/x-tar"})
|
||||
remove_mock.assert_not_called()
|
||||
@ -47,7 +47,7 @@ def test_asset_upload_with_removal(github: Github, github_release: dict[str, Any
|
||||
must remove existing file before upload
|
||||
"""
|
||||
mocker.patch("pathlib.Path.open")
|
||||
mocker.patch("ahriman.core.upload.github.Github._request")
|
||||
mocker.patch("ahriman.core.upload.github.Github.make_request")
|
||||
remove_mock = mocker.patch("ahriman.core.upload.github.Github.asset_remove")
|
||||
|
||||
github.asset_upload(github_release, Path("asset_name"))
|
||||
@ -65,10 +65,10 @@ def test_asset_upload_empty_mimetype(github: Github, github_release: dict[str, A
|
||||
mocker.patch("pathlib.Path.open")
|
||||
mocker.patch("ahriman.core.upload.github.Github.asset_remove")
|
||||
mocker.patch("mimetypes.guess_type", return_value=(None, None))
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github.make_request")
|
||||
|
||||
github.asset_upload(github_release, Path("/root/new.tar.xz"))
|
||||
request_mock.assert_called_once_with("POST", "upload_url", params={"name": "new.tar.xz"},
|
||||
request_mock.assert_called_once_with("POST", "upload_url", params=[("name", "new.tar.xz")],
|
||||
data=pytest.helpers.anyvar(int),
|
||||
headers={"Content-Type": "application/octet-stream"})
|
||||
|
||||
@ -125,7 +125,7 @@ def test_release_create(github: Github, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must create release
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github.make_request")
|
||||
github.release_create()
|
||||
request_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
|
||||
json={"tag_name": github.architecture, "name": github.architecture})
|
||||
@ -135,7 +135,7 @@ def test_release_get(github: Github, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must get release
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github.make_request")
|
||||
github.release_get()
|
||||
request_mock.assert_called_once_with("GET", pytest.helpers.anyvar(str, True))
|
||||
|
||||
@ -146,7 +146,8 @@ def test_release_get_empty(github: Github, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
response = requests.Response()
|
||||
response.status_code = 404
|
||||
mocker.patch("ahriman.core.upload.github.Github._request", side_effect=requests.HTTPError(response=response))
|
||||
mocker.patch("ahriman.core.upload.github.Github.make_request",
|
||||
side_effect=requests.HTTPError(response=response))
|
||||
assert github.release_get() is None
|
||||
|
||||
|
||||
@ -154,7 +155,7 @@ def test_release_get_exception(github: Github, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must re-raise non HTTPError exception
|
||||
"""
|
||||
mocker.patch("ahriman.core.upload.github.Github._request", side_effect=Exception())
|
||||
mocker.patch("ahriman.core.upload.github.Github.make_request", side_effect=Exception())
|
||||
with pytest.raises(Exception):
|
||||
github.release_get()
|
||||
|
||||
@ -164,7 +165,7 @@ def test_release_get_exception_http_error(github: Github, mocker: MockerFixture)
|
||||
must re-raise HTTPError exception with code differs from 404
|
||||
"""
|
||||
exception = requests.HTTPError(response=requests.Response())
|
||||
mocker.patch("ahriman.core.upload.github.Github._request", side_effect=exception)
|
||||
mocker.patch("ahriman.core.upload.github.Github.make_request", side_effect=exception)
|
||||
with pytest.raises(requests.HTTPError):
|
||||
github.release_get()
|
||||
|
||||
@ -173,7 +174,7 @@ def test_release_update(github: Github, github_release: dict[str, Any], mocker:
|
||||
"""
|
||||
must update release
|
||||
"""
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github._request")
|
||||
request_mock = mocker.patch("ahriman.core.upload.github.Github.make_request")
|
||||
github.release_update(github_release, "body")
|
||||
request_mock.assert_called_once_with("POST", "release_url", json={"body": "body"})
|
||||
|
||||
|
@ -1,11 +1,5 @@
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.core.upload.github import Github
|
||||
from ahriman.core.upload.http_upload import HttpUpload
|
||||
|
||||
|
||||
@ -40,24 +34,3 @@ def test_get_hashes_empty() -> None:
|
||||
must read empty body
|
||||
"""
|
||||
assert HttpUpload.get_hashes("") == {}
|
||||
|
||||
|
||||
def test_request(github: Github, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call request method
|
||||
"""
|
||||
response_mock = MagicMock()
|
||||
request_mock = mocker.patch("requests.Session.request", return_value=response_mock)
|
||||
|
||||
github._request("GET", "url", arg="arg")
|
||||
request_mock.assert_called_once_with("GET", "url", auth=github.auth, timeout=github.timeout, arg="arg")
|
||||
response_mock.raise_for_status.assert_called_once_with()
|
||||
|
||||
|
||||
def test_request_exception(github: Github, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call request method and log HTTPError exception
|
||||
"""
|
||||
mocker.patch("requests.Session.request", side_effect=requests.HTTPError())
|
||||
with pytest.raises(requests.HTTPError):
|
||||
github._request("GET", "url", arg="arg")
|
||||
|
@ -24,7 +24,7 @@ def test_package_upload(remote_service: RemoteService, package_ahriman: Package,
|
||||
mocker.patch("pathlib.Path.is_file", return_value=False)
|
||||
file_mock = MagicMock()
|
||||
open_mock = mocker.patch("pathlib.Path.open", return_value=file_mock)
|
||||
upload_mock = mocker.patch("ahriman.core.upload.http_upload.HttpUpload._request")
|
||||
upload_mock = mocker.patch("ahriman.core.upload.http_upload.HttpUpload.make_request")
|
||||
filename = package_ahriman.packages[package_ahriman.base].filename
|
||||
|
||||
remote_service.sync(Path("local"), [package_ahriman])
|
||||
@ -43,7 +43,7 @@ def test_package_upload_with_signature(remote_service: RemoteService, package_ah
|
||||
mocker.patch("pathlib.Path.is_file", return_value=True)
|
||||
file_mock = MagicMock()
|
||||
open_mock = mocker.patch("pathlib.Path.open", return_value=file_mock)
|
||||
upload_mock = mocker.patch("ahriman.core.upload.http_upload.HttpUpload._request")
|
||||
upload_mock = mocker.patch("ahriman.core.upload.http_upload.HttpUpload.make_request")
|
||||
filename = package_ahriman.packages[package_ahriman.base].filename
|
||||
|
||||
remote_service.sync(Path("local"), [package_ahriman])
|
||||
|
@ -4,27 +4,6 @@ from ahriman.models.user import User
|
||||
from ahriman.models.user_access import UserAccess
|
||||
|
||||
|
||||
def test_from_option(user: User) -> None:
|
||||
"""
|
||||
must generate user from options
|
||||
"""
|
||||
user = replace(user, access=UserAccess.Read, packager_id=None, key=None)
|
||||
assert User.from_option(user.username, user.password) == user
|
||||
# default is read access
|
||||
user = replace(user, access=UserAccess.Full)
|
||||
assert User.from_option(user.username, user.password) != user
|
||||
assert User.from_option(user.username, user.password, user.access) == user
|
||||
|
||||
|
||||
def test_from_option_empty() -> None:
|
||||
"""
|
||||
must return nothing if settings are missed
|
||||
"""
|
||||
assert User.from_option(None, "") is None
|
||||
assert User.from_option("", None) is None
|
||||
assert User.from_option(None, None) is None
|
||||
|
||||
|
||||
def test_check_credentials_hash_password(user: User) -> None:
|
||||
"""
|
||||
must generate and validate user password
|
||||
|
Loading…
Reference in New Issue
Block a user