mirror of
https://github.com/arcan1s/ahriman.git
synced 2025-04-24 15:27:17 +00:00
load http views dynamically
This commit is contained in:
parent
d5f4fc9b86
commit
2eeadfe879
12
docs/faq.rst
12
docs/faq.rst
@ -1303,6 +1303,18 @@ It is possible to customize html templates. In order to do so, create files some
|
||||
|
||||
In addition, default html templates supports style customization out-of-box. In order to customize style, just put file named ``user-style.jinja2`` to the templates directory.
|
||||
|
||||
Web API extension
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The application loads web views dynamically, so it is possible relatively easy extend its API. In order to do so:
|
||||
|
||||
#. Create view class which is derived from ``ahriman.web.views.base.BaseView`` class.
|
||||
#. Create implementation for this class.
|
||||
#. Put file into ``ahriman.web.views`` package.
|
||||
#. Restart application.
|
||||
|
||||
For more details about implementation and possibilities, kindly refer to module documentation and source code and `aiohttp documentation <https://docs.aiohttp.org/en/stable/>`_.
|
||||
|
||||
I did not find my question
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -92,6 +92,7 @@ class Migrations(LazyLogging):
|
||||
list[Migration]: list of found migrations
|
||||
"""
|
||||
migrations: list[Migration] = []
|
||||
|
||||
package_dir = Path(__file__).resolve().parent
|
||||
modules = [module_name for (_, module_name, _) in iter_modules([str(package_dir)])]
|
||||
|
||||
|
@ -17,18 +17,90 @@
|
||||
# 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 aiohttp.web import Application
|
||||
from aiohttp.web import Application, View
|
||||
from collections.abc import Generator
|
||||
from importlib.machinery import SourceFileLoader
|
||||
from pathlib import Path
|
||||
from pkgutil import ModuleInfo, iter_modules
|
||||
from types import ModuleType
|
||||
from typing import Any, Type, TypeGuard
|
||||
|
||||
from ahriman.web.views.api.docs import DocsView
|
||||
from ahriman.web.views.api.swagger import SwaggerView
|
||||
from ahriman.web.views.index import IndexView
|
||||
from ahriman.web.views import v1, v2
|
||||
from ahriman.web.views.base import BaseView
|
||||
|
||||
|
||||
__all__ = ["setup_routes"]
|
||||
|
||||
|
||||
def _dynamic_routes(module_root: Path) -> dict[str, Type[View]]:
|
||||
"""
|
||||
extract dynamic routes based on views
|
||||
|
||||
Args:
|
||||
module_root(Path): root module path with views
|
||||
|
||||
Returns:
|
||||
dict[str, Type[View]]: map of the route to its view
|
||||
"""
|
||||
def is_base_view(clz: Any) -> TypeGuard[Type[BaseView]]:
|
||||
return isinstance(clz, type) and issubclass(clz, BaseView)
|
||||
|
||||
routes: dict[str, Type[View]] = {}
|
||||
for module_info in _modules(module_root):
|
||||
module = _module(module_info)
|
||||
|
||||
for attribute_name in dir(module):
|
||||
view = getattr(module, attribute_name)
|
||||
if not is_base_view(view):
|
||||
continue
|
||||
routes.update([(route, view) for route in view.ROUTES])
|
||||
|
||||
return routes
|
||||
|
||||
|
||||
def _module(module_info: ModuleInfo) -> ModuleType:
|
||||
"""
|
||||
load module from its info
|
||||
|
||||
Args:
|
||||
module_info(ModuleInfo): module info descriptor
|
||||
|
||||
Returns:
|
||||
ModuleType: loaded module
|
||||
|
||||
Raises:
|
||||
ValueError: if loader is not an instance of ``SourceFileLoader``
|
||||
"""
|
||||
module_spec = module_info.module_finder.find_spec(module_info.name, None)
|
||||
if module_spec is None:
|
||||
raise ValueError(f"Module specification of {module_info.name} is empty")
|
||||
|
||||
loader = module_spec.loader
|
||||
if not isinstance(loader, SourceFileLoader):
|
||||
raise ValueError(f"Module {module_info.name} loader is not an instance of SourceFileLoader")
|
||||
|
||||
module = ModuleType(loader.name)
|
||||
loader.exec_module(module)
|
||||
|
||||
return module
|
||||
|
||||
|
||||
def _modules(module_root: Path) -> Generator[ModuleInfo, None, None]:
|
||||
"""
|
||||
extract available modules from package
|
||||
|
||||
Args:
|
||||
module_root(Path): module root path
|
||||
|
||||
Yields:
|
||||
ModuleInfo: module information each available module
|
||||
"""
|
||||
for module_info in iter_modules([str(module_root)]):
|
||||
if module_info.ispkg:
|
||||
yield from _modules(module_root / module_info.name)
|
||||
else:
|
||||
yield module_info
|
||||
|
||||
|
||||
def setup_routes(application: Application, static_path: Path) -> None:
|
||||
"""
|
||||
setup all defined routes
|
||||
@ -37,30 +109,8 @@ def setup_routes(application: Application, static_path: Path) -> None:
|
||||
application(Application): web application instance
|
||||
static_path(Path): path to static files directory
|
||||
"""
|
||||
application.router.add_view("/", IndexView)
|
||||
application.router.add_view("/index.html", IndexView)
|
||||
|
||||
application.router.add_view("/api-docs", DocsView)
|
||||
application.router.add_view("/api-docs/swagger.json", SwaggerView)
|
||||
|
||||
application.router.add_static("/static", static_path, follow_symlinks=True)
|
||||
|
||||
application.router.add_view("/api/v1/service/add", v1.AddView)
|
||||
application.router.add_view("/api/v1/service/pgp", v1.PGPView)
|
||||
application.router.add_view("/api/v1/service/rebuild", v1.RebuildView)
|
||||
application.router.add_view("/api/v1/service/process/{process_id}", v1.ProcessView)
|
||||
application.router.add_view("/api/v1/service/remove", v1.RemoveView)
|
||||
application.router.add_view("/api/v1/service/request", v1.RequestView)
|
||||
application.router.add_view("/api/v1/service/search", v1.SearchView)
|
||||
application.router.add_view("/api/v1/service/update", v1.UpdateView)
|
||||
application.router.add_view("/api/v1/service/upload", v1.UploadView)
|
||||
|
||||
application.router.add_view("/api/v1/packages", v1.PackagesView)
|
||||
application.router.add_view("/api/v1/packages/{package}", v1.PackageView)
|
||||
application.router.add_view("/api/v1/packages/{package}/logs", v1.LogsView)
|
||||
application.router.add_view("/api/v2/packages/{package}/logs", v2.LogsView)
|
||||
|
||||
application.router.add_view("/api/v1/status", v1.StatusView)
|
||||
|
||||
application.router.add_view("/api/v1/login", v1.LoginView)
|
||||
application.router.add_view("/api/v1/logout", v1.LogoutView)
|
||||
views = Path(__file__).parent / "views"
|
||||
for route, view in _dynamic_routes(views).items():
|
||||
application.router.add_view(route, view)
|
||||
|
@ -34,6 +34,7 @@ class DocsView(BaseView):
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Unauthorized
|
||||
ROUTES = ["/api-docs"]
|
||||
|
||||
@aiohttp_jinja2.template("api.jinja2")
|
||||
async def get(self) -> dict[str, Any]:
|
||||
|
@ -34,6 +34,7 @@ class SwaggerView(BaseView):
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Unauthorized
|
||||
ROUTES = ["/api-docs/swagger.json"]
|
||||
|
||||
async def get(self) -> Response:
|
||||
"""
|
||||
|
@ -38,9 +38,11 @@ class BaseView(View, CorsViewMixin):
|
||||
|
||||
Attributes:
|
||||
OPTIONS_PERMISSION(UserAccess): (class attribute) options permissions of self
|
||||
ROUTES(list[str]): (class attribute) list of supported routes
|
||||
"""
|
||||
|
||||
OPTIONS_PERMISSION = UserAccess.Unauthorized
|
||||
ROUTES: list[str] = []
|
||||
|
||||
@property
|
||||
def configuration(self) -> Configuration:
|
||||
|
@ -44,6 +44,7 @@ class IndexView(BaseView):
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Unauthorized
|
||||
ROUTES = ["/", "/index.html"]
|
||||
|
||||
@aiohttp_jinja2.template("build-status.jinja2")
|
||||
async def get(self) -> dict[str, Any]:
|
||||
|
@ -17,20 +17,3 @@
|
||||
# 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.web.views.v1.service.add import AddView
|
||||
from ahriman.web.views.v1.service.pgp import PGPView
|
||||
from ahriman.web.views.v1.service.process import ProcessView
|
||||
from ahriman.web.views.v1.service.rebuild import RebuildView
|
||||
from ahriman.web.views.v1.service.remove import RemoveView
|
||||
from ahriman.web.views.v1.service.request import RequestView
|
||||
from ahriman.web.views.v1.service.search import SearchView
|
||||
from ahriman.web.views.v1.service.update import UpdateView
|
||||
from ahriman.web.views.v1.service.upload import UploadView
|
||||
|
||||
from ahriman.web.views.v1.status.logs import LogsView
|
||||
from ahriman.web.views.v1.status.package import PackageView
|
||||
from ahriman.web.views.v1.status.packages import PackagesView
|
||||
from ahriman.web.views.v1.status.status import StatusView
|
||||
|
||||
from ahriman.web.views.v1.user.login import LoginView
|
||||
from ahriman.web.views.v1.user.logout import LogoutView
|
||||
|
@ -35,6 +35,7 @@ class AddView(BaseView):
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/service/add"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
|
@ -35,8 +35,9 @@ class PGPView(BaseView):
|
||||
POST_PERMISSION(UserAccess): (class attribute) post permissions of self
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
GET_PERMISSION = UserAccess.Reporter
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/service/pgp"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
|
@ -35,6 +35,7 @@ class ProcessView(BaseView):
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Reporter
|
||||
ROUTES = ["/api/v1/service/process/{process_id}"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
|
@ -35,6 +35,7 @@ class RebuildView(BaseView):
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/service/rebuild"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
|
@ -35,6 +35,7 @@ class RemoveView(BaseView):
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/service/remove"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
|
@ -35,6 +35,7 @@ class RequestView(BaseView):
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Reporter
|
||||
ROUTES = ["/api/v1/service/request"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
|
@ -38,6 +38,7 @@ class SearchView(BaseView):
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Reporter
|
||||
ROUTES = ["/api/v1/service/search"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
|
@ -35,6 +35,7 @@ class UpdateView(BaseView):
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/service/update"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Actions"],
|
||||
|
@ -39,6 +39,7 @@ class UploadView(BaseView):
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/service/upload"]
|
||||
|
||||
@staticmethod
|
||||
async def save_file(part: BodyPartReader, target: Path, *, max_body_size: int | None = None) -> tuple[str, Path]:
|
||||
|
@ -41,6 +41,7 @@ class LogsView(BaseView):
|
||||
|
||||
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Full
|
||||
GET_PERMISSION = UserAccess.Reporter
|
||||
ROUTES = ["/api/v1/packages/{package}/logs"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
|
@ -41,6 +41,7 @@ class PackageView(BaseView):
|
||||
|
||||
DELETE_PERMISSION = POST_PERMISSION = UserAccess.Full
|
||||
GET_PERMISSION = UserAccess.Read
|
||||
ROUTES = ["/api/v1/packages/{package}"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
|
@ -41,6 +41,7 @@ class PackagesView(BaseView):
|
||||
|
||||
GET_PERMISSION = UserAccess.Read
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/packages"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
|
@ -41,6 +41,7 @@ class StatusView(BaseView):
|
||||
|
||||
GET_PERMISSION = UserAccess.Read
|
||||
POST_PERMISSION = UserAccess.Full
|
||||
ROUTES = ["/api/v1/status"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Status"],
|
||||
|
@ -37,6 +37,7 @@ class LoginView(BaseView):
|
||||
"""
|
||||
|
||||
GET_PERMISSION = POST_PERMISSION = UserAccess.Unauthorized
|
||||
ROUTES = ["/api/v1/login"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Login"],
|
||||
|
@ -36,6 +36,7 @@ class LogoutView(BaseView):
|
||||
"""
|
||||
|
||||
POST_PERMISSION = UserAccess.Unauthorized
|
||||
ROUTES = ["/api/v1/logout"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Login"],
|
||||
|
@ -17,4 +17,3 @@
|
||||
# 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.web.views.v2.status.logs import LogsView
|
||||
|
@ -37,6 +37,7 @@ class LogsView(BaseView):
|
||||
"""
|
||||
|
||||
GET_PERMISSION = UserAccess.Reporter
|
||||
ROUTES = ["/api/v2/packages/{package}/logs"]
|
||||
|
||||
@aiohttp_apispec.docs(
|
||||
tags=["Packages"],
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user