mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-06-28 06:41:43 +00:00
feat: load http views dynamically (#113)
This commit is contained in:
@ -7,7 +7,7 @@ from collections.abc import Awaitable, Callable
|
||||
from marshmallow import Schema
|
||||
from pytest_mock import MockerFixture
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
from unittest.mock import MagicMock, Mock
|
||||
|
||||
import ahriman.core.auth.helpers
|
||||
|
||||
@ -20,6 +20,26 @@ from ahriman.models.user import User
|
||||
from ahriman.web.web import setup_service
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def patch_view(application: Application, attribute: str, mock: Mock) -> Mock:
|
||||
"""
|
||||
patch given attribute in views. This method is required because of dynamic load
|
||||
|
||||
Args:
|
||||
application(Application): application fixture
|
||||
attribute(str): attribute name to patch
|
||||
mock(Mock): mock object
|
||||
|
||||
Returns:
|
||||
Mock: mock set to object
|
||||
"""
|
||||
for route in application.router.routes():
|
||||
if hasattr(route.handler, attribute):
|
||||
setattr(route.handler, attribute, mock)
|
||||
|
||||
return mock
|
||||
|
||||
|
||||
@pytest.helpers.register
|
||||
def request(application: Application, path: str, method: str, params: Any = None, json: Any = None, data: Any = None,
|
||||
extra: dict[str, Any] | None = None, resource: Resource | None = None) -> MagicMock:
|
||||
|
@ -1,7 +1,73 @@
|
||||
import pytest
|
||||
|
||||
from aiohttp.web import Application
|
||||
from importlib.machinery import ModuleSpec
|
||||
from pathlib import Path
|
||||
from pytest_mock import MockerFixture
|
||||
from types import ModuleType
|
||||
|
||||
from ahriman.core.configuration import Configuration
|
||||
from ahriman.web.routes import setup_routes
|
||||
from ahriman.core.util import walk
|
||||
from ahriman.web.routes import _dynamic_routes, _module, _modules, setup_routes
|
||||
|
||||
|
||||
def test_dynamic_routes(resource_path_root: Path) -> None:
|
||||
"""
|
||||
must return all available routes
|
||||
"""
|
||||
views_root = resource_path_root / ".." / ".." / "src" / "ahriman" / "web" / "views"
|
||||
expected_views = [
|
||||
file
|
||||
for file in walk(views_root)
|
||||
if file.suffix == ".py" and file.name not in ("__init__.py", "base.py")
|
||||
]
|
||||
|
||||
routes = _dynamic_routes(views_root)
|
||||
assert all(isinstance(view, type) for view in routes.values())
|
||||
assert len(set(routes.values())) == len(expected_views)
|
||||
|
||||
|
||||
def test_module(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must load module
|
||||
"""
|
||||
exec_mock = mocker.patch("importlib.machinery.SourceFileLoader.exec_module")
|
||||
module_info = next(_modules(Path(__file__).parent))
|
||||
|
||||
module = _module(module_info)
|
||||
assert isinstance(module, ModuleType)
|
||||
exec_mock.assert_called_once_with(pytest.helpers.anyvar(int))
|
||||
|
||||
|
||||
def test_module_no_spec(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise ValueError if spec is not available
|
||||
"""
|
||||
mocker.patch("importlib.machinery.FileFinder.find_spec", return_value=None)
|
||||
module_info = next(_modules(Path(__file__).parent))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_module(module_info)
|
||||
|
||||
|
||||
def test_module_no_loader(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must raise ValueError if loader is not available
|
||||
"""
|
||||
mocker.patch("importlib.machinery.FileFinder.find_spec", return_value=ModuleSpec("name", None))
|
||||
module_info = next(_modules(Path(__file__).parent))
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
_module(module_info)
|
||||
|
||||
|
||||
def test_modules() -> None:
|
||||
"""
|
||||
must load modules
|
||||
"""
|
||||
modules = list(_modules(Path(__file__).parent.parent))
|
||||
assert modules
|
||||
assert all(not module.ispkg for module in modules)
|
||||
|
||||
|
||||
def test_setup_routes(application: Application, configuration: Configuration) -> None:
|
||||
|
@ -10,6 +10,13 @@ from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert BaseView.ROUTES == []
|
||||
|
||||
|
||||
def test_configuration(base: BaseView) -> None:
|
||||
"""
|
||||
must return configuration
|
||||
|
@ -15,6 +15,13 @@ async def test_get_permission() -> None:
|
||||
assert await IndexView.get_permission(request) == UserAccess.Unauthorized
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert IndexView.ROUTES == ["/", "/index.html"]
|
||||
|
||||
|
||||
async def test_get(client_with_auth: TestClient) -> None:
|
||||
"""
|
||||
must generate status page correctly (/)
|
||||
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import AddView
|
||||
from ahriman.web.views.v1.service.add import AddView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -17,6 +17,13 @@ async def test_get_permission() -> None:
|
||||
assert await AddView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert AddView.ROUTES == ["/api/v1/service/add"]
|
||||
|
||||
|
||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly
|
||||
|
@ -4,7 +4,7 @@ from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import PGPView
|
||||
from ahriman.web.views.v1.service.pgp import PGPView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -19,6 +19,13 @@ async def test_get_permission() -> None:
|
||||
assert await PGPView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert PGPView.ROUTES == ["/api/v1/service/pgp"]
|
||||
|
||||
|
||||
async def test_get(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must retrieve key from the keyserver
|
||||
|
@ -4,7 +4,7 @@ from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import ProcessView
|
||||
from ahriman.web.views.v1.service.process import ProcessView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -16,6 +16,13 @@ async def test_get_permission() -> None:
|
||||
assert await ProcessView.get_permission(request) == UserAccess.Reporter
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert ProcessView.ROUTES == ["/api/v1/service/process/{process_id}"]
|
||||
|
||||
|
||||
async def test_get(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly
|
||||
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import RebuildView
|
||||
from ahriman.web.views.v1.service.rebuild import RebuildView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -17,6 +17,13 @@ async def test_get_permission() -> None:
|
||||
assert await RebuildView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert RebuildView.ROUTES == ["/api/v1/service/rebuild"]
|
||||
|
||||
|
||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly
|
||||
|
@ -4,7 +4,7 @@ from aiohttp.test_utils import TestClient
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import RemoveView
|
||||
from ahriman.web.views.v1.service.remove import RemoveView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -16,6 +16,13 @@ async def test_get_permission() -> None:
|
||||
assert await RemoveView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert RemoveView.ROUTES == ["/api/v1/service/remove"]
|
||||
|
||||
|
||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly
|
||||
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import RequestView
|
||||
from ahriman.web.views.v1.service.request import RequestView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -17,6 +17,13 @@ async def test_get_permission() -> None:
|
||||
assert await RequestView.get_permission(request) == UserAccess.Reporter
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert RequestView.ROUTES == ["/api/v1/service/request"]
|
||||
|
||||
|
||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly
|
||||
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.aur_package import AURPackage
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import SearchView
|
||||
from ahriman.web.views.v1.service.search import SearchView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -17,6 +17,13 @@ async def test_get_permission() -> None:
|
||||
assert await SearchView.get_permission(request) == UserAccess.Reporter
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert SearchView.ROUTES == ["/api/v1/service/search"]
|
||||
|
||||
|
||||
async def test_get(client: TestClient, aur_package_ahriman: AURPackage, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call get request correctly
|
||||
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import UpdateView
|
||||
from ahriman.web.views.v1.service.update import UpdateView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -17,6 +17,13 @@ async def test_get_permission() -> None:
|
||||
assert await UpdateView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert UpdateView.ROUTES == ["/api/v1/service/update"]
|
||||
|
||||
|
||||
async def test_post(client: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must call post request correctly for alias
|
||||
|
@ -10,7 +10,7 @@ from unittest.mock import AsyncMock, MagicMock, call as MockCall
|
||||
|
||||
from ahriman.models.repository_paths import RepositoryPaths
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import UploadView
|
||||
from ahriman.web.views.v1.service.upload import UploadView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -22,6 +22,13 @@ async def test_get_permission() -> None:
|
||||
assert await UploadView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert UploadView.ROUTES == ["/api/v1/service/upload"]
|
||||
|
||||
|
||||
async def test_save_file(mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must correctly save file
|
||||
@ -84,8 +91,8 @@ async def test_post(client: TestClient, repository_paths: RepositoryPaths, mocke
|
||||
must process file upload via http
|
||||
"""
|
||||
local = Path("local")
|
||||
save_mock = mocker.patch("ahriman.web.views.v1.UploadView.save_file",
|
||||
side_effect=AsyncMock(return_value=("filename", local / ".filename")))
|
||||
save_mock = pytest.helpers.patch_view(client.app, "save_file",
|
||||
AsyncMock(return_value=("filename", local / ".filename")))
|
||||
rename_mock = mocker.patch("pathlib.Path.rename")
|
||||
# no content validation here because it has invalid schema
|
||||
|
||||
@ -103,11 +110,11 @@ async def test_post_with_sig(client: TestClient, repository_paths: RepositoryPat
|
||||
must process file upload with signature via http
|
||||
"""
|
||||
local = Path("local")
|
||||
save_mock = mocker.patch("ahriman.web.views.v1.UploadView.save_file",
|
||||
side_effect=AsyncMock(side_effect=[
|
||||
("filename", local / ".filename"),
|
||||
("filename.sig", local / ".filename.sig"),
|
||||
]))
|
||||
save_mock = pytest.helpers.patch_view(client.app, "save_file",
|
||||
AsyncMock(side_effect=[
|
||||
("filename", local / ".filename"),
|
||||
("filename.sig", local / ".filename.sig"),
|
||||
]))
|
||||
rename_mock = mocker.patch("pathlib.Path.rename")
|
||||
# no content validation here because it has invalid schema
|
||||
|
||||
|
@ -5,7 +5,7 @@ from aiohttp.test_utils import TestClient
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import LogsView
|
||||
from ahriman.web.views.v1.status.logs import LogsView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -20,6 +20,13 @@ async def test_get_permission() -> None:
|
||||
assert await LogsView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert LogsView.ROUTES == ["/api/v1/packages/{package}/logs"]
|
||||
|
||||
|
||||
async def test_delete(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must delete logs for package
|
||||
|
@ -5,7 +5,7 @@ from aiohttp.test_utils import TestClient
|
||||
from ahriman.models.build_status import BuildStatus, BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import PackageView
|
||||
from ahriman.web.views.v1.status.package import PackageView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -20,6 +20,13 @@ async def test_get_permission() -> None:
|
||||
assert await PackageView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert PackageView.ROUTES == ["/api/v1/packages/{package}"]
|
||||
|
||||
|
||||
async def test_delete(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must delete single base
|
||||
|
@ -6,7 +6,7 @@ from pytest_mock import MockerFixture
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import PackagesView
|
||||
from ahriman.web.views.v1.status.packages import (PackagesView)
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -21,6 +21,13 @@ async def test_get_permission() -> None:
|
||||
assert await PackagesView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert PackagesView.ROUTES == ["/api/v1/packages"]
|
||||
|
||||
|
||||
async def test_get(client: TestClient, package_ahriman: Package, package_python_schedule: Package) -> None:
|
||||
"""
|
||||
must return status for all packages
|
||||
|
@ -8,7 +8,7 @@ from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.internal_status import InternalStatus
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import StatusView
|
||||
from ahriman.web.views.v1.status.status import StatusView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -23,6 +23,13 @@ async def test_get_permission() -> None:
|
||||
assert await StatusView.get_permission(request) == UserAccess.Full
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert StatusView.ROUTES == ["/api/v1/status"]
|
||||
|
||||
|
||||
async def test_get(client: TestClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must generate web service status correctly
|
||||
|
@ -5,7 +5,7 @@ from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.user import User
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import LoginView
|
||||
from ahriman.web.views.v1.user.login import LoginView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -17,6 +17,13 @@ async def test_get_permission() -> None:
|
||||
assert await LoginView.get_permission(request) == UserAccess.Unauthorized
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert LoginView.ROUTES == ["/api/v1/login"]
|
||||
|
||||
|
||||
async def test_get_default_validator(client_with_auth: TestClient) -> None:
|
||||
"""
|
||||
must return 405 in case if no OAuth enabled
|
||||
|
@ -5,7 +5,7 @@ from aiohttp.web import HTTPUnauthorized
|
||||
from pytest_mock import MockerFixture
|
||||
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v1 import LogoutView
|
||||
from ahriman.web.views.v1.user.logout import LogoutView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -17,6 +17,13 @@ async def test_get_permission() -> None:
|
||||
assert await LogoutView.get_permission(request) == UserAccess.Unauthorized
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert LogoutView.ROUTES == ["/api/v1/logout"]
|
||||
|
||||
|
||||
async def test_post(client_with_auth: TestClient, mocker: MockerFixture) -> None:
|
||||
"""
|
||||
must log out user correctly
|
||||
|
@ -5,7 +5,7 @@ from aiohttp.test_utils import TestClient
|
||||
from ahriman.models.build_status import BuildStatusEnum
|
||||
from ahriman.models.package import Package
|
||||
from ahriman.models.user_access import UserAccess
|
||||
from ahriman.web.views.v2 import LogsView
|
||||
from ahriman.web.views.v2.status.logs import LogsView
|
||||
|
||||
|
||||
async def test_get_permission() -> None:
|
||||
@ -17,6 +17,13 @@ async def test_get_permission() -> None:
|
||||
assert await LogsView.get_permission(request) == UserAccess.Reporter
|
||||
|
||||
|
||||
def test_routes() -> None:
|
||||
"""
|
||||
must return correct routes
|
||||
"""
|
||||
assert LogsView.ROUTES == ["/api/v2/packages/{package}/logs"]
|
||||
|
||||
|
||||
async def test_get(client: TestClient, package_ahriman: Package) -> None:
|
||||
"""
|
||||
must get logs for package
|
||||
|
Reference in New Issue
Block a user