mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-07-16 15:29:56 +00:00
add cross-service upload
This commit is contained in:
@ -70,6 +70,7 @@ def test_schema(configuration: Configuration) -> None:
|
||||
assert schema.pop("remote-call")
|
||||
assert schema.pop("remote-pull")
|
||||
assert schema.pop("remote-push")
|
||||
assert schema.pop("remote-service")
|
||||
assert schema.pop("report")
|
||||
assert schema.pop("rsync")
|
||||
assert schema.pop("s3")
|
||||
|
@ -30,7 +30,7 @@ def test_is_process_alive(remote_call: RemoteCall, mocker: MockerFixture) -> Non
|
||||
request_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
assert remote_call.is_process_alive("id")
|
||||
request_mock.assert_called_once_with("GET", f"{remote_call.client.address}/api/v1/service/process/id")
|
||||
request_mock.assert_called_once_with("GET", "/api/v1/service/process/id")
|
||||
|
||||
|
||||
def test_is_process_alive_unknown(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
@ -52,7 +52,7 @@ def test_remote_update(remote_call: RemoteCall, mocker: MockerFixture) -> None:
|
||||
request_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request", return_value=response_obj)
|
||||
|
||||
assert remote_call.remote_update() == "id"
|
||||
request_mock.assert_called_once_with("POST", f"{remote_call.client.address}/api/v1/service/update", json={
|
||||
request_mock.assert_called_once_with("POST", "/api/v1/service/update", json={
|
||||
"aur": False,
|
||||
"local": False,
|
||||
"manual": True,
|
||||
|
@ -19,7 +19,6 @@ def test_login_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must generate login url correctly
|
||||
"""
|
||||
assert web_client._login_url.startswith(web_client.address)
|
||||
assert web_client._login_url.endswith("/api/v1/login")
|
||||
|
||||
|
||||
@ -27,10 +26,24 @@ def test_status_url(web_client: WebClient) -> None:
|
||||
"""
|
||||
must generate package status url correctly
|
||||
"""
|
||||
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).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
|
||||
@ -81,7 +94,8 @@ def test_login(web_client: WebClient, user: User, mocker: MockerFixture) -> None
|
||||
}
|
||||
|
||||
web_client._login(requests.Session())
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json=payload)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
|
||||
params=None, json=payload, files=None)
|
||||
|
||||
|
||||
def test_login_failed(web_client: WebClient, user: User, mocker: MockerFixture) -> None:
|
||||
@ -111,49 +125,34 @@ def test_login_skip(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
requests_mock.assert_not_called()
|
||||
|
||||
|
||||
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(f"/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_make_request(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must make HTTP request
|
||||
"""
|
||||
request_mock = mocker.patch("requests.Session.request")
|
||||
|
||||
assert web_client.make_request("GET", "url") is not None
|
||||
assert web_client.make_request("GET", "url", params=[("param", "value")]) is not None
|
||||
assert web_client.make_request("GET", "/url1") is not None
|
||||
assert web_client.make_request("GET", "/url2", params=[("param", "value")]) is not None
|
||||
|
||||
assert web_client.make_request("POST", "url") is not None
|
||||
assert web_client.make_request("POST", "url", json={"param": "value"}) is not None
|
||||
assert web_client.make_request("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", "url") is not None
|
||||
assert web_client.make_request("DELETE", "/url6") is not None
|
||||
|
||||
request_mock.assert_has_calls([
|
||||
MockCall("GET", "url", params=None, json=None),
|
||||
MockCall("GET", f"{web_client.address}/url1", params=None, json=None, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("GET", "url", params=[("param", "value")], json=None),
|
||||
MockCall("GET", f"{web_client.address}/url2", params=[("param", "value")], json=None, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", "url", params=None, json=None),
|
||||
MockCall("POST", f"{web_client.address}/url3", params=None, json=None, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("POST", "url", params=None, json={"param": "value"}),
|
||||
MockCall("POST", f"{web_client.address}/url4", params=None, json={"param": "value"}, files=None),
|
||||
MockCall().raise_for_status(),
|
||||
MockCall("DELETE", "url", params=None, json=None),
|
||||
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(),
|
||||
])
|
||||
|
||||
@ -174,7 +173,8 @@ def test_package_add(web_client: WebClient, package_ahriman: Package, mocker: Mo
|
||||
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)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
|
||||
params=None, json=payload, files=None)
|
||||
|
||||
|
||||
def test_package_add_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -230,7 +230,8 @@ def test_package_get_all(web_client: WebClient, package_ahriman: Package, mocker
|
||||
requests_mock = mocker.patch("requests.Session.request", return_value=response_obj)
|
||||
|
||||
result = web_client.package_get(None)
|
||||
requests_mock.assert_called_once_with("GET", web_client._package_url(), params=None, json=None)
|
||||
requests_mock.assert_called_once_with("GET", f"{web_client.address}{web_client._package_url()}",
|
||||
params=None, json=None, files=None)
|
||||
assert len(result) == len(response)
|
||||
assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
|
||||
|
||||
@ -263,8 +264,9 @@ def test_package_get_single(web_client: WebClient, package_ahriman: Package, moc
|
||||
requests_mock = mocker.patch("requests.Session.request", return_value=response_obj)
|
||||
|
||||
result = web_client.package_get(package_ahriman.base)
|
||||
requests_mock.assert_called_once_with("GET", web_client._package_url(package_ahriman.base),
|
||||
params=None, json=None)
|
||||
requests_mock.assert_called_once_with("GET",
|
||||
f"{web_client.address}{web_client._package_url(package_ahriman.base)}",
|
||||
params=None, json=None, files=None)
|
||||
assert len(result) == len(response)
|
||||
assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
|
||||
|
||||
@ -282,7 +284,8 @@ def test_package_logs(web_client: WebClient, log_record: logging.LogRecord, pack
|
||||
}
|
||||
|
||||
web_client.package_logs(package_ahriman.base, log_record)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json=payload)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True),
|
||||
params=None, json=payload, files=None)
|
||||
|
||||
|
||||
def test_package_logs_failed(web_client: WebClient, log_record: logging.LogRecord, package_ahriman: Package,
|
||||
@ -312,7 +315,8 @@ def test_package_remove(web_client: WebClient, package_ahriman: Package, mocker:
|
||||
requests_mock = mocker.patch("requests.Session.request")
|
||||
|
||||
web_client.package_remove(package_ahriman.base)
|
||||
requests_mock.assert_called_once_with("DELETE", pytest.helpers.anyvar(str, True), params=None, json=None)
|
||||
requests_mock.assert_called_once_with("DELETE", pytest.helpers.anyvar(str, True),
|
||||
params=None, json=None, files=None)
|
||||
|
||||
|
||||
def test_package_remove_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -341,7 +345,7 @@ def test_package_update(web_client: WebClient, package_ahriman: Package, mocker:
|
||||
web_client.package_update(package_ahriman.base, BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json={
|
||||
"status": BuildStatusEnum.Unknown.value
|
||||
})
|
||||
}, files=None)
|
||||
|
||||
|
||||
def test_package_update_failed(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
@ -373,7 +377,8 @@ def test_status_get(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
requests_mock = mocker.patch("requests.Session.request", return_value=response_obj)
|
||||
|
||||
result = web_client.status_get()
|
||||
requests_mock.assert_called_once_with("GET", web_client._status_url, params=None, json=None)
|
||||
requests_mock.assert_called_once_with("GET", f"{web_client.address}{web_client._status_url}",
|
||||
params=None, json=None, files=None)
|
||||
assert result.architecture == "x86_64"
|
||||
|
||||
|
||||
@ -402,7 +407,7 @@ def test_status_update(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
web_client.status_update(BuildStatusEnum.Unknown)
|
||||
requests_mock.assert_called_once_with("POST", pytest.helpers.anyvar(str, True), params=None, json={
|
||||
"status": BuildStatusEnum.Unknown.value
|
||||
})
|
||||
}, files=None)
|
||||
|
||||
|
||||
def test_status_update_self_failed(web_client: WebClient, mocker: MockerFixture) -> None:
|
||||
|
@ -5,6 +5,7 @@ from unittest.mock import MagicMock
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.core.upload.github import Github
|
||||
from ahriman.core.upload.remote_service import RemoteService
|
||||
from ahriman.core.upload.rsync import Rsync
|
||||
from ahriman.core.upload.s3 import S3
|
||||
|
||||
@ -45,6 +46,22 @@ def github_release() -> dict[str, Any]:
|
||||
}
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def remote_service(configuration: Configuration) -> RemoteService:
|
||||
"""
|
||||
fixture for remote service synchronization
|
||||
|
||||
Args:
|
||||
configuration(Configuration): configuration fixture
|
||||
|
||||
Returns:
|
||||
RemoteService: remote service test instance
|
||||
"""
|
||||
configuration.set_option("web", "host", "localhost")
|
||||
configuration.set_option("web", "port", "8080")
|
||||
return RemoteService("x86_64", configuration, "remote-service")
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rsync(configuration: Configuration) -> Rsync:
|
||||
"""
|
||||
|
49
tests/ahriman/core/upload/test_remote_service.py
Normal file
49
tests/ahriman/core/upload/test_remote_service.py
Normal file
@ -0,0 +1,49 @@
|
||||
import pytest
|
||||
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.core.upload.remote_service import RemoteService
|
||||
from ahriman.models.package import Package
|
||||
|
||||
|
||||
def test_package_upload(remote_service: RemoteService, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must upload package to remote host
|
||||
"""
|
||||
open_mock = mocker.patch("pathlib.Path.open")
|
||||
upload_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
filename = package_ahriman.packages[package_ahriman.base].filename
|
||||
|
||||
remote_service.sync(Path("local"), [package_ahriman])
|
||||
open_mock.assert_called_once_with("rb")
|
||||
upload_mock.assert_called_once_with("POST", "/api/v1/service/upload", files={
|
||||
"archive": (filename, pytest.helpers.anyvar(int), "application/octet-stream", {})
|
||||
})
|
||||
|
||||
|
||||
def test_package_upload_no_filename(
|
||||
remote_service: RemoteService,
|
||||
package_ahriman: Package,
|
||||
mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must skip upload if no filename set
|
||||
"""
|
||||
open_mock = mocker.patch("pathlib.Path.open")
|
||||
upload_mock = mocker.patch("ahriman.core.status.web_client.WebClient.make_request")
|
||||
package_ahriman.packages[package_ahriman.base].filename = None
|
||||
|
||||
remote_service.sync(Path("local"), [package_ahriman])
|
||||
open_mock.assert_not_called()
|
||||
upload_mock.assert_not_called()
|
||||
|
||||
|
||||
def test_sync(remote_service: RemoteService, package_ahriman: Package, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must run sync command
|
||||
"""
|
||||
upload_mock = mocker.patch("ahriman.core.upload.remote_service.RemoteService.package_upload")
|
||||
local = Path("local")
|
||||
|
||||
remote_service.sync(local, [package_ahriman])
|
||||
upload_mock.assert_called_once_with(local, package_ahriman)
|
@ -53,3 +53,15 @@ def test_upload_github(configuration: Configuration, mocker: MockerFixture) -> N
|
||||
upload_mock = mocker.patch("ahriman.core.upload.github.Github.sync")
|
||||
Upload.load("x86_64", configuration, "github").run(Path("path"), [])
|
||||
upload_mock.assert_called_once_with(Path("path"), [])
|
||||
|
||||
|
||||
def test_upload_ahriman(configuration: Configuration, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must upload via ahriman
|
||||
"""
|
||||
upload_mock = mocker.patch("ahriman.core.upload.remote_service.RemoteService.sync")
|
||||
configuration.set_option("web", "host", "localhost")
|
||||
configuration.set_option("web", "port", "8080")
|
||||
|
||||
Upload.load("x86_64", configuration, "remote-service").run(Path("path"), [])
|
||||
upload_mock.assert_called_once_with(Path("path"), [])
|
||||
|
@ -26,3 +26,5 @@ def test_from_option_valid() -> None:
|
||||
|
||||
assert ReportSettings.from_option("remote-call") == ReportSettings.RemoteCall
|
||||
assert ReportSettings.from_option("reMOte-cALL") == ReportSettings.RemoteCall
|
||||
assert ReportSettings.from_option("ahriman") == ReportSettings.RemoteCall
|
||||
assert ReportSettings.from_option("AhRiMAN") == ReportSettings.RemoteCall
|
||||
|
@ -20,3 +20,8 @@ def test_from_option_valid() -> None:
|
||||
|
||||
assert UploadSettings.from_option("github") == UploadSettings.Github
|
||||
assert UploadSettings.from_option("GitHub") == UploadSettings.Github
|
||||
|
||||
assert UploadSettings.from_option("remote-service") == UploadSettings.RemoteService
|
||||
assert UploadSettings.from_option("Remote-Service") == UploadSettings.RemoteService
|
||||
assert UploadSettings.from_option("ahriman") == UploadSettings.RemoteService
|
||||
assert UploadSettings.from_option("AhRiMAN") == UploadSettings.RemoteService
|
||||
|
1
tests/ahriman/web/schemas/test_file_schema.py
Normal file
1
tests/ahriman/web/schemas/test_file_schema.py
Normal file
@ -0,0 +1 @@
|
||||
# schema testing goes in view class tests
|
@ -33,7 +33,7 @@ async def test_get(client: TestClient, mocker: MockerFixture) -> None:
|
||||
assert not response_schema.validate(json)
|
||||
|
||||
|
||||
async def test_post_empty(client: TestClient, mocker: MockerFixture) -> None:
|
||||
async def test_get_empty(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call raise 404 on unknown process
|
||||
"""
|
||||
|
@ -17,7 +17,7 @@ async def test_get_permission() -> None:
|
||||
assert await UpdateView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
async def test_post_update(client: TestClient, mocker: MockerFixture) -> None:
|
||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly for alias
|
||||
"""
|
||||
|
91
tests/ahriman/web/views/service/test_views_service_upload.py
Normal file
91
tests/ahriman/web/views/service/test_views_service_upload.py
Normal file
@ -0,0 +1,91 @@
|
||||
import pytest
|
||||
|
||||
from aiohttp import FormData
|
||||
from aiohttp.test_utils import TestClient
|
||||
from io import BytesIO
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.service.upload import UploadView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
"""
|
||||
must return correct permission for the request
|
||||
"""
|
||||
for method in ("POST",):
|
||||
request = pytest.helpers.request("", "", method)
|
||||
assert await UploadView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly for alias
|
||||
"""
|
||||
open_mock = mocker.patch("pathlib.Path.open")
|
||||
# no content validation here because it has invalid schema
|
||||
|
||||
data = FormData()
|
||||
data.add_field("archive", BytesIO(b"content"), filename="filename", content_type="application/octet-stream")
|
||||
|
||||
response = await client.post("/api/v1/service/upload", data=data)
|
||||
assert response.ok
|
||||
open_mock.assert_called_once_with("wb")
|
||||
|
||||
|
||||
async def test_post_not_multipart(client: TestClient) -> None:
|
||||
"""
|
||||
must return 400 on invalid payload
|
||||
"""
|
||||
response = await client.post("/api/v1/service/upload")
|
||||
assert response.status == 400
|
||||
|
||||
|
||||
async def test_post_not_bodypart(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return 400 on invalid iterator in multipart
|
||||
"""
|
||||
mocker.patch("aiohttp.MultipartReader.next", return_value=42) # surprise, motherfucker
|
||||
data = FormData()
|
||||
data.add_field("archive", BytesIO(b"content"), filename="filename", content_type="application/octet-stream")
|
||||
|
||||
response = await client.post("/api/v1/service/upload", data=data)
|
||||
assert response.status == 400
|
||||
|
||||
|
||||
async def test_post_not_archive(client: TestClient) -> None:
|
||||
"""
|
||||
must return 400 on invalid multipart key
|
||||
"""
|
||||
data = FormData()
|
||||
data.add_field("random", BytesIO(b"content"), filename="filename", content_type="application/octet-stream")
|
||||
|
||||
response = await client.post("/api/v1/service/upload", data=data)
|
||||
assert response.status == 400
|
||||
|
||||
|
||||
async def test_post_no_filename(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must return 400 if filename is not set
|
||||
"""
|
||||
mocker.patch("aiohttp.BodyPartReader.filename", return_value=None)
|
||||
data = FormData()
|
||||
data.add_field("random", BytesIO(b"content"), filename="filename", content_type="application/octet-stream")
|
||||
|
||||
response = await client.post("/api/v1/service/upload", data=data)
|
||||
assert response.status == 400
|
||||
|
||||
|
||||
async def test_post_filename_invalid(client: TestClient) -> None:
|
||||
"""
|
||||
must return 400 if filename is invalid
|
||||
"""
|
||||
data = FormData()
|
||||
data.add_field("archive", BytesIO(b"content"), filename="..", content_type="application/octet-stream")
|
||||
response = await client.post("/api/v1/service/upload", data=data)
|
||||
assert response.status == 400
|
||||
|
||||
data = FormData()
|
||||
data.add_field("archive", BytesIO(b"content"), filename="", content_type="application/octet-stream")
|
||||
response = await client.post("/api/v1/service/upload", data=data)
|
||||
assert response.status == 400
|
@ -106,6 +106,8 @@ password =
|
||||
repository = ahriman
|
||||
username = arcan1s
|
||||
|
||||
[remote-service]
|
||||
|
||||
[web]
|
||||
debug = no
|
||||
debug_check_host = no
|
||||
|
Reference in New Issue
Block a user