mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-04 07:43:42 +00:00 
			
		
		
		
	Add web status route (#13)
* add status route * typed status and get status at the start of application
This commit is contained in:
		@ -5,9 +5,11 @@ from pathlib import Path
 | 
			
		||||
from pytest_mock import MockerFixture
 | 
			
		||||
from unittest import mock
 | 
			
		||||
 | 
			
		||||
from ahriman import version
 | 
			
		||||
from ahriman.application.lock import Lock
 | 
			
		||||
from ahriman.core.exceptions import DuplicateRun, UnsafeRun
 | 
			
		||||
from ahriman.models.build_status import BuildStatusEnum
 | 
			
		||||
from ahriman.models.internal_status import InternalStatus
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_enter(lock: Lock, mocker: MockerFixture) -> None:
 | 
			
		||||
@ -15,6 +17,7 @@ def test_enter(lock: Lock, mocker: MockerFixture) -> None:
 | 
			
		||||
    must process with context manager
 | 
			
		||||
    """
 | 
			
		||||
    check_user_mock = mocker.patch("ahriman.application.lock.Lock.check_user")
 | 
			
		||||
    check_version_mock = mocker.patch("ahriman.application.lock.Lock.check_version")
 | 
			
		||||
    clear_mock = mocker.patch("ahriman.application.lock.Lock.clear")
 | 
			
		||||
    create_mock = mocker.patch("ahriman.application.lock.Lock.create")
 | 
			
		||||
    update_status_mock = mocker.patch("ahriman.core.status.client.Client.update_self")
 | 
			
		||||
@ -24,6 +27,7 @@ def test_enter(lock: Lock, mocker: MockerFixture) -> None:
 | 
			
		||||
    check_user_mock.assert_called_once()
 | 
			
		||||
    clear_mock.assert_called_once()
 | 
			
		||||
    create_mock.assert_called_once()
 | 
			
		||||
    check_version_mock.assert_called_once()
 | 
			
		||||
    update_status_mock.assert_has_calls([
 | 
			
		||||
        mock.call(BuildStatusEnum.Building),
 | 
			
		||||
        mock.call(BuildStatusEnum.Success)
 | 
			
		||||
@ -48,6 +52,30 @@ def test_exit_with_exception(lock: Lock, mocker: MockerFixture) -> None:
 | 
			
		||||
    ])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_check_version(lock: Lock, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must check version correctly
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("ahriman.core.status.client.Client.get_internal",
 | 
			
		||||
                 return_value=InternalStatus(version=version.__version__))
 | 
			
		||||
    logging_mock = mocker.patch("logging.Logger.warning")
 | 
			
		||||
 | 
			
		||||
    lock.check_version()
 | 
			
		||||
    logging_mock.assert_not_called()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_check_version_mismatch(lock: Lock, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must check version correctly
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("ahriman.core.status.client.Client.get_internal",
 | 
			
		||||
                 return_value=InternalStatus(version="version"))
 | 
			
		||||
    logging_mock = mocker.patch("logging.Logger.warning")
 | 
			
		||||
 | 
			
		||||
    lock.check_version()
 | 
			
		||||
    logging_mock.assert_called_once()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_check_user(lock: Lock, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must check user correctly
 | 
			
		||||
 | 
			
		||||
@ -4,6 +4,7 @@ from ahriman.core.configuration import Configuration
 | 
			
		||||
from ahriman.core.status.client import Client
 | 
			
		||||
from ahriman.core.status.web_client import WebClient
 | 
			
		||||
from ahriman.models.build_status import BuildStatusEnum
 | 
			
		||||
from ahriman.models.internal_status import InternalStatus
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -38,6 +39,13 @@ def test_get(client: Client, package_ahriman: Package) -> None:
 | 
			
		||||
    assert client.get(None) == []
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_internal(client: Client) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return dummy status for web service
 | 
			
		||||
    """
 | 
			
		||||
    assert client.get_internal() == InternalStatus()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_self(client: Client) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return unknown status for service
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ from requests import Response
 | 
			
		||||
 | 
			
		||||
from ahriman.core.status.web_client import WebClient
 | 
			
		||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
 | 
			
		||||
from ahriman.models.internal_status import InternalStatus
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -26,6 +27,14 @@ def test_package_url(web_client: WebClient, package_ahriman: Package) -> None:
 | 
			
		||||
    assert web_client._package_url(package_ahriman.base).endswith(f"/api/v1/packages/{package_ahriman.base}")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_status_url(web_client: WebClient) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate service status url correctly
 | 
			
		||||
    """
 | 
			
		||||
    assert web_client._status_url().startswith(f"http://{web_client.host}:{web_client.port}")
 | 
			
		||||
    assert web_client._status_url().endswith("/api/v1/status")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_add(web_client: WebClient, package_ahriman: Package, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must process package addition
 | 
			
		||||
@ -103,6 +112,37 @@ def test_get_single(web_client: WebClient, package_ahriman: Package, mocker: Moc
 | 
			
		||||
    assert (package_ahriman, BuildStatusEnum.Unknown) in [(package, status.status) for package, status in result]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_internal(web_client: WebClient, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return web service status
 | 
			
		||||
    """
 | 
			
		||||
    response_obj = Response()
 | 
			
		||||
    response_obj._content = json.dumps(InternalStatus(architecture="x86_64").view()).encode("utf8")
 | 
			
		||||
    response_obj.status_code = 200
 | 
			
		||||
 | 
			
		||||
    requests_mock = mocker.patch("requests.get", return_value=response_obj)
 | 
			
		||||
 | 
			
		||||
    result = web_client.get_internal()
 | 
			
		||||
    requests_mock.assert_called_once()
 | 
			
		||||
    assert result.architecture == "x86_64"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_internal_failed(web_client: WebClient, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must suppress any exception happened during web service status getting
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("requests.get", side_effect=Exception())
 | 
			
		||||
    assert web_client.get_internal() == InternalStatus()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_internal_failed_http_error(web_client: WebClient, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must suppress any exception happened during web service status getting
 | 
			
		||||
    """
 | 
			
		||||
    mocker.patch("requests.get", side_effect=requests.exceptions.HTTPError())
 | 
			
		||||
    assert web_client.get_internal() == InternalStatus()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_get_self(web_client: WebClient, mocker: MockerFixture) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return service status
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,10 @@ import pytest
 | 
			
		||||
 | 
			
		||||
from unittest.mock import MagicMock, PropertyMock
 | 
			
		||||
 | 
			
		||||
from ahriman import version
 | 
			
		||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
 | 
			
		||||
from ahriman.models.counters import Counters
 | 
			
		||||
from ahriman.models.internal_status import InternalStatus
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
from ahriman.models.package_description import PackageDescription
 | 
			
		||||
 | 
			
		||||
@ -12,6 +15,24 @@ def build_status_failed() -> BuildStatus:
 | 
			
		||||
    return BuildStatus(BuildStatusEnum.Failed, 42)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def counters() -> Counters:
 | 
			
		||||
    return Counters(total=10,
 | 
			
		||||
                    unknown=1,
 | 
			
		||||
                    pending=2,
 | 
			
		||||
                    building=3,
 | 
			
		||||
                    failed=4,
 | 
			
		||||
                    success=0)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def internal_status(counters: Counters) -> InternalStatus:
 | 
			
		||||
    return InternalStatus(architecture="x86_64",
 | 
			
		||||
                          packages=counters,
 | 
			
		||||
                          version=version.__version__,
 | 
			
		||||
                          repository="aur-clone")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@pytest.fixture
 | 
			
		||||
def package_tpacpi_bat_git() -> Package:
 | 
			
		||||
    return Package(
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										31
									
								
								tests/ahriman/models/test_counters.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								tests/ahriman/models/test_counters.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
from dataclasses import asdict
 | 
			
		||||
 | 
			
		||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
 | 
			
		||||
from ahriman.models.counters import Counters
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_counters_from_json_view(counters: Counters) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must construct same object from json
 | 
			
		||||
    """
 | 
			
		||||
    assert Counters.from_json(asdict(counters)) == counters
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_counters_from_packages(package_ahriman: Package, package_python_schedule: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must construct object from list of packages with their statuses
 | 
			
		||||
    """
 | 
			
		||||
    payload = [
 | 
			
		||||
        (package_ahriman, BuildStatus(status=BuildStatusEnum.Success)),
 | 
			
		||||
        (package_python_schedule, BuildStatus(status=BuildStatusEnum.Failed)),
 | 
			
		||||
    ]
 | 
			
		||||
 | 
			
		||||
    counters = Counters.from_packages(payload)
 | 
			
		||||
    assert counters.total == 2
 | 
			
		||||
    assert counters.success == 1
 | 
			
		||||
    assert counters.failed == 1
 | 
			
		||||
 | 
			
		||||
    json = asdict(counters)
 | 
			
		||||
    total = json.pop("total")
 | 
			
		||||
    assert total == sum(i for i in json.values())
 | 
			
		||||
							
								
								
									
										8
									
								
								tests/ahriman/models/test_internal_status.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								tests/ahriman/models/test_internal_status.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
from ahriman.models.internal_status import InternalStatus
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_internal_status_from_json_view(internal_status: InternalStatus) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must construct same object from json
 | 
			
		||||
    """
 | 
			
		||||
    assert InternalStatus.from_json(internal_status.view()) == internal_status
 | 
			
		||||
							
								
								
									
										22
									
								
								tests/ahriman/web/views/test_view_status.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								tests/ahriman/web/views/test_view_status.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,22 @@
 | 
			
		||||
from pytest_aiohttp import TestClient
 | 
			
		||||
 | 
			
		||||
import ahriman.version as version
 | 
			
		||||
 | 
			
		||||
from ahriman.models.build_status import BuildStatusEnum
 | 
			
		||||
from ahriman.models.package import Package
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def test_get(client: TestClient, package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must generate web service status correctly)
 | 
			
		||||
    """
 | 
			
		||||
    await client.post(f"/api/v1/packages/{package_ahriman.base}",
 | 
			
		||||
                      json={"status": BuildStatusEnum.Success.value, "package": package_ahriman.view()})
 | 
			
		||||
 | 
			
		||||
    response = await client.get("/api/v1/status")
 | 
			
		||||
    assert response.status == 200
 | 
			
		||||
 | 
			
		||||
    json = await response.json()
 | 
			
		||||
    assert json["version"] == version.__version__
 | 
			
		||||
    assert json["packages"]
 | 
			
		||||
    assert json["packages"]["total"] == 1
 | 
			
		||||
		Reference in New Issue
	
	Block a user